nodriver’da Async SDK ile CAPTCHA Nasıl Çözülür

nodriver’da bir captcha adımı her zamanki üç hamleden oluşur: sitekey’i sayfadan oku, bir çözücüye gönder, token’ı JavaScript ile geri yaz. Değişen şey, nodriver’ın tepeden tırnağa asenkron olması. Chrome’u bir asyncio websocket’i üzerinden sürer, yani bloklayan bir çözüm yalnızca betiğinizi bekletmez, her DevTools mesajını taşıyan soketi de durdurur. Onu async istemciyle eşleştirin, böylece her şey yanıt verir durumda kalsın.
Neye ihtiyacınız var
- Python 3.10 veya üzeri, ayrıca nodriver 0.50 ve CapSkip SDK kurulu.
- Betiğin çalıştığı yerde kurulu Chrome, Chromium, Edge veya Brave. nodriver onu doğrudan başlatır.
- Korumalı formun sayfa URL’si. Sitekey çalışma zamanında okunur.
- Betik ile çözücü aynı makinedeyse Yerel modda, değilse Sunucu modunda çalışan CapSkip. Her ikisi de şurada açıklanıyor: bağlantı ayarları.
# Both packages, one line. pip install nodriver capskip
nodriver bu işin şeklini neden değiştirir
nodriver, undetected-chromedriver’ın aynı yazar tarafından yazılmış resmi halefi ve en dikkat çeken yanı, yığının hiçbir yerinde webdriver ve Selenium bulunmaması. Kendi başlattığı bir tarayıcıyla DevTools Protocol konuşur. Yamalanacak bir chromedriver ikili dosyası yok, Chrome ile uyumlu tutulacak bir sürücü sürümü yok.
CAPTCHA işi açısından önemli olan, o cümlenin ikinci yarısı: tamamen asenkron. Bağlantı, asyncio tarafından yönetilen bir websocket ve bir arka plan görevi protokol mesajlarını oradan okuyor. Her element araması, her gezinme ve her olay işleyicisi o görevin sıraya alınmasına bağlı. Ortada senkron bir çözücü çağırırsanız, çözüm boyunca süreçte başka hiçbir şey çalışmaz; bu süre reCAPTCHA v2 için rutin olarak on beş ila kırk beş saniyedir.
Yani bu framework için kural kısa. Async istemciyi kullanın ve await edin.
Açık konuşmakta fayda var: webdriver olmaması, tespit olmaması demek değil. Sürücüyü kaldırmak tek bir sinyali kaldırır ve parmak izinizin geri kalanını olduğu yerde bırakır. Bir challenge’ı çözmek ile bir tarayıcı gibi görünmek ayrı işlerdir ve bu yazı ilkini kapsıyor.
Adım 1: sitekey'i sayfadan okuyun
Sitekey, widget iframe’inin içinde değil, ana belgede yer alır. Google’ın işaretlemesi onu bir kapsayıcıya data-sitekey özniteliği olarak koyar ve nodriver’ın select metodu o kapsayıcıyı CSS seçiciyle bulur. Son satırdaki köşeli parantez erişimine dikkat edin, çünkü insanları yakalayan şey tam olarak o.
# pip install nodriver
import nodriver as uc
async def main():
browser = await uc.start()
page = await browser.get("https://example.com/page-with-recaptcha")
# select() retries for 10 seconds by default, so it doubles
# as a wait condition for a widget that renders late.
holder = await page.select("div.g-recaptcha")
sitekey = holder.attrs["data-sitekey"]
print(sitekey) # 6Lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
uc.loop().run_until_complete(main())Öznitelik adları, element üzerinde HTML’de nasıl yazıldıysa aynen, tireler dahil saklanır; bu yüzden bir özniteliği okumanın tek güvenilir yolu köşeli parantez erişimidir. Noktalı kısayol işe yarayacakmış gibi görünür ve sessizce yaramaz: bir elementten data_sitekey istemek hata fırlatmak yerine None döndürür, çünkü o arama bir varsayılana düşer. Sonra o None boş bir anahtar olarak çözücüye gider ve çok daha sonra, ona neden olan satırdan çok uzakta şu hata olarak ortaya çıkar: ERROR_GOOGLEKEY. Bilinmesi gereken tam olarak tek bir gerçek yeniden adlandırma var: class özniteliği, Python anahtar sözcüğüyle çakışmasın diye class_ altında saklanır.
Bazı siteler sitekey’i ana sayfada hiç açığa çıkarmaz ve yalnızca widget iframe URL’sinde geçirir. Bu durumda onu sorgu dizesinden okuyun.
# Fallback: the k= parameter on the anchor iframe.
from urllib.parse import urlparse, parse_qs
frame = await page.select("iframe[src*='recaptcha/api2/anchor']")
sitekey = parse_qs(urlparse(frame.attrs["src"]).query)["k"][0]Adım 2: soketi durdurmadan çözün
Python SDK iki istemciyle geliyor. CapSkip senkrondur; AsyncCapSkip ise bir takma ad değil, gerçek bir asyncio uygulamasıdır ve bu framework’ün ihtiyacı tam olarak budur. İkisi de kendi makinenizdeki çözücüyle 8080 portu üzerinden konuşur ve hiçbiri çözüm başına faturalandırmaz.
# pip install capskip from capskip import AsyncCapSkip solver = AsyncCapSkip(host="127.0.0.1", port=8080) # Same call shape for v3 (version="v3") and Enterprise # (enterprise=1). Invisible v2 takes invisible=1. result = await solver.recaptcha(sitekey=sitekey, url=PAGE_URL) token = result["code"] # the g-recaptcha-response value
Çağrı await edilebilir olduğu için, hiç threading olmadan birden fazla sekme aynı anda çözüm yapabilir. Sayfaları açın, çözümleri toplayın, sonra her token’ı ait olduğu sekmeye enjekte edin. SDK’nın sabit bir aralıkta uyumak yerine sorgulamasını nasıl kademeli olarak seyrelttiği dahil daha geniş desen şurada anlatılıyor: CAPTCHA’ları paralel çözme.
# Three tabs, three solves, one wait.
import asyncio
results = await asyncio.gather(*[
solver.recaptcha(sitekey=k, url=u) for k, u in targets
])Turnstile ve GeeTest’in kendi metotları var ve ikisi de yukarıdaki çağrıyla aynı şekli alıyor. Her birinin tam parametre listesi şurada: CapSkip API dokümantasyonu.
Adım 3: token'ı enjekte edin ve gönderin
Yanıt textarea’sı display:none ile gizlidir, dolayısıyla içine yazmak hiçbir otomasyon aracında seçenek değildir. Onu JavaScript ile yazarsınız. nodriver’ın evaluate metodu, yanına argüman geçirmenin hiçbir yolu olmadan bir ifade dizesi alır; bu yüzden token o dizenin içine gömülmek zorunda ve bunu yapmanın güvenli yolu f-string değil, json.dumps. Bir JSON dize sabiti, tırnaklama ve kaçış dahil, geçerli bir JavaScript dize sabitidir.
# json.dumps gives a correctly quoted JS string literal.
import json
await page.evaluate(
"document.getElementById('g-recaptcha-response').value = "
+ json.dumps(token)
)
# Then submit the form the way the page expects.
button = await page.select("button[type=submit]")
await button.click()Değerin yerine oturup oturmadığını kontrol etmenin kendine ait bir tuzağı var ve on saniyenizi hak ediyor. return_by_value ayarlıyken evaluate, düz Python değerini yalnızca o değer truthy olduğunda geri verir. Boş bir dize ya da bir sıfır bu kapsamın dışına düşer ve elinize bunun yerine bir protokol nesnesi geçer. Yani uzunluğu okuyup test etmeyin, çünkü sıfır uzunluk tam da tespit etmeye çalıştığınız durum. Asla falsy olamayacak bir şey döndürün.
# String() keeps a zero-length answer truthy, so the check
# reports the real number instead of a protocol object.
length = await page.evaluate(
"String(document.getElementById('g-recaptcha-response').value.length)",
return_by_value=True,
)
print(length) # "0" means the injection did not landSite, gönderimde textarea’yı okumak yerine bir callback tanımlıyorsa, değeri ayarladıktan sonra onu çağırın. Fonksiyon adı siteye özgüdür, o yüzden tahmin etmek yerine sayfanın kendi işaretlemesinden okuyun. Her hâlükârda bu hâlâ sıradan bir reCAPTCHA v2: callback, token’ı nasıl teslim ettiğinizi değiştirir, nasıl çözüldüğünü değil. reCAPTCHA v2 çözücü sayfası her iki gönderim biçimini de kapsar.
Tam çalışan örnek
Yukarıdakilerin hepsi tek bir betikte. Çözücü bir kez oluşturulup yeniden kullanılıyor ve tarayıcı bir finally bloğunda durduruluyor; böylece başarısız bir çözüm arkada bir Chrome süreci bırakmıyor.
# pip install nodriver capskip
import json
import nodriver as uc
from capskip import AsyncCapSkip
PAGE_URL = "https://example.com/page-with-recaptcha"
async def main():
solver = AsyncCapSkip(host="127.0.0.1", port=8080)
browser = await uc.start()
try:
page = await browser.get(PAGE_URL)
holder = await page.select("div.g-recaptcha")
sitekey = holder.attrs["data-sitekey"]
if not sitekey:
raise RuntimeError("Widget found but data-sitekey was empty.")
result = await solver.recaptcha(sitekey=sitekey, url=PAGE_URL)
await page.evaluate(
"document.getElementById('g-recaptcha-response').value = "
+ json.dumps(result["code"])
)
button = await page.select("button[type=submit]")
await button.click()
await page.sleep(2)
print(page.target.url) # the page you land on after submitting
finally:
browser.stop()
uc.loop().run_until_complete(main())Çözücüyü başka bir makinede çalıştırmak
nodriver er ya da geç bir sunucuya taşınır ve orada iki şeye ihtiyaç duyar: bir Chromium ikili dosyası ve Chrome’un üzerine çizeceği bir masaüstü oturumu. Headless mod varsayılan olarak kapalı, dolayısıyla böyle bir oturumu olmayan bir sunucuda headless’ın açıkça açılması gerekir. Çözücünün bu yolculuğa onunla birlikte çıkması gerekmez.
CapSkip’in iki bağlantı modu var. Yerel mod 127.0.0.1 adresine bağlanır ve yalnızca o cihaza yanıt verir; betiği yazarken doğru ayar budur. Sunucu modu ağ adresinize veya genel IP’nize bağlanır, böylece bir kazıma VM’i, bir konteyner ana makinesi ya da ikinci bir iş istasyonu aynı çözücüyü API üzerinden çağırır. Sabit bir genel IP o adresi sabit tutar. Kodda geçirdiğiniz host dışında hiçbir şey değişmez ve maliyet açısından da hiçbir şey değişmez, çünkü donanım hâlâ sizin.
# Same SDK, same call. Only the host moves. solver = AsyncCapSkip(host="10.0.0.12", port=8080, apiKey="YOUR_API_KEY")
Çözücü bir ağ adresini dinlemeye başladığında anahtar doğrulamasını açın ve her makineye kendi anahtarını verin; böylece birini diğerlerine dokunmadan iptal edebilirsiniz. kurulum kılavuzu her iki modu da adım adım anlatıyor.
Bunu yaparken karışmaması gereken bir ad çakışması var. nodriver’ın kendi start fonksiyonu da bir host ve bir port kabul eder; bunlar çözücüyü değil, bağlanmak istediğiniz bir Chrome hata ayıklama uç noktasını tanımlar. İkisini birden verirseniz nodriver hiç tarayıcı başlatmaz. Çözücü adresi istemci yapıcısına aittir, başka hiçbir yere değil.
Sık görülen hatalar ve anlamları
| Gördüğünüz | Neden | Düzeltme |
|---|---|---|
| attrs aramasında AttributeError | select() hiçbir şey bulamadı ve None döndürdü | Seçiciyi genişletin veya select zaman aşımını artırın |
| Sitekey hiç hata olmadan None geliyor | Noktalı erişim tireli bir özniteliğe ulaşamaz | Onu attrs üzerinden köşeli parantez erişimiyle okuyun |
| ERROR_GOOGLEKEY | Çözücüye boş bir sitekey ulaştı | Bir çözüm harcamadan önce değeri kontrol edin |
| Betik çözüm boyunca askıda kalıyor | Senkron bir istemci olay döngüsünü bloke etti | AsyncCapSkip kullanın ve çağrıyı await edin |
| NetworkException | CapSkip çalışmıyor ya da ana makine adresi yanlış | Uygulamayı başlatın veya host değerini sunucu adresine yöneltin |
| TimeoutException | Çözüm recaptchaTimeout süresini aştı | Varsayılan 300 saniyenin üzerine çıkarın |
| evaluate bir dize değil, bir nesne döndürüyor | Değer falsy olduğu için düz dönüş atlandı | Değeri değil, uzunluğu String() ile sarın |
FAQ
Yalnızca bir kez çözüyorsam senkron istemciyi kullanmaya devam edebilir miyim?
Edebilirsiniz ve kısa bir betikte bunu hiç fark etmeyebilirsiniz. Feda ettiğiniz şey, çözüm sırasında gelen her protokol mesajı: gezinme olayları, yükleme olayları, bir olay işleyicisinin beklediği her şey. Uzun bir çalıştırmada bu, aramaların görünür bir sebep olmadan zaman aşımına uğraması olarak ortaya çıkar. Async istemcinin maliyeti bir import ve bir await, yani bu takası yapmak için pek bir sebep yok.
reCAPTCHA iframe’ine girmem gerekiyor mu?
Hayır ve insanların gereğinden fazla uğraştığı kısım tam olarak burası. Onay kutusu bir iframe içinde yaşıyor, ama sitekey özniteliği ile gizli yanıt textarea’sının ikisi de ana belgeye ait. Bir frame’e yalnızca site sitekey’i sayfadan esirgediğinde ve onu frame’in kendi URL’sinden okumak zorunda kaldığınızda dokunursunuz.
undetected-chromedriver’dan geçiş yapıyorum. Neler aynı kalıyor?
Üç hamle olduğu gibi kalıyor, çünkü hiçbir zaman sürücüye özgü değildiler: sitekey’i oku, çöz, token’ı textarea’ya yaz. Aynı kalmayan şey, etraflarındaki API; çünkü artık her çağrı await edilebilir ve ortada bir driver nesnesi yok. nodriver ayrıca, çalışan bir undetected-chromedriver örneğini bir browser nesnesine çeviren bir yardımcı da içeriyor; bu da bir betiği aşama aşama taşımanıza imkân veriyor. Eski yaklaşım şurada anlatılıyor: undetected-chromedriver CAPTCHA rehberi.
Kazıyıcım bir VPS üzerinde çalışıyor. Çözücü nereye gitmeli?
Nereye isterseniz, yeter ki ikisi birbirine erişebilsin. Sunucu modu, çözücünün loopback adresi yerine bir ağ adresini dinlemesini sağlar; böylece VPS onu tıpkı herhangi bir dahili servis gibi API üzerinden çağırır. host argümanını o adrese yöneltin, anahtar doğrulamasını açın ve VPS’e kendi anahtarını verin. Çözücünün bir ekrana ihtiyacı yok; tarayıcının olduğu düşünülürse bu işinizi kolaylaştırır.
Kısa özet
Sitekey’i attrs üzerinde köşeli parantez erişimiyle okuyun, 127.0.0.1:8080 üzerinde AsyncCapSkip ile çözün, token’ı json.dumps kullanarak evaluate üzerinden enjekte edin, sonra gönderin. Her şeyi await edin, çünkü senkron bir çözüm tarayıcıyı süren soketi tutar. Selenium ve Playwright dahil daha geniş Python tablosu için bkz. Python CAPTCHA çözücü sayfası.
Bir taramayı büyütmeden önce bir sonucu açıkça söylemekte fayda var. CapSkip, kendi donanımınızda çalışan bir sınırsız captcha çözücü olduğu için, bin sayfayı yeniden deneyen bir çalıştırma, on sayfayı yeniden deneyenle tam olarak aynı maliyeti çıkarır.
