Playwright’ta ALTCHA Nasıl Çözülür ve Gizli Alan Nasıl Doldurulur

Playwright’ta ALTCHA çözmek için işi asla widget’a yaptırmazsınız. ALTCHA, tanıma değil iş kanıtıdır: site bir hashleme problemi dağıtır ve buna doğru cevap veren herkes içeri alınır, bedeli de CPU zamanıdır. Bakılacak bir şey ve tıklanacak bir şey yoktur; yani tarayıcı, CAPTCHA için değil akışın geri kalanı için oradadır. Sayfanın zaten istediği challenge’ı yakalayın, kendi makinenizde milisaniyeler içinde hashleyin, sonra cevabı formun gönderdiği alana yazın. Bu rehber, bunu Python için Playwright ile yapıyor.
Neye ihtiyacınız var
- Bir Windows makinesinde çalışan CapSkip 1.2.6 veya üzeri. ALTCHA desteği o sürümle geldi, dolayısıyla daha eski bir yapıda çağrılacak bir metot yok.
- Python 3.10 veya üzeri; Playwright ve CapSkip paketleri kurulu ve en az bir tarayıcı indirilmiş olmalı.
- Widget’ın üzerinde durduğu sayfanın URL’si. Sitekey gerekmez, çünkü ALTCHA’da sitekey yoktur.
- Çözücü için bir adres. Yerel mod yalnızca o cihaza yanıt vermek üzere 127.0.0.1 üzerinde dinler; Sunucu modu ise ağ adresinizi veya genel IP’nizi dinler, böylece bir konteyner, bir CI runner ya da başka bir makine aynı API üzerinden ona erişebilir. Hangisinin geçerli olduğunu Adım 4 anlatıyor; ikisi de şurada yer alır: bağlantı ayarları.
# pip install playwright capskip pip install playwright capskip playwright install chromium
Adım 1: sayfa açıkken challenge’ı yakalayın
Geri kalan her şey bu tek değere bağlı. Challenge; bir algoritma, bir challenge hash’i, bir salt, bir imza ve bir üst sayı taşıyan küçük bir JSON belgesidir ve site bunu imzalar. Bir Playwright koşusunun içinden ona ulaşmanın iki yolu var; hangisinin işe yaradığı sayfanın nasıl kurulduğuna bağlı.
Uç noktayı widget’tan okuyun
Widget öğesi, soracağı uç noktanın adını taşır. Özniteliği tahmin etmeyin, çünkü widget kuşakları arasında değişti.
| Widget nesli | Challenge’ı adlandıran öznitelik |
|---|---|
| v1 ve v2 | Bir endpoint için challengeurl, challenge satır içi olduğunda ayrı bir challengejson özniteliği |
| v3 ve sonrası | challenge; bu tek öznitelik ya bir URL ya da challenge verisini alır |
# pip install playwright capskip
from playwright.sync_api import sync_playwright
PAGE_URL = "https://example.com/signup"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(PAGE_URL)
# v1 and v2 use challengeurl; v3 and later use challenge.
widget = page.locator("altcha-widget")
endpoint = widget.get_attribute("challengeurl")
if not endpoint:
endpoint = widget.get_attribute("challenge")Widget’ın type özniteliğindeki üç etkileşim stili, yani native, checkbox ve switch, tamamen görseldir. Aynı veriyi gönderirler ve fark asla çözücüye ulaşmaz, dolayısıyla hangisine baktığınızı çözmeniz gerekmez. Ayrı olan display özniteliği de aynı şekilde görseldir. ALTCHA bunların hepsini şurada belgeliyor: kendi widget rehberi.
Ya da sayfanın zaten aldığı yanıtı yakalayın
Attribute’u okumak her zaman yetmez. Bir v3 widget’ı challenge verisini o attribute içinde URL yerine tutabilir, o yüzden değer süslü parantezle başlıyorsa JSON olarak ayrıştırın; tamamen JavaScript’ten yapılandırılmış bir widget ise markup’ta okunacak hiçbir şey bırakmaz. Ağ yanıtını yakalamak bu ikinci durumu karşılar ve size işaretçi yerine belgenin kendisini verir. Beklemeyi, çekmeyi tetikleyen şey her neyse ondan önce kurun; yoksa istek, hiçbir şey dinlemezken gerçekleşir.
# Filter out the widget's own script: its URL also contains
# altcha, and it loads before the challenge is ever requested.
is_challenge = lambda r: ("altcha" in r.url
and "json" in r.headers.get("content-type", ""))
# This fires during navigation only when the widget carries
# auto="onload". Otherwise wrap the click that triggers it.
with page.expect_response(is_challenge) as caught:
page.goto(PAGE_URL)
challenge = caught.value.json()
print(challenge["algorithm"], challenge["maxnumber"])O biçime güvenmeden önce widget’ın auto özniteliğini kontrol edin. Doğrulamanın ne zaman başlayacağına o karar verir ve yalnızca onload değeri isteği gezinme sırasında gönderir. Hiç verilmemişse ya da onfocus veya onsubmit olarak ayarlanmışsa, biri forma dokunana kadar hiçbir şey getirilmez; o yüzden beklemeyi goto çevresinde değil, widget’a yapılan bir tıklama çevresinde kurun.
Tüm URL yerine bir alt dizeyle eşleştirin, ama asla tek başına altcha kelimesiyle değil. Yol her sitede farklıdır ve çoğu zaman önbellek kıran bir sorgu dizesi taşır; bu yüzden tam karşılaştırma, bunun hiç tetiklenmemesinin bir nedenidir, fazla gevşek eşleştirmek de diğeridir: widget script’i genellikle içinde altcha geçen bir yoldan sunulur, ilk o yüklenir ve bekleme, hiçbir JSON ayrıştırıcısının kabul etmeyeceği bir JavaScript üzerinde sonuçlanır. Playwright bu deseni şurada anlatıyor: kendi ağ rehberi.
Adım 2: challenge’ı tek ALTCHA metoduna verin
Tek bir metot ve tam anlatımı şu sayfada: ALTCHA çözücü sayfası, üstelik bu metot challenge’ı her iki biçimde de alıyor. Uç noktayı iletin, çözücü challenge’ı kendisi indirsin. Belgeyi iletin, hiçbir istek dışarı çıkmasın.
from capskip import CapSkip solver = CapSkip(host="127.0.0.1", port=8080) # The document from step 1, so nothing is fetched twice. result = solver.altcha(url=PAGE_URL, challenge_json=challenge) # Or hand over the endpoint and let CapSkip fetch it. # result = solver.altcha(url=PAGE_URL, challenge_url=endpoint) print(result["token"]) # base64 payload for the form field print(result["number"]) # the counter that satisfied it
Tarayıcı onu zaten görmüşse satır içi belgeyi tercih edin. Bir tarayıcı sürmenin tavsiyeyi değiştirdiği tek yer burasıdır. Tarayıcı oturumunuza verilen challenge, sitenin sizi üzerinden değerlendireceği challenge’dır; dolayısıyla çözücüye tam olarak o belgeyi vermek ikisini uyumlu tutar. Bir uç noktadan ikinci bir challenge çekmek yanlış değil, ama bu, sayfa bir challenge tutarken sizin başka birini cevaplamanız anlamına gelir ve challenge’ı oturuma bağlayan bir sitede cevap tutmaz. Seçenek, sizin için serileştirilen bir sözlük alır; elinizde hazır varsa bir JSON dizesi de alır.
O sonuçtaki iki anahtar yalnızca ALTCHA için var. token anahtarı formun istediği base64 veriyi, number anahtarı ise challenge’ı çözen sayacı tutar. code anahtarı token ile aynı dizeyi taşır, yani ikisi de iş görür; ama token anahtarı, gideceği alanın adını taşıyor. GeeTest anahtarları ve Turnstile kullanıcı ajanı burada yok.
Çözücünün kapsadığı algoritmalar
Eski şema SHA-1, SHA-256, SHA-384 ve SHA-512 ile, iş kanıtı v2 ise PBKDF2 ve yinelemeli SHA ile karşılanıyor. PBKDF2, ALTCHA’nın kendi önerdiği varsayılan; yani canlı sitelerin büyük çoğunluğu bu.
Argon2id ve scrypt istisnadır ve denenmek yerine reddedilirler: bunlardan birini isteyen bir challenge, yaklaşık saniyenin üçte biri içinde ERROR_CAPTCHA_UNSOLVABLE olarak geri döner ve asla yeniden denenmez, çünkü belleğe aç bir fonksiyonu yeniden deneme düzeltmez. Bu tipte o sonuç, okunamayan bir görseli değil algoritmayı işaret eder. Hata kodu için şuna bakın: kendine ait bir rehber.
Adım 3: token’ı widget’ın gizli alanına yazın
Widget, verisini adı kendi name özniteliğinden gelen bir gizli input içinde gönderir; o özniteliğin varsayılanı altcha’dır. Varsaymak yerine, challenge özniteliğini okuduğunuz gibi bunu da okuyun. Tarayıcı koşusunda alanı kendiniz doldurursunuz, çünkü widget’ı hiçbir şey doğrulamadı ve widget hiçbir şey doldurmuş olmayacak.
# Walk up from the submit button so the field lands in the
# form that actually posts, not in the first form on the page.
SET_ALTCHA_FIELD = """({name, token}) => {
const button = document.querySelector('button[type=submit]');
const form = button ? button.form : document.querySelector('form');
let field = form.querySelector('[name=' + name + ']');
if (!field) {
field = document.createElement('input');
field.type = 'hidden';
field.name = name;
form.appendChild(field);
}
field.value = token;
}"""
field_name = widget.get_attribute("name") or "altcha"
page.evaluate(SET_ALTCHA_FIELD, {"name": field_name, "token": result["token"]})
page.click("button[type=submit]")Doğru formu seçin. Bir kayıt sayfasında çoğu zaman birden fazla form bulunur; gönder düğmesi başka bir forma aitken alanı sayfadaki ilk forma eklemek, sunucunun hiçbir zaman bir değer görmemesi demektir. Snippet tam da bu yüzden gönder düğmesinden yukarı doğru çıkıyor.
Dizeyi olduğu gibi geçirin. Token, alanları sunucunun HMAC imzası kapsamında olan bir JSON belgesinin base64 hâlidir; bu yüzden düzeltme gibi görünen her şey onu bozar: boşluk kırpmak, çözüp yeniden kodlamak ya da JSON’u anahtarlar başka sırada olacak şekilde yeniden kurmak. Bazı entegrasyonlar veriyi bir form alanı yerine bir JSON gövde alanından okur ve widget bunu bir çerezle teslim edecek şekilde de yapılandırılabilir; o yüzden sayfanın kendi gönderiminin ne yolladığına bakın ve aynısını yapın.
Düz bir HTTP istemcisinde olmayan, tarayıcının eklediği bir şey var: sayfa, form üzerinde kendi script’ini çalıştırabilir. Gönder düğmesi devre dışı kalıyorsa sayfa, alanı okumak yerine widget’ın başarılı olduğunu duymayı bekliyordur. Burada iki dürüst cevap var ve hangisini seçeceğiniz sayfadan ne kadarını korumak istediğinize bağlı. Sayfanın neyi dinlediğini bulup onu karşılayabilirsiniz ya da düğmeyi atlayıp formun alanlarını doğrudan tarayıcının çerezleriyle gönderebilirsiniz; ikincisi genelde daha kısa ve her zaman daha kararlıdır.
Adım 4: Playwright CI’ya taşındığında çözücü nerede çalışır
Yukarıdaki örnekler 127.0.0.1 kullanıyor, çünkü script’iniz ve CapSkip aynı makineyi paylaşırken doğrusu bu. Çözücüyü tarayıcı ya da sayfa değil, sizin Python kodunuz çağırır; dolayısıyla adresi belirleyen şey, test sürecinin nerede çalıştığıdır. Playwright bunu unutmayı kolaylaştırır, çünkü tarayıcı zaten çoğu zaman başka bir yerdedir.
O süreci resmî Playwright Docker imajına ya da bir CI runner’a taşıyın; loopback artık konteyneri gösterir ve orada dinleyen hiçbir şey olmadığı için ilk çözüm bir NetworkException fırlatır. CapSkip’i Sunucu moduna alın; bunun yerine ağ adresinizi veya genel IP’nizi dinler ve konteyner aynı HTTP API üzerinden bağlanır. Rota internetten geçiyorsa statik bir genel IP önerilir; yalnızca beklediğiniz adreslere izin veren bir güvenlik duvarı kuralıyla birlikte. Sunucu modu yalnızca çözücünün nerede dinlediğini değiştirir, başka hiçbir şeyi değil: donanım hâlâ sizin ve kullanım hâlâ ölçülmüyor.
| Python sürecinin çalıştığı yer | Hangi bağlantı modu |
|---|---|
| CapSkip makinesinde, yerel bir tarayıcı sürerek | Local modu. 127.0.0.1 gerçekten doğru |
| Aynı ağdaki başka bir makinede | Server modu, o makinenin özel adresinde |
| Bir Playwright konteynerinde, bir CI runner’da veya bir VPS üzerinde | Statik bir genel IP ve bir güvenlik duvarı kuralı ile Server modu |
| Yerelde, ama uzak bir tarayıcıya bağlanarak | Yerel mod. Tarayıcı çözücüyle hiç konuşmaz |
Tek bir script’in her iki yerde de çalışması için host ve port değerlerini ortamdan okuyun. Bunları geçmek istemezseniz istemci CAPSKIP_HOST, CAPSKIP_PORT ve CAPSKIP_API_KEY değerlerini kendisi de alır.
Proxy’lerle ilgili ALTCHA’ya özgü bir not. Burada proxy destekleniyor, ama yalnızca challenge’ın indirilmesi için kullanılıyor. Üzerinden yönlendirilecek bir tarayıcı oturumu yok, yani iş kanıtının kendisine hiçbir etkisi olmuyor; challenge belgesini satır içi geçtiğinizde ise hiçbir işe yaramıyor.
Tam çalışan örnek
import os
from capskip import CapSkip, ApiException, NetworkException, TimeoutException
from playwright.sync_api import sync_playwright
PAGE_URL = "https://example.com/signup"
SET_ALTCHA_FIELD = """({name, token}) => {
const button = document.querySelector('button[type=submit]');
const form = button ? button.form : document.querySelector('form');
let field = form.querySelector('[name=' + name + ']');
if (!field) {
field = document.createElement('input');
field.type = 'hidden';
field.name = name;
form.appendChild(field);
}
field.value = token;
}"""
solver = CapSkip(
host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"),
port=int(os.environ.get("CAPSKIP_PORT", 8080)),
)
def is_challenge(r):
return "altcha" in r.url and "json" in r.headers.get("content-type", "")
with sync_playwright() as p:
page = p.chromium.launch(headless=True).new_page()
# Catch, solve and submit with nothing slow in between.
with page.expect_response(is_challenge) as caught:
page.goto(PAGE_URL)
try:
result = solver.altcha(url=PAGE_URL, challenge_json=caught.value.json())
except ApiException:
raise SystemExit("refused: Argon2id, scrypt, or an expired challenge")
except NetworkException:
raise SystemExit("solver unreachable: check host and connection mode")
except TimeoutException:
raise SystemExit("no answer inside defaultTimeout")
field_name = page.locator("altcha-widget").get_attribute("name") or "altcha"
page.fill("input[name=email]", "someone@example.com")
page.evaluate(SET_ALTCHA_FIELD, {"name": field_name, "token": result["token"]})
page.click("button[type=submit]")Dört istisnanın hepsi CapSkipError’dan türüyor; dolayısıyla onu yakalamak, SDK’nın fırlatabileceği her hatayı tek bir blokta karşılar. Yanıt farklılaştığında yukarıdaki gibi özel olanları, farklılaşmadığında CapSkipError’ı yakalayın.
Diğer tipler aynı istemciden aynı şekilde çalışıyor. reCAPTCHA ve Turnstile bir sitekey ile bir sayfa URL’si alır, GeeTest bir gt değeri, bir challenge ve sayfa URL’sini alır, görsel çözme ise bir dosya yolu, bir URL veya base64 alır. Paketin sunduğu bütün metotlar şurada listeleniyor: Python CAPTCHA çözücü sayfası, tarayıcı tarafının tamamı ise şurada: Playwright CAPTCHA çözücü sayfası.
Sık görülen hatalar ve anlamları
| Gördüğünüz | Neden | Düzeltme |
|---|---|---|
| Her iki widget özniteliği de None dönüyor | Widget tamamen JavaScript’ten yapılandırılmış, dolayısıyla iki addan hiçbiri markup’ta yok | Bunun yerine yanıtı yakalayın; bu yöntem markup’a bağlı değildir |
| get_attribute 30 saniye takılıyor, sonra hata fırlatıyor | Widget öğesi hiç görünmedi, locator da varsayılan zaman aşımını doldurdu | Seçiciyi işlenmiş sayfaya karşı kontrol edin, sonra yanıtı yakalamaya geri dönün |
| Yanıt beklemesi zaman aşımına uğruyor | Widget’ın auto özniteliği onload olarak ayarlı değil, yani hiçbir şey getirilmedi; ya da bekleme gezinmeden sonra kuruldu | Getirmeyi tetikleyen her neyse onu sarın ve bağlam yöneticisini ondan önce açın |
| Yakalanan yanıtta JSON ayrıştırma hatası | Bekleme, URL’sinde yine altcha geçen widget’ın kendi script’i üzerinde sonuçlandı | Filtreye JSON içerik tipini ekleyin |
| Az önce yakaladığınız bir challenge’da ApiException | Satır içi challenge’ın süresi çoktan dolmuştu, çözücü de hashlemek yerine onu reddetti | Tek nefeste yakalayıp çözün ya da uç noktayı geçin ki çözücü yeniden indirebilsin |
| Günlüğünüzün çözüldüğünü gösterdiği bir token’la, çıplak bir doğrulama hatası | Challenge’ın süresi, çözüm ile gönderim arasında doldu | Arada yavaş hiçbir şey olmadan yakalayın, çözün ve gönderin |
| Yaklaşık saniyenin üçte biri içinde, bir ApiException içinde ERROR_CAPTCHA_UNSOLVABLE | Challenge, Argon2id veya scrypt kullanıyor | Yeniden denenecek bir şey yok. Bu ikisi tasarım gereği reddedilir |
| İlk çözümde bir NetworkException | CapSkip çalışmıyor ya da script bir konteynerde ve loopback’e yönlendirilmiş | CapSkip’i başlatın, sonra Yerel mod ile Sunucu modu arasında seçim yapın |
| Form gönderiliyor ama sunucu eksik bir altcha değeri bildiriyor | Gizli alan, sayfadaki başka bir forma eklendi | Gönder düğmesinin ait olduğu formu sorgulayın |
| 120 saniyeyi işaret eden bir TimeoutException | Çözücü, varsayılan yoklama zaman aşımı içinde yanıt vermedi | Çözücünün çalıştığını ve dolu olmadığını kontrol edin. Tavanı yükseltmek aynı cevabı yalnızca geciktirir |
| Gönder düğmesi hiçbir zaman etkinleşmiyor | Sayfa bunu, kendi script’inin widget’ın başarılı olduğunu görmesine bağlıyor | Form alanlarını doğrudan gönderin ya da sayfanın dinlediği her neyse onu karşılayın |
| Çağrıda bir ValidationException | Challenge seçeneklerinden hiçbiri verilmedi ya da ALTCHA’nın almadığı bir seçenek geçildi | Uç noktayı ya da belgeyi geçin ve başka her şeyi bırakın |
FAQ
ALTCHA’yı çözmek için tarayıcıya ihtiyacım var mı?
Hayır. ALTCHA bir hashleme problemidir; CPU ile çözülür ve cevapta hiçbir tarayıcı yer almaz. Playwright’ı açmanızın tek nedeni CAPTCHA idiyse kapatın: challenge’ı bir HTTP istemcisiyle indirin ve token’ı geri gönderin. Bunu düz Python rehberi adım adım anlatıyor. Playwright, akışın geri kalanı gerçek bir sayfaya ihtiyaç duyduğunda yerini hak eder: çerez kuran bir giriş, çok adımlı bir form ya da işaretlemesini script içinde üreten bir site gibi.
Playwright, Docker veya GitHub Actions üzerinden çözücüye erişebilir mi?
Evet, Sunucu modu üzerinden. CapSkip’i bağlantı ayarları altında loopback yerine ağ adresinizi ya da genel IP’nizi dinleyecek şekilde ayarlayın, sonra host ortam değişkenini ona yöneltin. Konteyner, runner ve çözücü böylece tek bir makinedeymiş gibi aynı HTTP API’yi konuşur. Rota internetten geçiyorsa statik bir genel IP kullanın ve bir güvenlik duvarı kuralıyla kısıtlayın. Çözücü her durumda sizin sahip olduğunuz donanımda kalır; yani lisansta ya da çözüm sayısında hiçbir şey değişmez.
Bir ALTCHA token’ı ne kadar geçerli kalır?
Uzun değil ve süreyi site belirler. Bazı pencereler iki dakika dolmadan kapanır. Biri dolduğunda site, cevabı tam olarak yanlış bir cevap gibi görünen çıplak bir doğrulama hatasıyla reddeder ve yanıtta hangisinin olduğunu size söyleyen hiçbir şey bulunmaz. Bu yüzden challenge’ları önceden toplamayın, tarayıcı üç sayfa daha gezerken bir token’ı değişkende bekletmeyin ve bir kişi formu doldururken asla token tutmayın. Taze bir çözüm milisaniyeler sürer; bu, bayat birinin neden başarısız olduğunu çözmekten daha ucuzdur.
Playwright’ın kendi zaman aşımları çözümü yarıda keser mi?
Hayır, çünkü çözüm bir Playwright çağrısı değil. Varsayılan 30 saniyelik eylem ve gezinme zaman aşımları tıklamaları, beklemeleri ve sayfa yüklemelerini kapsar; sizin çözücü çağrınız ise bunlardan ikisinin arasında duran sıradan bir Python satırıdır. Geçerli olan üst sınır, istemcinin kendi varsayılan 120 saniyelik yoklama zaman aşımıdır; ALTCHA bir tarayıcı oturumu değil CPU işi olduğu için daha uzun reCAPTCHA sınırı yerine bunu kullanır. Bunun yerine testin tamamını saran bir sınıra dikkat edin: test başına bir eklenti zaman aşımı ya da bir CI iş sınırı gibi.
Kısa özet
Widget’ın istediği challenge’ı ister öznitelikten ister yanıttan yakalayın, o belgeyi sayfa URL’siyle birlikte tek ALTCHA metoduna verin ve token’ı, adını widget’ın kendi name özniteliğinden alan gizli bir alana, gerçekten gönderim yapan formun içine yazın. Token’a yolda dokunmayın. Yakalama, çözme ve gönderimi birbirine yakın tutun, çünkü pencere iki dakika dolmadan kapanabilir ve süresi dolmuş bir challenge yanlış bir cevaptan ayırt edilemez. Script çözücüyle aynı makineyi paylaşmayı bıraktığı anda Sunucu moduna geçin.
- Bu tipin ne olduğu ve nasıl çalıştığı: ALTCHA çözücü sayfası.
- Tarayıcısız aynı iş: düz Python ile ALTCHA çözme.
Yeniden denemeyi nasıl tasarlayacağınızı değiştiren son bir nokta. Bu yol yerel captcha çözücü üzerinden iş kanıtını zaten sahip olduğunuz bir makinede hesapladığı için, elenen bir challenge size yalnızca kendi CPU’nuzdan birkaç milisaniyeye mal olur, başka hiçbir şeye değil; yani uzun bir koşu boyunca bayat bir token’ı idare etmek yerine sayfayı yeniden yükleyip taze bir tane almayı göze alabilirsiniz.
