DrissionPage’de CAPTCHA Nasıl Çözülür ve Token Nasıl Enjekte Edilir

drissionpage captcha - How to Solve CAPTCHA in DrissionPage and Inject the Token

DrissionPage’de bir captcha adımı üç hamleden oluşur: sitekey’i sayfadan oku, bir çözücüye gönder, sonra token’ı JavaScript ile forma geri yaz. DrissionPage, bir webdriver yerine DevTools Protocol üzerinden gerçek bir Chrome sürer, yani tarayıcı tarafı Selenium’a göre daha basittir. Element modeli değil ve eksik bir widget’ı locator’ınızdan gelen bir hata yerine çözücüden gelen bir hataya çeviren bir varsayılanı var. Bu betiklerin çoğunun kafa karıştırıcı biçimde başarısız olmasının nedeni işte o varsayılan, o yüzden kendi bölümü var.

Neye ihtiyacınız var

  • Python 3.10 veya üzeri, ayrıca DrissionPage 4.1 ve CapSkip SDK kurulu.
  • DrissionPage’in başlatabileceği veya bağlanabileceği bir Chromium tarayıcı.
  • Korumalı formun sayfa URL’si. Çalışma zamanında okuduğunuz sitekey.
  • Betik ile çözücü aynı makinedeyse Yerel modda, betik başka bir makinede çalışıyorsa Sunucu modunda çalışan CapSkip. Her ikisi de şurada açıklanıyor: bağlantı ayarları.
# Both packages, one line.
pip install DrissionPage capskip

DrissionPage bu işin şeklini neden değiştirir

DrissionPage, Chrome ile doğrudan CDP üzerinden konuşur. Arada bir chromedriver süreci yok, yani yamalanacak bir sürücü ikili dosyası ve tarayıcı sürümünüzle uyumlu tutulacak ayrı bir servis de yok. Ayrıca zaten çalışan bir tarayıcıya bağlanabiliyor ve bu burada gerçekten işe yarıyor: bir kez elle giriş yapar, profili açık bırakır ve betiğin oturumu devralmasına izin verirsiniz.

Değişmeyen şey token. Çözülmüş bir reCAPTCHA, gönderimden önce gizli g-recaptcha-response textarea’sında son bulması gereken bir dizedir. O textarea display:none, dolayısıyla içine yazmak hiçbir otomasyon aracında seçenek değildir. Onu JavaScript ile yazarsınız ve DrissionPage’in run_js metodu bunu yapmanın yoludur.

Açık konuşmakta fayda var: webdriver olmaması, tespit olmaması demek değil. Chrome’u CDP üzerinden çalıştırmak tek bir sinyali ortadan kaldırır ve parmak izinizin geri kalanını tam olduğu yerde bırakır. Challenge’ı çözmek, bir tarayıcı gibi görünmekten ayrı bir iştir ve bu yazı yalnızca ilkini kapsıyor.

Adım 1: sitekey'i sayfadan okuyun

Sitekey, widget iframe’inin içinde değil, ana belgede durur. Google’ın kendi işaretlemesi onu bir kapsayıcı elemente şu öznitelikle koyar: data-sitekey, DrissionPage’in öznitelik locator’ı da onu bir CSS seçicisi olmadan bulur.

# pip install DrissionPage
from DrissionPage import ChromiumPage

page = ChromiumPage()
page.get("https://example.com/page-with-recaptcha")

# @attr finds by attribute. The default search timeout is 10s,
# which is plenty for a widget that renders on load.
holder = page.ele("@data-sitekey")
sitekey = holder.attr("data-sitekey")

print(sitekey)   # 6Lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

DrissionPage’in locator önekleri burada öğrenmeye değer, çünkü aksi halde yazacağınız XPath’in çoğunun yerini alırlar: @attr=value tam eşleşir, @attr:value bir alt dizeyi eşler, @attr^value başlangıcı eşler, @attr$value ise sonu eşler. Bir locator’ın önüne tag:iframe örneğindeki gibi bir etiket adı koymak, element tipini de sabitler.

Bazı siteler sitekey’i ana sayfaya hiç koymaz ve yalnızca iframe URL’sinde geçirir. Bu durumda onu oradan okuyun; istediğiniz frame’i eşlemek için contains kullanın.

# Fallback: the k= query parameter on the widget iframe.
from urllib.parse import urlparse, parse_qs

