DrissionPage में कैप्चा कैसे हल करें और token कैसे inject करें

DrissionPage में कैप्चा वाला step तीन चालों का है: पेज से sitekey पढ़ें, उसे सॉल्वर को भेजें, फिर token को JavaScript से वापस form में लिख दें। DrissionPage किसी webdriver की जगह DevTools Protocol के ज़रिए एक असली Chrome चलाता है, इसलिए ब्राउज़र वाला हिस्सा Selenium से आसान है। element model आसान नहीं है, और उसमें एक default ऐसा है जो ग़ायब widget को आपके locator की error के बजाय सॉल्वर की error बना देता है। यही default वजह है कि ऐसी ज़्यादातर scripts उलझाने वाले तरीके से फेल होती हैं, इसलिए उसे अपना अलग सेक्शन मिला है।
आपको क्या चाहिए
- Python 3.10 या उससे नया, साथ में DrissionPage 4.1 और CapSkip SDK इंस्टॉल किए हुए।
- एक Chromium ब्राउज़र जिसे DrissionPage launch कर सके या जिससे attach हो सके।
- सुरक्षित form का पेज URL। वह sitekey जो आप runtime पर पढ़ते हैं।
- CapSkip, Local mode में अगर script और सॉल्वर एक ही मशीन साझा करते हैं, या Server mode में अगर script किसी दूसरी मशीन पर चलती है। दोनों की जानकारी यहाँ है: कनेक्शन सेटिंग्स.
# Both packages, one line. pip install DrissionPage capskip
DrissionPage इस काम की शक्ल क्यों बदल देता है
DrissionPage सीधे CDP के ज़रिए Chrome से बात करता है। बीच में कोई chromedriver process नहीं होती, इसलिए न कोई driver binary patch करनी है और न कोई अलग service आपके ब्राउज़र वर्ज़न के साथ मिलाकर रखनी है। यह पहले से चल रहे ब्राउज़र से attach भी हो जाता है, जो यहाँ सचमुच काम आता है: आप एक बार हाथ से लॉग इन कर लें, profile खुला छोड़ दें, और script को वही session उठाने दें।
जो नहीं बदलता वह है token। हल हो चुका reCAPTCHA एक string है जिसे submit से पहले छिपे हुए g-recaptcha-response textarea तक पहुँचना ही होता है। वह textarea display:noneपर सेट है, इसलिए किसी भी ऑटोमेशन टूल में उसमें टाइप करना विकल्प ही नहीं है। आप उसे JavaScript से लिखते हैं, और DrissionPage का run_js method यही काम करता है।
एक बात साफ़ कह देना ठीक रहेगा: webdriver न होने का मतलब detection न होना नहीं है। CDP पर Chrome चलाने से एक signal हट जाता है, बाकी पूरा fingerprint जहाँ था वहीं रहता है। challenge हल करना और ब्राउज़र जैसा दिखना, ये दो अलग काम हैं, और यह पोस्ट सिर्फ़ पहले वाले को कवर करती है।
Step 1: page से sitekey पढ़ें
sitekey host document पर होता है, widget iframe के अंदर नहीं। Google का अपना markup उसे एक container element पर data-sitekeyके रूप में रखता है, और DrissionPage का attribute locator उसे बिना किसी CSS selector के ढूँढ लेता है।
# 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) # 6LxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxDrissionPage के locator prefixes यहाँ सीखने लायक हैं, क्योंकि वे उस ज़्यादातर XPath की जगह ले लेते हैं जो आपको वरना लिखनी पड़ती: @attr=value पूरा मिलान करता है, @attr:value substring से मिलान करता है, @attr^value शुरुआत से मिलान करता है, और @attr$value अंत से। locator के आगे tag नाम लगाना, जैसे tag:iframe, element का प्रकार भी तय कर देता है।
कुछ साइटें sitekey को host पेज पर रखती ही नहीं और उसे सिर्फ़ iframe URL में भेजती हैं। ऐसे में उसे वहीं से पढ़ें, और जो frame चाहिए उसे मिलाने के लिए contains का इस्तेमाल करें।
# 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]Step 2: उसे local API पर हल करें
सॉल्वर आपकी अपनी मशीन पर port 8080 पर चलता है और 2captcha API बोलता है, इसलिए SDK call एक ही लाइन की है और उसके पीछे कोई प्रति-हल शुल्क नहीं है। पेज URL page.url से पास करें, उसे दोबारा टाइप करके नहीं, क्योंकि get() और solve के बीच हुआ कोई redirect वरना आपसे ग़लत URL submit करवा देगा।
# 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 और GeeTest के अपने methods हैं, solver.turnstile() और solver.geetest(), और दोनों का आकार ऊपर वाली call जैसा ही है। हर एक की पूरी parameter सूची यहाँ है: CapSkip API डॉक्यूमेंटेशन.
Step 3: token inject करें और submit करें
DrissionPage अतिरिक्त arguments को run_js में positionally भेजता है, और script उन्हें arguments[0] वग़ैरह के रूप में पढ़ती है। token को f-string से JavaScript बनाकर भेजने के बजाय एक argument के तौर पर पास करें: token लंबा होता है, अपारदर्शी होता है और कभी-कभी ऐसे characters से भरा होता है जिनकी quoting के बारे में आप सोचना भी नहीं चाहेंगे।
# 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()अगर साइट submit पर textarea पढ़ने के बजाय कोई callback परिभाषित करती है, तो value सेट करने के बाद उसे कॉल करें। वह function नाम हर साइट का अलग होता है, इसलिए अंदाज़ा लगाने के बजाय उसे पेज के अपने markup से पढ़ें, और ध्यान रखें कि यह अब भी सामान्य reCAPTCHA v2 ही है: callback सिर्फ़ यह बदलता है कि आप token कैसे सौंपते हैं, यह नहीं कि वह हल कैसे होता है। reCAPTCHA v2 सॉल्वर पेज दोनों submission शैलियों को कवर करता है।
वह चुपचाप होने वाली नाकामी जो सबसे ज़्यादा समय खाती है
DrissionPage में Settings.raise_when_ele_not_found डिफ़ॉल्ट रूप से False पर आता है। जो locator किसी से मेल नहीं खाता, वह error नहीं उठाता। वह एक NoneElement लौटाता है, जो falsy है और Python के None के बराबर तुलना करता है, और आपकी script चलती रहती है।
यह एक सोचा समझा design फ़ैसला है और वैकल्पिक elements टटोलते समय अच्छा भी लगता है। यहाँ यह अच्छा नहीं लगता, क्योंकि अगला काम आप attribute पढ़ने का करते हैं, और जो error मिलती है वह widget का नहीं, locator का नाम लेती है। इससे भी बुरा वह मामला है जहाँ element सचमुच मौजूद है पर उस पर data-sitekey attribute है ही नहीं। तब attr() None लौटा देता है, वही None एक ख़ाली key बनकर सीधे सॉल्वर तक पहुँच जाता है, और नाकामी आख़िरकार ERROR_GOOGLEKEY बनकर एक ऐसी API call से सामने आती है जिस पर आपको शक ही नहीं था।
दो लाइनें इसे ठीक कर देती हैं। raising चालू करें, और उस पर एक solve ख़र्च करने से पहले sitekey जाँच लें।
# 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.")पूरा चलने वाला उदाहरण
ऊपर की सारी बातें, एक ही script में। finally block यहाँ आम से ज़्यादा मायने रखता है, क्योंकि DrissionPage ऐसे ब्राउज़र से attach हो सकता है जिसे आपने शुरू ही नहीं किया था, और विरासत में मिले ब्राउज़र को बंद कर देना शायद ही कभी आपकी मंशा होती है।
# 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()सॉल्वर को किसी दूसरी मशीन पर चलाना
स्क्रैपर देर सबेर किसी सर्वर पर चले ही जाते हैं, और DrissionPage उनके साथ चला जाता है: उसे एक Chromium binary और एक display buffer चाहिए, इसके अलावा ख़ास कुछ नहीं। सॉल्वर को उसी मशीन पर उसके पीछे जाना ज़रूरी नहीं है।
CapSkip में दो connection modes हैं। लोकल 127.0.0.1 से bind होता है और सिर्फ़ उसी डिवाइस से पहुँच में रहता है, जो script लिखते समय सही सेटिंग है। सर्वर आपके नेटवर्क या public IP से bind होता है, जिससे कोई scraping VM, कोई container host या कोई दूसरा workstation उसी सॉल्वर को API के ज़रिए कॉल कर सकता है। एक static public IP उस पते को स्थिर रखता है। कोड में और कुछ नहीं बदलता, सिर्फ़ वह host पता बदलता है जो आप पास करते हैं, और कीमत भी नहीं बदलती, क्योंकि हार्डवेयर तब भी आपका ही है।
# Same SDK, same call. Only the host moves. solver = CapSkip(host="10.0.0.12", port=8080, apiKey="YOUR_API_KEY")
जैसे ही सॉल्वर किसी नेटवर्क पते पर सुनना शुरू करे, key validation चालू कर दें, और हर मशीन को उसकी अपनी key दें ताकि एक को बाकियों को छुए बिना रद्द किया जा सके। सेटअप गाइड दोनों modes को विस्तार से समझाती है।
आम errors और उनका मतलब
| आप जो देखते हैं | कारण | फिक्स |
|---|---|---|
| attr() पर ElementNotFoundError | locator किसी से मेल नहीं खाया और NoneElement लौटा दिया | locator को चौड़ा करें, या widget के render होने का इंतज़ार करें |
| script ग़ायब element के आगे चलती रहती है | raise_when_ele_not_found का डिफ़ॉल्ट False है | Settings.set_raise_when_ele_not_found(True) |
| ERROR_GOOGLEKEY | attr() ने None लौटाया और एक ख़ाली key submit हो गई | value जाँचें, या iframe से k parameter पढ़ें |
| NetworkException | CapSkip चल नहीं रहा या host ग़लत है | ऐप शुरू करें, या host को सर्वर पते पर point करें |
| TimeoutException | solve recaptchaTimeout से ज़्यादा लंबा चला | उसे डिफ़ॉल्ट 300 सेकंड से ऊपर बढ़ाएँ |
| Form ऐसे token को अस्वीकार करता है जो ठीक हल हुआ था | submit से पहले ही token पुराना पड़ गया | submit करने से ठीक पहले हल करें, पेज लोड होने पर नहीं |
FAQ
मुझे ChromiumPage इस्तेमाल करना चाहिए या नया Chromium class?
कोई भी। वर्ज़न 4.1 top level package से Chromium, ChromiumPage, SessionPage और WebPage export करता है, और एक tab तक पहुँचने का सबसे छोटा रास्ता अब भी ChromiumPage है। आप जो भी चुनें, कैप्चा वाला काम एक जैसा ही रहता है, क्योंकि sitekey पढ़ना, solve करना और token inject करना, तीनों tab object के ज़रिए ही होते हैं।
क्या मुझे reCAPTCHA iframe के अंदर switch करना पड़ेगा?
नहीं, और लोग इसी हिस्से को ज़रूरत से ज़्यादा जटिल बना देते हैं। checkbox एक iframe में रहता है, लेकिन sitekey attribute और छिपा हुआ response textarea, दोनों host document के हैं। frame को आप सिर्फ़ तब छूते हैं जब साइट sitekey को पेज पर देती ही नहीं और आपको उसे frame के अपने URL से पढ़ना पड़ता है।
मेरी script बिना कुछ कहे None sitekey क्यों पास कर देती है?
क्योंकि DrissionPage का डिफ़ॉल्ट यह है कि वह error उठाने के बजाय एक falsy placeholder लौटा दे, और क्योंकि जो element मौजूद है उसमें भी आपका माँगा हुआ attribute न होना मुमकिन है। दोनों रास्तों का अंत एक ऐसे variable में None से होता है जिसे आपने string मान रखा था। फ़ाइल के सबसे ऊपर raising चालू करें और value पर एक साफ़ जाँच जोड़ें, ये दोनों मिलकर दोनों मामलों को ढक लेते हैं।
मेरा स्क्रैपर एक VPS पर चलता है। सॉल्वर कहाँ रखूँ?
जहाँ आपको ठीक लगे, बस दोनों एक दूसरे तक पहुँच पाने चाहिए। Server mode सॉल्वर को loopback पते के बजाय किसी नेटवर्क पते पर सुनने लगाता है, इसलिए VPS उसे API के ज़रिए ठीक वैसे ही कॉल करता है जैसे किसी भी internal service को करता। host argument को उसी पते पर लगाएँ, key validation चालू करें, और VPS को उसकी अपनी key दें।
संक्षेप में
sitekey को attribute locator से पढ़ें, उसे 127.0.0.1:8080पर हल करें, token को run_js और positional arguments से inject करें, फिर submit करें। यह सब करने से पहले raise_when_ele_not_found चालू कर दें, क्योंकि डिफ़ॉल्ट ठीक वही नाकामी छिपाता है जो आपको सबसे पहले मिलेगी। Selenium और Playwright समेत Python की व्यापक तस्वीर के लिए देखें Python कैप्चा सॉल्वर पेज। और चूँकि सॉल्वर एक लोकल captcha सॉल्वर है, न कि कोई metered सेवा, इसलिए एक हज़ार पेज दोबारा आज़माने वाली crawl की लागत उतनी ही है जितनी दस पेज दोबारा आज़माने वाली की।
