Crawl4AI’da Oturumu Kaybetmeden CAPTCHA Nasıl Çözülür

Crawl4AI’ın kendi CAPTCHA çözücüsü yoktur, dolayısıyla bir Crawl4AI captcha akışı, birbirine bağladığınız üç çağrıdan oluşur: sayfayı adlandırılmış bir oturumda yükleyin, sitekey’i harici bir çözücüyle çözün, ardından token’ı yerleştirip göndermek için aynı sekmeye geri dönün. İnsanları tuzağa düşüren kısım, Crawl4AI’ın işleri çalıştırma sırasıdır. 0.8.5 sürümünden beri js_code, wait_for’dan sonra çalışır; dolayısıyla formu js_code içinde gönderen ve sonraki sayfayı wait_for ile bekleyen bir tarama, zaman aşımına uğrayana kadar orada bekler. Gönderim js_code_before_wait içinde yapılmalıdır. Bu rehber önce reCAPTCHA v2 ile oturum akışını, ardından her çağrıyı sizin kontrol etmediğiniz derin taramalar (deep crawl) için bir hook’u adım adım anlatıyor.
Neye ihtiyacınız var
- Python 3.10 veya daha yenisi ve Crawl4AI 0.9 veya sonrası. Buradaki her şey 0.9.4 kaynak kodundan okundu ve bu sürümle çalıştırıldı; 3. adımdaki sıralama 0.8.5’e kadar uzanır.
- CapSkip Python paketi; size, Crawl4AI’ın zaten üzerinde çalıştığı event loop’a uyan, gerçekten asenkron bir istemci olan AsyncCapSkip’i verir.
- CAPTCHA’nın bulunduğu sayfanın URL’si ve yalnızca form başarıyla gönderildikten sonra görünen bir öğe için bir seçici (selector).
- Bir Windows makinesinde çalışan CapSkip. Crawler aynı makinede çalışıyorsa Local modu 127.0.0.1 üzerinden yanıt verir; çalışmıyorsa Server modu ağınızı veya genel IP’nizi dinler. Her ikisi de şurada anlatılıyor: bağlantı ayarları.
# pip install crawl4ai pip install -U crawl4ai capskip # Downloads the browser Crawl4AI drives, once per machine. crawl4ai-setup
1. Adım: sayfayı bir oturumda yükleyin ve sitekey’i okuyun
İlk çağrıya bir session_id verin. Bu, çağrı döndükten sonra sekmeyi çerezleriyle ve widget’ıyla birlikte bozulmadan açık tutar; böylece daha sonra çözdüğünüz token, onu isteyen sayfaya ulaşır. session_id olmadan Crawl4AI, HTML’i alır almaz sayfayı kapatır.
# pip install crawl4ai
import re
from crawl4ai import CrawlerRunConfig
PAGE_URL = "https://example.com/signup"
SESSION = "signup"
# The widget element, in any attribute order, with or without other classes.
WIDGET = r'<[^>]*class="(?:[^"]*\s)?g-recaptcha(?:\s[^"]*)?"[^>]*>'
async def read_sitekey(crawler):
# session_id keeps this tab open for the next arun call.
config = CrawlerRunConfig(session_id=SESSION)
first = await crawler.arun(PAGE_URL, config=config)
# Do not stop on first.success: a CAPTCHA page can be
# flagged as blocked while its HTML is complete.
widget = re.search(WIDGET, first.html)
match = widget and re.search(r'data-sitekey="([^"]+)"', widget.group(0))
return match.group(1) if match else NoneO yorum boşuna orada değil. Crawl4AI 0.9 her sonuçta bir anti-bot kontrolü çalıştırır ve engelleme sayfası olarak okuduğu bir sayfayı başarısız olarak işaretler: success False döner ve error_message, Blocked by anti-bot protection ile başlar. Her 403 veya 503 HTML yanıtı bu kapsama girer, 429 da öyle. Diğer hata durum kodlarında, widget’ının class özniteliği tam olarak g-recaptcha olan 10 KB altındaki bir sayfa bu kapsama girer. Render edilmiş HTML yine first.html içindedir ve ihtiyacınız olan sitekey hâlâ oradadır; bu yüzden taramanın başarısız olduğuna karar vermeden önce onu okuyun.
html alanı render edilmiş sayfadır, dolayısıyla script ile oluşturulan bir widget da içindedir. Anahtar öğenin üzerinde değilse widget iframe’inin src değerinde k= parametresini arayın; widget geç render ediliyorsa ilk çağrıya o iframe için bir wait_for ekleyin.
2. Adım: AsyncCapSkip ile çözün
Crawl4AI baştan sona asyncio tabanlıdır, bu yüzden asenkron istemciyi kullanın. Bu istemci, bloklayan çağrıların etrafına sarılmış bir sarmalayıcı değil, gerçek bir coroutine’dir; yani yirmi saniye süren bir çözüm, aynı event loop üzerindeki diğer tüm taramaları dondurmaz.
# pip install capskip
from capskip import AsyncCapSkip
solver = AsyncCapSkip(host="127.0.0.1", port=8080)
async def solve(sitekey):
# Invisible v2 takes invisible=1, Enterprise takes enterprise=1.
result = await solver.recaptcha(sitekey=sitekey, url=PAGE_URL)
return result["code"] # the g-recaptcha-response valueBu çalışırken Crawl4AI içinde bekleyen hiçbir şey yoktur, çünkü çözüm tek bir çağrının içinde değil, iki çağrının arasında gerçekleşir. Sekme orada öylece durur. Sayacı işleyen şey ise token’dır: bir reCAPTCHA token’ı verildikten sonra yaklaşık iki dakika geçerlidir, bu yüzden doğrudan gönderime geçin. Ayrıntılar şurada: bir reCAPTCHA token'ının ne kadar süre geçerli kaldığı.
3. Adım: token’ı aynı sekmede enjekte edin ve gönderin
İkinci çağrı oturumu yeniden kullanır ve js_only ayarlar; bu, Crawl4AI’a URL’yi yeniden yüklemek yerine elindeki sayfada JavaScript çalıştırmasını söyler. Yeniden yükleme ikinci bir sayfa yüklemesine mal olur ve bu yükleme baştan yeniden bir challenge ile karşılaşabilir; ayrıca ilk yüklemenin kurduğu her şeyi, örneğin kısmen doldurulmuş bir formu, sıfırlar.
import json
async def submit(crawler, token):
# Crawl4AI wraps this in an async function, so statements work.
inject = (
"document.getElementById('g-recaptcha-response').value = "
f"{json.dumps(token)};"
"document.querySelector('form').submit();"
)
config = CrawlerRunConfig(
session_id=SESSION,
js_only=True, # same tab, no reload
js_code_before_wait=inject, # runs BEFORE wait_for
wait_for="css:.signup-complete",
)
return await crawler.arun(PAGE_URL, config=config)Script’in js_code_before_wait içine girmesinin nedeni şu. 0.8.5’ten beri ve her 0.9 sürümünde bir tarama önce js_code_before_wait’i, sonra wait_for’u, en son da bitmiş sayfa üzerinde js_code’u çalıştırır. Yani gönderimi js_code içine koyarsanız wait_for, daha hiçbir şey gönderilmeden sonraki sayfayı aramaya başlar ve varsayılanı 60 saniye olan page_timeout dolana kadar aramayı sürdürür; o noktada çağrı Wait condition failed ile başarısız olur ve form hiç gönderilmez. Crawl4AI’ın kendi dokümantasyonundaki bir örnek de dahil olmak üzere, aynı çağrıda js_code içinden gönderip wait_for ile bekleyen örnekler 0.9.4’te tam olarak buna takılır.
Dizeyi token’ı tırnakların arasına yapıştırarak değil, json.dumps ile oluşturun; çünkü bir JSON dize değişmezi, kaçış karakterleri dahil, aynı zamanda geçerli bir JavaScript dize değişmezidir. Gezinmeyi kendiniz beklemeniz gerekmez: wait_for gezinme boyunca yoklamayı sürdürür ve .signup-complete öğesini sonraki sayfada bulur. Crawl4AI’ın yapmayacağı şey, script başarısız olduğunda hata fırlatmaktır. Bir sözdizimi hatası bir log satırı alır, ancak içinde yazım hatası olan bir öğe ID’si gibi bir çalışma zamanı hatası log satırı bile olmadan yutulur ve yalnızca wait_for’un zaman aşımına uğraması olarak görünür; bu yüzden çözücüyü suçlamadan önce iki ifadeyi gerçek sayfada DevTools konsolunda test edin.
Bazı siteler textarea’yı hiç okumaz. Widget’a bir callback kaydeder ve gönderimi oradan yaparlar; bu durumda iki ifadeyi, data-callback özniteliğinde adı geçen fonksiyona token’ı veren bir çağrıyla değiştirin. Altta yine sıradan reCAPTCHA v2 vardır; bu şurada açıklanıyor: reCAPTCHA v2 çözücü sayfası.
4. Adım: derin tarama içinde bir hook ile çözmek
Oturum akışı, her arun çağrısı sizin kontrolünüzdeyken bir Crawl4AI captcha’sını halleder. Derin tarama ya da bir arun_many toplu işi size bu imkânı vermez; bu yüzden çözümü bunun yerine after_goto hook’una bağlayın. Bu hook, crawler’ın gittiği her URL için Playwright sayfası üzerinde, gezinmeden hemen sonra ve wait_for’dan önce çalışır (js_only çağrıları onu atlar); böylece widget içermeyen sayfalar doğrudan geçer.
from capskip import CapSkipError
async def after_goto(page, context, url, response, **kwargs):
holder = await page.query_selector("div.g-recaptcha[data-sitekey]")
if holder is None:
return page # no widget, nothing to do
sitekey = await holder.get_attribute("data-sitekey")
try:
result = await solver.recaptcha(sitekey=sitekey, url=page.url)
except CapSkipError:
return page # keep the page HTML if the solve fails
async with page.expect_navigation():
await page.evaluate(
"t => { document.getElementById('g-recaptcha-response').value = t;"
" document.querySelector('form').submit(); }", result["code"])
return page
crawler.crawler_strategy.set_hook("after_goto", after_goto)Bu yolla ilgili bilmeniz gereken birkaç şey var. Hook taramanın içinde çalışır, dolayısıyla çözüm süresi o sayfanın süresine eklenir; aynı anda bir widget’a rastlayan birkaç sayfanın her biri kendi çözümünü bekler ve AsyncCapSkip bunları yan yana yürütür. Hook crawler genelinde geçerlidir, bu yüzden kontrolü hafif tutun, çünkü her sayfa onu çalıştırır. try bloğu da önemlidir: hook’tan dışarı sızan bir istisna o URL’nin taramasının tamamını başarısız kılar ve hiç HTML döndürmez; dolayısıyla try bloğu olmasa bir CapSkip kesintisi, derin bir taramadaki tüm korumalı sayfaları boşaltırdı. Bir tuzak daha: Crawl4AI ilk yanıtın durum kodunu saklar. 403 olarak sunulan bir challenge, hook onu çözmüş ve result.html arkasındaki sayfa olsa bile yine success False ve Blocked by anti-bot protection ile döner. Bu URL’lerde success değerine güvenmek yerine html içinde kendi içeriğinizi arayın. Crawl4AI her hook’u ve argümanlarını şurada belgeliyor: kendi hook sayfası. Aynı hook, Playwright ile çalışan her crawler’a uyar; daha geniş tablo ise şurada: Playwright CAPTCHA çözücü sayfası.
Crawler başka bir makinedeyken CapSkip nerede çalışır
Bir tarama büyüdükçe Crawl4AI çoğu zaman bir Linux sunucusunda ya da bir konteynerde çalışmaya başlar; CapSkip ise bir Windows uygulamasıdır, dolayısıyla ikisi sık sık farklı makinelerde bulunur. Server modu tam da bunun içindir. CapSkip’in 127.0.0.1 yerine ağ adresinizi veya genel IP’nizi dinlemesini sağlar; böylece crawler, ona ulaşabildiği her yerden aynı HTTP API üzerinden çağrı yapar. Bu rota internetten geçiyorsa statik bir genel IP kullanın, API anahtarı doğrulamasını açın ve bir Windows Firewall kuralıyla portu crawler’ın adresleriyle sınırlayın. Bu hâlâ sizin sahip olduğunuz bir makinedir ve üzerinde çözüm başına ücretlendirme hâlâ yoktur.
SDK ortam değişkenlerini kendiliğinden okumaz. Tam örnekte olduğu gibi CAPSKIP_HOST, CAPSKIP_PORT ve CAPSKIP_API_KEY değerlerini kendi kodunuzda okuyup istemciye verin.
Hesaba katmanız gereken bir Crawl4AI ayrıntısı: 0.9.0’dan beri Docker sunucusu, ağ üzerinden gelen session_id, js_code ve js_code_before_wait alanlarını HTTP 400 ile reddeder ve artık hook kodu kabul etmez. Bu rehberdeki yolların hiçbiri REST API üzerinden geçemez; bu yüzden akışı kütüphaneyle kendi Python sürecinizde çalıştırın.
Tam çalışan örnek
# pip install crawl4ai capskip
import asyncio
import json
import os
import re
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
from capskip import AsyncCapSkip
PAGE_URL = "https://example.com/signup"
SESSION = "signup"
WIDGET = r'<[^>]*class="(?:[^"]*\s)?g-recaptcha(?:\s[^"]*)?"[^>]*>'
solver = AsyncCapSkip(
apiKey=os.getenv("CAPSKIP_API_KEY", "capskip"),
host=os.getenv("CAPSKIP_HOST", "127.0.0.1"),
port=int(os.getenv("CAPSKIP_PORT", "8080")),
)
async def main():
async with AsyncWebCrawler() as crawler:
first = await crawler.arun(
PAGE_URL, config=CrawlerRunConfig(session_id=SESSION))
widget = re.search(WIDGET, first.html)
match = widget and re.search(r'data-sitekey="([^"]+)"', widget.group(0))
if not match:
raise RuntimeError(f"no sitekey found: {first.error_message}")
result = await solver.recaptcha(sitekey=match.group(1), url=PAGE_URL)
inject = (
"document.getElementById('g-recaptcha-response').value = "
f"{json.dumps(result['code'])};"
"document.querySelector('form').submit();"
)
done = await crawler.arun(PAGE_URL, config=CrawlerRunConfig(
session_id=SESSION,
js_only=True,
js_code_before_wait=inject,
wait_for="css:.signup-complete",
wait_for_timeout=30000,
))
print(done.success) # True once the next page has loaded
asyncio.run(main())wait_for_timeout, gönderim sonrası beklemeye kendi sınırını verir; böylece hiçbir yere varmayan bir gönderim, page_timeout’un 60 saniyesinde değil, 30 saniyede başarısız olur. .signup-complete yerine yalnızca formdan sonraki sayfada bulunan herhangi bir öğeyi kullanın. Selenium ve düz HTTP istemcileri dahil Python dünyasının geri kalanı için şuraya bakın: Python CAPTCHA çözücü sayfası.
Sık görülen hatalar ve anlamları
| Gördüğünüz | Neden | Düzeltme |
|---|---|---|
| Yaklaşık 60 saniye sonra Wait condition failed alınıyor ve form hiç gönderilmiyor | Gönderim, 0.8.5’ten beri wait_for’dan sonra çalışan js_code içinde | Script’i js_code_before_wait içine taşıyın |
| first.success, Blocked by anti-bot protection ile birlikte False dönüyor | Crawl4AI’ın engelleme kontrolü CAPTCHA sayfasını bir engelleme sayfası olarak okudu | Sitekey’i yine de first.html içinden okuyun; sayfa sağlamdır |
| wait_for ikinci çağrıda zaman aşımına uğruyor ve html boş bir sayfa | session_id farklı, bu yüzden Crawl4AI yeni ve boş bir sekme açtı | İki çağrıda da aynı session_id değerini kullanın |
| Token textarea’da ama site CAPTCHA’nın başarısız olduğunu söylüyor | Site gönderimi bir data-callback fonksiyonu üzerinden yapıyor ya da token’ın süresi doldu | Callback’i token ile çağırın ve çözümden sonraki iki dakika içinde gönderin |
| Hiç hata yok, sonra bekleme zaman aşımına uğruyor | Enjeksiyon script’i sayfanın içinde hata fırlattı ve Crawl4AI bunu sessizce yutuyor | Script’i önce DevTools konsolunda çalıştırın ve kullandığı ID’leri kontrol edin |
| Crawl4AI Docker sunucusundan HTTP 400 | Sunucu, ağ üzerinden gelen session_id ve script alanlarını reddediyor | Kütüphaneyi kendi Python sürecinizde çalıştırın |
| Çözümden gelen NetworkException | CapSkip çalışmıyor ya da host ve port yanlış makineyi gösteriyor | CapSkip’i başlatın; crawler başka bir yerdeyse Server modunu kullanın |
| Çözümden gelen TimeoutException | Çözüm recaptchaTimeout süresini aştı | Constructor’da varsayılan 300 saniyenin üzerine çıkarın |
FAQ
Crawl4AI CAPTCHA’ları kendi başına çözer mi?
Hayır. Onları tespit eder, yani anti-bot kontrolü bir CAPTCHA sayfasını engellenmiş bir tarama olarak işaretler; bir sayfa engellendiğinde bir proxy listesi üzerinden yeniden de deneyebilir. magic ve simulate_user seçenekleri, anti-bot sistemlerinin aradığı fare hareketini ve kaydırmayı ekler; bu da bir challenge çıkma olasılığını düşürebilir. Bunların hiçbiri, sayfaya gelmiş bir widget’ı çözmez; harici bir çözücünün doldurduğu boşluk budur ve bu, şurada anlatılan boşlukla aynıdır: web scraping için CAPTCHA çözücü sayfası.
Aynı akış Cloudflare Turnstile için de işe yarar mı?
Widget için evet. solver.turnstile metodunu sitekey ve sayfa URL’siyle çağırın ve token’ı reCAPTCHA textarea’sı yerine cf-turnstile-response adlı gizli input alanına yazın. Tam sayfa challenge’lar ise ayrı bir iştir, çünkü token’ları CapSkip’in bildirdiği user agent ile birlikte gitmelidir; dolayısıyla onu gönderen tarayıcının da aynı user agent’ı sunması gerekir. Bu durum için şunu inceleyin: Cloudflare Turnstile çözücü sayfasını.
Crawler’ım bir konteynerde çalışıyor. CapSkip nereye kurulur?
Kontrolünüzdeki bir Windows makinesine, Server modu açık olarak. Konteyner ona, tıpkı diğer iç servisler gibi API üzerinden ulaşır; böylece crawler ile çözücünün aynı işletim sistemini paylaşması gerekmez. Windows makinesinin adresini CAPSKIP_HOST olarak verin, API anahtarı doğrulamasını etkinleştirin ve crawler’a kendi anahtarını verin; böylece o anahtar tek başına iptal edilebilir.
Oturum iki çağrı arasında ne kadar süre açık kalır?
Bir çözümün sürdüğünden çok daha uzun. 0.9.4 kaynak kodundaki tarayıcı yöneticisi 30 dakika boyunca kullanılmayan oturumları temizler ve crawler’ı kapatmak hepsini kapatır. Pratikte önemli olan sınır token’ınkidir, yani yaklaşık iki dakika; bu yüzden çözüm ile gönderim doğrudan birbirini izlemelidir.
Kısa özet
İşte tüm Crawl4AI captcha akışı tek paragrafta. Sayfayı bir session_id ile yükleyin ve Crawl4AI taramayı engellenmiş olarak nitelese bile sitekey’i HTML’den okuyun. Onu AsyncCapSkip ile çözün. js_only=True ile aynı sekmeye geri dönün, token’ı yerleştirip js_code_before_wait içinden gönderin ve sonraki sayfayı wait_for ve onun kendi zaman aşımıyla bekleyin. Derin taramalarda aynısını after_goto hook’undan yapın. Crawler başka bir makinede çalışıyorsa istemciyi bir Server modu adresine yönlendirin.
Bir taramayı nasıl boyutlandıracağınızı değiştiren son bir nokta. Kullandığınız captcha atlatma aracı zaten sahip olduğunuz bir makinede çalıştığı için, her sayfada bir widget’a rastlayan bir tarama, yalnızca bir kez rastlayanla aynı maliyettedir; dolayısıyla bir URL’yi sırf bir challenge’ın arkasında diye atlamanız için hiçbir neden yok.