frame = page.ele("tag:iframe@src:recaptcha/api2/anchor")
src = frame.attr("src")
sitekey = parse_qs(urlparse(src).query)["k"][0]

Adım 2: yerel API ile çözün

Çözücü kendi makinenizde 8080 portunda çalışır ve 2captcha API’sini konuşur, dolayısıyla SDK çağrısı tek satırdır ve arkasında çözüm başına faturalandırma yoktur. Sayfa URL’sini elle yeniden yazmak yerine page.url üzerinden geçirin, çünkü get() ile çözüm arasında olacak bir yönlendirme aksi halde yanlış URL’yi göndermenize yol açardı.

# pip install capskip
from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)

# Same call shape for v3 (version="v3") and Enterprise (enterprise=1).
result = solver.recaptcha(sitekey=sitekey, url=page.url)

token = result["code"]   # the g-recaptcha-response value

Turnstile ve GeeTest’in kendi metotları var: solver.turnstile() ve solver.geetest(). İkisi de yukarıdaki çağrıyla aynı şekli alır. Her birinin tam parametre listesi şurada: CapSkip API dokümantasyonu.

Adım 3: token'ı enjekte edin ve gönderin

DrissionPage ekstra argümanları run_js metoduna konuma göre geçirir ve betik onları arguments[0] ve devamı olarak okur. JavaScript’i bir f-string ile kurmak yerine token’ı argüman olarak geçirin: token uzun, anlaşılmaz ve zaman zaman tırnaklamayı hiç düşünmek istemeyeceğiniz karakterlerle dolu olur.

# Extra args arrive as arguments[0], arguments[1], ...
page.run_js(
    "document.getElementById('g-recaptcha-response').value = arguments[0];",
    token,
)

# Then submit the form the way the page expects.
page.ele("tag:button@type=submit").click()

Site gönderimde textarea’yı okumak yerine bir callback tanımlıyorsa, değeri ayarladıktan sonra onu çağırın. Bu siteye özgü bir fonksiyon adıdır, o yüzden tahmin etmek yerine sayfanın kendi işaretlemesinden okuyun. Ayrıca bunun hâlâ sıradan bir reCAPTCHA v2 olduğunu unutmayın: 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.

En çok zaman kaybettiren sessiz hata

DrissionPage, Settings.raise_when_ele_not_found ayarı False olarak gelir. Hiçbir şeyle eşleşmeyen bir locator hata fırlatmaz. Geriye bir NoneElement döner; bu değer falsy’dir ve Python’ın None değerine eşit çıkar, betiğiniz de yoluna devam eder.

Bu bilinçli bir tasarım tercihi ve isteğe bağlı elementleri yoklarken hoş bir davranış. Burada hoş değil, çünkü bir sonraki yaptığınız şey bir öznitelik okumak ve aldığınız hata widget’ı değil locator’ı işaret ediyor. Daha kötüsü, elementin gerçekten var olduğu ama hiç data-sitekey özniteliği taşımadığı durum. O zaman attr() geriye None döndürür, o None boş bir anahtar olarak ta çözücüye kadar gider ve hata en sonunda, hiç şüphelenmediğiniz bir API çağrısından gelen ERROR_GOOGLEKEY olarak ortaya çıkar.

İki satır bunu düzeltir. Hata fırlatmayı açın ve bir çözüm harcamadan önce sitekey’i kontrol edin.

# Make a missing element fail where it happened.
from DrissionPage.common import Settings

Settings.set_raise_when_ele_not_found(True)

# And still check the value, because a found element can hold nothing.
if not sitekey:
    raise RuntimeError("No sitekey on the page. Check the widget rendered.")

Tam çalışan örnek

Yukarıdakilerin hepsi tek bir betikte. finally bloğu burada normalden daha önemli, çünkü DrissionPage sizin başlatmadığınız bir tarayıcıya bağlanabilir ve devraldığınız bir tarayıcıyı kapatmak nadiren istediğiniz şeydir.

# pip install DrissionPage capskip
from DrissionPage import ChromiumPage
from DrissionPage.common import Settings
from capskip import CapSkip

Settings.set_raise_when_ele_not_found(True)

PAGE_URL = "https://example.com/page-with-recaptcha"
page = ChromiumPage()
solver = CapSkip(host="127.0.0.1", port=8080)

