Crawl4AI में कैप्चा कैसे हल करें, session खोए बिना

Crawl4AI का अपना कोई कैप्चा सॉल्वर नहीं है, इसलिए Crawl4AI में कैप्चा वाला flow तीन calls का होता है जिन्हें आप आपस में जोड़ते हैं: पेज को एक नाम वाले session में लोड करें, sitekey को किसी बाहरी सॉल्वर से हल करें, फिर उसी टैब में लौटकर token डालें और सबमिट करें। लोग जिस बात पर अटकते हैं, वह है Crawl4AI के चीज़ें चलाने का क्रम। वर्ज़न 0.8.5 से js_code, wait_for के बाद चलता है, इसलिए जो क्रॉल फॉर्म को js_code में सबमिट करता है और अगले पेज का इंतज़ार wait_for से करता है, वह timeout होने तक वहीं अटका रहता है। सबमिट की सही जगह js_code_before_wait है। यह गाइड reCAPTCHA v2 के साथ session flow समझाती है, फिर deep crawls के लिए एक hook, जहाँ हर call आपके नियंत्रण में नहीं होती।
आपको क्या चाहिए
- Python 3.10 या उससे नया, और Crawl4AI 0.9 या उसके बाद का वर्ज़न। यहाँ की हर बात 0.9.4 के source से पढ़ी गई और उसी पर चलाकर देखी गई है; स्टेप 3 वाला क्रम 0.8.5 से चला आ रहा है।
- CapSkip Python पैकेज, जो आपको AsyncCapSkip देता है: एक सचमुच asynchronous क्लाइंट, जो उसी event loop में फ़िट बैठता है जिस पर Crawl4AI पहले से चलता है।
- कैप्चा वाले पेज का URL, और किसी ऐसी चीज़ का selector जो फॉर्म सबमिट हो जाने के बाद ही दिखती है।
- किसी Windows मशीन पर चलता हुआ CapSkip। जब crawler उसी मशीन पर चलता है तो Local मोड 127.0.0.1 पर जवाब देता है; जब ऐसा नहीं होता, तो Server मोड आपके नेटवर्क या पब्लिक IP पर सुनता है। दोनों की जानकारी यहाँ है: कनेक्शन सेटिंग्स.
# pip install crawl4ai pip install -U crawl4ai capskip # Downloads the browser Crawl4AI drives, once per machine. crawl4ai-setup
स्टेप 1: पेज को session में लोड करें और sitekey पढ़ें
पहली call को एक session_id दें। इससे call लौटने के बाद भी टैब खुला रहता है, उसकी cookies और widget जस के तस रहते हैं, ताकि जो token आप बाद में हल करें वह उसी पेज में पहुँचे जिसने उसे माँगा था। session_id के बिना, HTML मिलते ही Crawl4AI पेज बंद कर देता है।
# 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 Noneवह comment बेवजह नहीं है। Crawl4AI 0.9 हर result पर एक anti-bot जाँच चलाता है, और जिस पेज को वह block पेज समझता है उसे failed मार्क कर देता है: success, False लौटता है और error_message, Blocked by anti-bot protection से शुरू होता है। कोई भी 403 या 503 HTML response इस दायरे में आता है, और 429 भी। दूसरे error statuses पर, 10 KB से छोटा पेज तब इस दायरे में आता है जब उसके widget का class attribute ठीक g-recaptcha हो। रेंडर हुआ HTML फिर भी first.html में होता है, और उसमें वह sitekey भी होती है जो आपको चाहिए, इसलिए क्रॉल को फेल मानने से पहले उसे पढ़ लें।
html फ़ील्ड रेंडर हुआ पेज है, इसलिए script से बना widget भी उसमें होता है। अगर key element पर नहीं है, तो widget iframe के src में k= पैरामीटर खोजें, और अगर widget देर से रेंडर होता है तो पहली call में उस iframe पर एक wait_for जोड़ दें।
स्टेप 2: AsyncCapSkip से हल करें
Crawl4AI ऊपर से नीचे तक asyncio है, इसलिए asynchronous क्लाइंट इस्तेमाल करें। यह blocking calls के ऊपर लपेटा गया wrapper नहीं, बल्कि असली coroutine है, यानी बीस सेकंड लेने वाला solve उसी event loop पर चल रहे बाक़ी सभी क्रॉल को रोक नहीं देता।
# 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 valueजब तक यह चलता है, Crawl4AI में कुछ भी इंतज़ार नहीं कर रहा होता, क्योंकि solve दो calls के बीच होता है, किसी एक के अंदर नहीं। टैब बस वहीं पड़ा रहता है। घड़ी असल में token की चलती है: reCAPTCHA token जारी होने के बाद करीब दो मिनट तक चलता है, इसलिए सीधे सबमिट पर जाएँ। पूरा ब्योरा यहाँ है: reCAPTCHA token कितनी देर तक वैध रहता है.
स्टेप 3: उसी टैब में inject और सबमिट करें
दूसरी call उसी session को दोबारा इस्तेमाल करती है और js_only सेट करती है, जो Crawl4AI से कहता है कि URL को फिर से लोड करने के बजाय पहले से मौजूद पेज में JavaScript चलाए। reload का मतलब है दूसरी बार पेज लोड करना, जिस पर फिर से challenge आ सकता है, और यह वह सब कुछ रीसेट कर देता है जो पहले लोड ने तैयार किया था, जैसे आधा भरा हुआ फॉर्म।
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 js_code_before_wait में क्यों जाती है, इसकी वजह यह है। 0.8.5 से, और हर 0.9 रिलीज़ में, क्रॉल पहले js_code_before_wait चलाता है, फिर wait_for, और सबसे आख़िर में js_code, तैयार पेज पर। इसलिए अगर आप सबमिट को js_code में रखते हैं, तो कुछ भी सबमिट होने से पहले ही wait_for अगला पेज खोजना शुरू कर देता है, और तब तक खोजता रहता है जब तक page_timeout ख़त्म न हो जाए, जो डिफ़ॉल्ट रूप से 60 सेकंड है। तब call, Wait condition failed के साथ फेल हो जाती है और फॉर्म कभी भेजा ही नहीं जाता। जो उदाहरण एक ही call में js_code से सबमिट करते हैं और wait_for से इंतज़ार करते हैं, जिनमें Crawl4AI के अपने दस्तावेज़ों का एक उदाहरण भी शामिल है, वे 0.9.4 पर ठीक इसी समस्या में फँसते हैं।
token को quotes के बीच चिपकाने के बजाय स्ट्रिंग json.dumps से बनाएँ, क्योंकि कोई JSON string literal एक वैध JavaScript string literal भी होता है, escaping समेत। navigation का इंतज़ार आपको खुद नहीं करना पड़ता: wait_for उसके पार भी polling करता रहता है और अगले पेज पर .signup-complete ढूँढ लेता है। Crawl4AI जो नहीं करेगा, वह है script फेल होने पर error उठाना। syntax error पर एक log line मिलती है, लेकिन run-time error, जैसे किसी element ID में typo, बिना किसी log line के निगल लिया जाता है और सिर्फ़ wait_for के timeout के रूप में सामने आता है, इसलिए सॉल्वर को दोष देने से पहले दोनों statements को असली पेज पर DevTools console में टेस्ट करें।
कुछ साइटें textarea पढ़ती ही नहीं। वे widget के साथ एक callback register करती हैं और वहीं से सबमिट करती हैं, इसलिए दोनों statements की जगह data-callback attribute में लिखे function को कॉल करें और उसे token पास करें। भीतर से यह अब भी सामान्य reCAPTCHA v2 ही है, जैसा यहाँ समझाया गया है: reCAPTCHA v2 सॉल्वर पेज.
स्टेप 4: deep crawl के अंदर hook से हल करना
जब हर arun call आपके हाथ में हो, तब session flow Crawl4AI के कैप्चा को संभाल लेता है। deep crawl या arun_many batch में यह सुविधा नहीं मिलती, इसलिए solve को इसके बजाय after_goto hook से जोड़ें। यह hook Playwright पेज पर navigation के ठीक बाद और wait_for से पहले चलता है, हर उस URL के लिए जिस पर crawler जाता है (js_only calls इसे छोड़ देती हैं), इसलिए बिना widget वाले पेज सीधे निकल जाते हैं।
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)इस रास्ते के बारे में कुछ बातें जान लें। hook क्रॉल के अंदर चलता है, इसलिए solve का समय उसी पेज में जुड़ता है, और एक साथ widget से टकराने वाले कई पेज अपने-अपने solve का इंतज़ार करते हैं, जिन्हें AsyncCapSkip साथ-साथ संभालता है। hook पूरे crawler पर global है, इसलिए जाँच को हल्का रखें, क्योंकि हर पेज उसे चलाता है। और try block मायने रखता है: hook से बाहर निकलने वाला exception उस URL का पूरा क्रॉल फेल कर देता है और कोई HTML नहीं लौटाता। try block न हो, तो CapSkip के बंद होने पर deep crawl का हर सुरक्षित पेज खाली लौटेगा। एक और पेच: Crawl4AI पहले response का status code ही रखता है। 403 के रूप में सर्व किया गया challenge फिर भी success False और Blocked by anti-bot protection के साथ लौटता है, भले ही hook ने उसे हल कर दिया हो और result.html उसके पीछे वाला पेज हो। ऐसे URLs पर success पर भरोसा करने के बजाय html में अपना content जाँचें। Crawl4AI हर hook और उसके arguments को यहाँ दर्ज करता है: उसका hooks पेज। यही hook Playwright से चलने वाले किसी भी crawler के लिए ठीक है, और व्यापक तस्वीर यहाँ है: Playwright कैप्चा सॉल्वर पेज.
जब crawler कहीं और चले, तब CapSkip कहाँ चलता है
क्रॉल बड़ा होने पर Crawl4AI अक्सर किसी Linux सर्वर या कंटेनर में पहुँच जाता है, और CapSkip एक Windows ऐप्लिकेशन है, इसलिए दोनों अक्सर अलग-अलग मशीनों पर रहते हैं। Server मोड इसी के लिए है। यह CapSkip को 127.0.0.1 के बजाय आपके नेटवर्क पते या पब्लिक IP पर सुनने देता है, ताकि crawler जहाँ से भी उस तक रास्ता बना सके, वहीं से उसी HTTP API के ज़रिए उसे कॉल करे। अगर वह रास्ता इंटरनेट से होकर जाता है तो स्टैटिक पब्लिक IP इस्तेमाल करें, API key validation चालू करें, और Windows Firewall नियम से पोर्ट को सिर्फ़ crawler के पतों तक सीमित रखें। यह अब भी आपकी अपनी मशीन है, और उस पर हल करना अब भी बिना मीटर वाला है।
SDK एनवायरनमेंट वेरिएबल खुद नहीं पढ़ता। CAPSKIP_HOST, CAPSKIP_PORT और CAPSKIP_API_KEY को अपने कोड में पढ़ें और उन्हें पास करें, जैसा पूरा उदाहरण करता है।
Crawl4AI की एक बात, जिसे ध्यान में रखकर योजना बनानी होगी: 0.9.0 से उसका Docker सर्वर session_id, js_code और js_code_before_wait को HTTP 400 के साथ मना कर देता है जब वे नेटवर्क पर आते हैं, और वह अब hook code भी स्वीकार नहीं करता। इस गाइड का कोई भी रास्ता REST API से होकर नहीं जा सकता, इसलिए इसे अपनी खुद की Python प्रोसेस में library के साथ चलाएँ।
पूरा चलने वाला उदाहरण
# 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 सबमिट के बाद वाले इंतज़ार को उसकी अपनी सीमा देता है, ताकि कहीं न पहुँचने वाला सबमिट page_timeout के 60 सेकंड के बजाय 30 सेकंड में फेल हो जाए। .signup-complete की जगह कोई भी ऐसी चीज़ रखें जो सिर्फ़ फॉर्म के बाद वाले पेज पर मौजूद हो। बाक़ी Python दुनिया के लिए, जिसमें Selenium और सादे HTTP क्लाइंट शामिल हैं, देखें: Python कैप्चा सॉल्वर पेज.
आम errors और उनका मतलब
| आप जो देखते हैं | कारण | फिक्स |
|---|---|---|
| करीब 60 सेकंड बाद Wait condition failed, और फॉर्म कभी सबमिट नहीं हुआ | सबमिट js_code में है, जो 0.8.5 से wait_for के बाद चलता है | script को js_code_before_wait में ले जाएँ |
| first.success False है, साथ में Blocked by anti-bot protection | Crawl4AI की block जाँच ने कैप्चा पेज को block पेज समझ लिया | फिर भी sitekey first.html से पढ़ें; पेज सही-सलामत है |
| दूसरी call पर wait_for का timeout हो जाता है और html एक खाली पेज है | session_id अलग है, इसलिए Crawl4AI ने एक नया, खाली टैब खोल दिया | दोनों calls पर एक ही session_id इस्तेमाल करें |
| token textarea में है, पर साइट कहती है कि कैप्चा फेल हो गया | साइट data-callback function के ज़रिए सबमिट करती है, या token expire हो गया | callback को token के साथ कॉल करें, और हल करने के दो मिनट के भीतर सबमिट करें |
| कोई error नहीं, फिर wait का timeout हो जाता है | injection script ने पेज के अंदर error फेंका, और Crawl4AI उसे चुपचाप निगल जाता है | पहले script को DevTools console में चलाएँ और उसमें इस्तेमाल हुए IDs जाँचें |
| Crawl4AI Docker सर्वर से HTTP 400 | सर्वर नेटवर्क पर आने वाले session_id और script फ़ील्ड को मना कर देता है | library को अपनी खुद की Python प्रोसेस में चलाएँ |
| solve से NetworkException | CapSkip चल नहीं रहा, या host और port गलत मशीन की ओर इशारा करते हैं | CapSkip शुरू करें, और जब crawler कहीं और हो तो Server मोड इस्तेमाल करें |
| solve से TimeoutException | solve recaptchaTimeout से ज़्यादा लंबा चला | constructor में उसे 300 सेकंड के डिफ़ॉल्ट से ऊपर बढ़ाएँ |
FAQ
क्या Crawl4AI खुद कैप्चा हल करता है?
नहीं। वह उन्हें पहचानता है, इस अर्थ में कि उसकी anti-bot जाँच कैप्चा पेज को blocked क्रॉल मार्क कर देती है, और पेज block होने पर वह प्रॉक्सी की एक सूची के ज़रिए दोबारा कोशिश कर सकता है। magic और simulate_user ऑप्शन वह mouse movement और scrolling जोड़ते हैं जिसे anti-bot सिस्टम खोजते हैं, जिससे challenge आने की संभावना कम हो सकती है। लेकिन पेज पर आ चुके widget को इनमें से कुछ भी हल नहीं करता। यही कमी कोई बाहरी सॉल्वर पूरी करता है, और यही कमी यहाँ भी बताई गई है: वेब स्क्रैपिंग के लिए कैप्चा सॉल्वर पेज.
क्या यही flow Cloudflare Turnstile के लिए भी काम करता है?
widget के लिए, हाँ। solver.turnstile को sitekey और पेज URL के साथ कॉल करें, और token को reCAPTCHA textarea के बजाय cf-turnstile-response नाम वाले hidden input में लिखें। full-page challenges अलग काम हैं, क्योंकि उनके token को उसी user agent के साथ जाना होता है जो CapSkip लौटाता है, इसलिए उसे सबमिट करने वाले ब्राउज़र को भी वही user agent दिखाना होगा। वह मामला यहाँ कवर किया गया है: Cloudflare Turnstile सॉल्वर पेज.
मेरा crawler एक कंटेनर में चलता है। CapSkip कहाँ रखूँ?
किसी ऐसी Windows मशीन पर जो आपके नियंत्रण में हो, और Server मोड चालू करके। तब कंटेनर उस तक किसी भी दूसरी internal सर्विस की तरह API के ज़रिए पहुँचता है, इसलिए crawler और सॉल्वर का एक ही operating system साझा करना ज़रूरी नहीं। Windows मशीन का पता CAPSKIP_HOST के रूप में पास करें, API key validation चालू करें, और crawler को उसकी अपनी key दें, ताकि उसे अलग से रद्द किया जा सके।
दोनों calls के बीच session कितनी देर खुला रहता है?
किसी solve में लगने वाले समय से कहीं ज़्यादा। 0.9.4 के source में browser manager उन sessions को साफ़ कर देता है जो 30 मिनट से इस्तेमाल नहीं हुए, और crawler बंद करने पर वे सब बंद हो जाते हैं। व्यवहार में जो सीमा मायने रखती है, वह token की है, करीब दो मिनट, इसलिए solve और सबमिट एक के ठीक बाद एक होने चाहिए।
संक्षेप में
Crawl4AI में कैप्चा का पूरा flow एक पैराग्राफ़ में। पेज को session_id के साथ लोड करें और HTML से sitekey पढ़ें, तब भी जब Crawl4AI क्रॉल को blocked कहे। उसे AsyncCapSkip से हल करें। js_only=True के साथ उसी टैब में लौटें, token डालें और js_code_before_wait से सबमिट करें, और अगले पेज का इंतज़ार wait_for और उसके अपने timeout के साथ करें। deep crawls के लिए यही काम after_goto hook से करें। जब crawler किसी दूसरी मशीन पर चले, तो क्लाइंट को Server मोड वाले पते पर point करें।
एक आख़िरी बात, जो बदल देती है कि आप क्रॉल का आकार कैसे तय करते हैं। चूँकि कैप्चा बायपास उस मशीन पर चलता है जो पहले से आपकी है, इसलिए हर पेज पर widget से टकराने वाले क्रॉल की लागत उतनी ही है जितनी सिर्फ़ एक बार टकराने वाले क्रॉल की। यानी किसी URL को सिर्फ़ इस वजह से छोड़ने का कोई कारण नहीं कि वह किसी challenge के पीछे है।