try:
    page.get(PAGE_URL)
    sitekey = page.ele("@data-sitekey").attr("data-sitekey")
    if not sitekey:
        raise RuntimeError("Widget found but data-sitekey was empty.")

    result = solver.recaptcha(sitekey=sitekey, url=page.url)
    page.run_js(
        "document.getElementById('g-recaptcha-response').value = arguments[0];",
        result["code"],
    )
    page.ele("tag:button@type=submit").click()
    page.wait.doc_loaded()

    print(page.title)   # the page you land on after submitting
finally:
    page.quit()

Çözücüyü başka bir makinede çalıştırmak

Kazıyıcılar er ya da geç bir sunucuya taşınır ve DrissionPage onlarla birlikte taşınır: bir Chromium ikili dosyasına ve bir görüntü arabelleğine ihtiyaç duyar, çok daha fazlasına değil. Çözücünün onu aynı makineye kadar takip etmesi gerekmez.

CapSkip’in iki bağlantı modu var. Yerel 127.0.0.1 adresine bağlanır ve yalnızca o cihazdan erişilebilir; betiği yazarken doğru ayar budur. Sunucu 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ğırabilir. Sabit bir genel IP o adresi sabit tutar. Kodda geçirdiğiniz ana makine adresi dışında hiçbir şey değişmez ve fiyatlandırma da değişmez, çünkü donanım hâlâ sizin.

# Same SDK, same call. Only the host moves.
solver = CapSkip(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. Bu konuda kurulum kılavuzu her iki modu da adım adım anlatıyor.

Sık görülen hatalar ve anlamları

GördüğünüzNedenDüzeltme
attr() üzerinde ElementNotFoundErrorLocator hiçbir şeyle eşleşmedi ve NoneElement döndürdüLocator’ı genişletin veya widget’ın render edilmesini bekleyin
Betik eksik bir elementi geçip devam ediyorraise_when_ele_not_found varsayılan olarak FalseSettings.set_raise_when_ele_not_found(True)
ERROR_GOOGLEKEYattr() None döndürdü ve boş bir anahtar gönderildiDeğeri kontrol edin veya k parametresini iframe’den okuyun
NetworkExceptionCapSkip ç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
Form, sorunsuz çözülmüş bir token'ı reddediyorToken gönderimden önce eskidiSayfa yüklenirken değil, göndermeden hemen önce çözün

FAQ

ChromiumPage’i mi yoksa daha yeni Chromium sınıfını mı kullanmalıyım?

İkisi de olur. 4.1 sürümü Chromium, ChromiumPage, SessionPage ve WebPage sınıflarını üst düzey paketten dışa aktarır ve tek bir sekmeye giden en kısa yol hâlâ ChromiumPage. Hangisini seçerseniz seçin CAPTCHA tarafı aynıdır, çünkü sitekey okuma, çözüm ve token enjeksiyonu hep sekme nesnesi üzerinden gerçekleşir.

reCAPTCHA iframe’ine geçmem gerekiyor mu?

Hayır ve insanların fazla mühendislik yaptığı kısım tam olarak burası. Onay kutusu bir iframe’de yaşıyor ama sitekey özniteliği de gizli yanıt textarea’sı da 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.

Betiğim neden hiç şikâyet etmeden None bir sitekey geçiriyor?

Çünkü DrissionPage’in varsayılanı hata fırlatmak yerine falsy bir yer tutucu döndürmek ve çünkü var olan bir element yine de istediğiniz özniteliği taşımayabilir. Her iki yol da string sandığınız bir değişkenin içinde bir None ile bitiyor. Dosyanın başında hata fırlatmayı açın ve değere açık bir kontrol ekleyin; ikisi birlikte her iki durumu da kapsar.

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.

Kısa özet

Sitekey’i bir öznitelik locator’ıyla okuyun, çözümü şurada yapın: 127.0.0.1:8080, token’ı run_js ve konumsal argümanlarla enjekte edin, ardından gönderin. Bunların hiçbirine başlamadan önce raise_when_ele_not_found ayarını açın, çünkü varsayılan tam da ilk karşılaşacağınız hatayı gizliyor. Selenium ve Playwright dahil daha geniş Python tablosu için bkz. Python CAPTCHA çözücü sayfası. Ve çözücü ölçümlü bir hizmet değil, bir yerel captcha çözücü olduğu için, bin sayfayı yeniden deneyen bir tarama, on sayfayı yeniden deneyen bir taramayla aynı maliyeti çıkarır.