httpx और aiohttp में कैप्चा कैसे हल करें (बिना ब्राउज़र)

httpx captcha - How to Solve CAPTCHAs in httpx and aiohttp (No Browser)

httpx का captcha flow तीन चरणों और करीब बीस लाइनों का होता है, और लोग जहाँ गलती करते हैं उसका सॉल्वर से कोई लेना-देना नहीं। इस stack में ब्राउज़र होता ही नहीं। यहाँ कोई भी साइट की JavaScript नहीं चलाता, इसलिए वह hidden field बनता ही नहीं जिसमें token को जाना था, और form भी आपके लिए कोई submit नहीं करता। तीनों आप ख़ुद करते हैं: HTML में से sitekey पढ़ें, उसे हल करें, फिर token को एक सामान्य form field की तरह उसी client से वापस भेजें जिसने पेज fetch किया था।

आपको क्या चाहिए

  • Python 3.10 या उससे नया, और httpx या aiohttp में से कोई एक। CapSkip का Python SDK दोनों के साथ काम करता है।
  • सुरक्षित पेज का URL। sitekey पहले से जानने की ज़रूरत नहीं, क्योंकि पहला चरण उसे ख़ुद पढ़ लेता है।
  • CapSkip, Local mode में अगर script और सॉल्वर एक ही मशीन साझा करते हैं, या Server mode में अगर नहीं करते। दोनों की जानकारी यहाँ है: कनेक्शन सेटिंग्स.
# pip install capskip
pip install capskip httpx

# Using aiohttp instead? Install it as well. See the note below
# about which HTTP libraries you end up with either way.
pip install capskip aiohttp

SDK के साथ httpx पहले से आता है

dependencies गिनने से पहले यह जान लेना ठीक रहेगा। CapSkip का Python package requests, httpx और aiofiles को पक्की runtime dependencies घोषित करता है, इसलिए SDK install करते ही httpx भी install हो जाता है, चाहे आपने माँगा हो या नहीं। अगर httpx पहले से ही आपका scraping client है, तो सॉल्वर आपके environment में कोई नई HTTP library नहीं जोड़ता और AsyncCapSkip आपके ही event loop को साझा करता है। अगर आप aiohttp पर हैं, तो httpx उसके साथ-साथ आ जाता है और अब एक ही virtualenv में दो clients हो जाते हैं। इससे कुछ टूटता नहीं, पर यह ऐसी बात है जिसके बारे में कोई dependency review ज़रूर पूछेगा।

जब कोई ब्राउज़र नहीं होता तो क्या बदलता है

Selenium या Playwright में आप उस hidden textarea की value सेट करते हैं जिसे reCAPTCHA render करता है, और पेज का अपना submit handler उसे आगे ले जाता है। यहाँ इनमें से कुछ भी मौजूद नहीं। एक HTTP client सिर्फ़ bytes लाता है और script tags को कोई evaluate नहीं करता, इसलिए widget कभी render ही नहीं होता, textarea कभी बनता ही नहीं, और चलाने के लिए कोई submit handler होता ही नहीं।

इसकी जगह जो आपके पास है वह ज़्यादा सरल है और समझने में आसान। token सिर्फ़ एक string है, और साइट उसे किसी field नाम के तहत POST body में चाहती है। reCAPTCHA v2 के लिए उस field का नाम g-recaptcha-response है, और यह नाम वही रहता है चाहे उसे ब्राउज़र ने भरा हो या आपने। Turnstile cf-turnstile-response इस्तेमाल करता है। GeeTest एक के बजाय तीन fields भेजता है। सत्यापन साइट के अपने server पर Google या Cloudflare के ख़िलाफ़ होता है, इसलिए दूसरी तरफ़ कोई नहीं बता सकता कि यह string आपकी किस process ने बनाई।

चरण 1: sitekey उसी client से पढ़ें जिससे आप submit करेंगे

पूरे flow के लिए एक ही client इस्तेमाल करें। यह सफ़ाई-पसंदगी नहीं है। यही असली बात है: cookie jar और connection pool ही वे चीज़ें हैं जो POST को उसी visitor का दिखाती हैं जिसने GET किया था। submit के लिए नया client बनाया तो पेज की सेट की हुई हर session cookie फेंक देंगे, और सही token के बावजूद फेल होने की यह बहुत आम वजह है।

# pip install capskip httpx
import re
import httpx

PAGE_URL = "https://example.com/page-with-recaptcha"

# One client for the whole flow. Its cookie jar is what makes
# the POST later look like the same visitor as this GET.
client = httpx.Client(timeout=30, follow_redirects=True)

html = client.get(PAGE_URL).text
match = re.search(r'data-sitekey=["\']([^"\']+)', html)
if not match:
    raise RuntimeError("No data-sitekey in the HTML.")

sitekey = match.group(1)   # this is what the solver needs

अगर वह regex कुछ नहीं ढूँढ पाता, तो widget markup में परोसा नहीं जा रहा बल्कि JavaScript से inject हो रहा है, और कोई HTTP client उसे कभी नहीं देख पाएगा। पेज को ब्राउज़र में खोलें, network tab से sitekey एक बार निकाल लें, और उसे hardcode कर दें। sitekey सार्वजनिक होती है और स्थिर रहती है, इसलिए यह ऐसा शॉर्टकट नहीं जिस पर बुरा लगे।

चरण 2: उसे अपनी ही मशीन पर हल करें

हल करना आपके अपने hardware पर सुन रही एक service के ख़िलाफ़ बस एक call है। reCAPTCHA का हर variant वही एक method है, बस keyword arguments अलग होते हैं: invisible को 1, enterprise को 1, या version को v3 के साथ एक action। Turnstile और GeeTest के अपने methods हैं, ढाँचा वही रहता है। पूरी parameter सूची यहाँ है: CapSkip API डॉक्यूमेंटेशन.

# CapSkip listens on your machine, so this is a loopback call.
from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)
token = solver.recaptcha(sitekey=sitekey, url=PAGE_URL)["code"]

वह call polling के दौरान block रहती है। SDK सपाट interval पर poll नहीं करता: यह 250 मिलीसेकंड से शुरू होकर pollingInterval की ओर धीमा होता जाता है, यही वजह है कि SDK का हल आमतौर पर raw endpoint के ख़िलाफ़ हाथ से लिखे loop से जल्दी लौटता है। ऊपरी सीमा recaptchaTimeout है, जो डिफ़ॉल्ट रूप से 300 सेकंड होती है।

चरण 3: token को एक form field की तरह post करें

अब उसे उसी client पर, उसी form में वापस भेजें जिसे ब्राउज़र ने submit किया होता। form के बाक़ी fields भी शामिल करें, जिनमें वह छिपा हुआ CSRF या nonce मान भी है जिसे आपने पहले चरण में HTML से पढ़ा था।

# g-recaptcha-response is an ordinary form field. Same client,
# so the session cookies from the GET go along with it.
reply = client.post(
    PAGE_URL,
    data={
        "username": "demo",
        "g-recaptcha-response": token,
    },
)

print(reply.status_code)

token को तुरंत भेजें। reCAPTCHA token लगभग दो मिनट तक वैध रहता है, और हल तथा submit के बीच कोई queue, कोई retry backoff या कोई sleep उस बजट को आपकी नज़र में आए बिना जला देगा। उसकी उम्र और व्यवहार में उसका क्या मतलब है, यह यहाँ कवर किया गया है: reCAPTCHA token expiration की गाइड.

पूरा चलता हुआ उदाहरण, async

वही तीन चरण, async client के साथ। Python में AsyncCapSkip sync वाले का उपनाम नहीं बल्कि असली async implementation है, इसलिए यह आपके event loop को साझा करता है और polling के दौरान कोई thread रोककर नहीं रखता।

# pip install capskip httpx
import asyncio
import re
import httpx
from capskip import AsyncCapSkip

PAGE_URL = "https://example.com/page-with-recaptcha"
SITEKEY_RE = re.compile(r'data-sitekey=["\']([^"\']+)')

async def submit_once(client, solver):
    html = (await client.get(PAGE_URL)).text
    match = SITEKEY_RE.search(html)
    if not match:
        raise RuntimeError("No data-sitekey in the HTML.")

    result = await solver.recaptcha(sitekey=match.group(1), url=PAGE_URL)

    reply = await client.post(
        PAGE_URL,
        data={"g-recaptcha-response": result["code"]},
    )
    return reply.status_code

async def main():
    solver = AsyncCapSkip(host="127.0.0.1", port=8080)
    async with httpx.AsyncClient(timeout=30, follow_redirects=True) as client:
        print(await submit_once(client, solver))

asyncio.run(main())

इनमें से कई को एक साथ चलाना हो तो उन्हें gather कर लें। पूरे batch के लिए एक ही solver instance काफ़ी है, और आमतौर पर हर पहचान के लिए एक client चाहिए, क्योंकि साझा cookie jar का मतलब साझा session है। इसके batching वाले पहलू पर यहाँ लिखा गया है: Python के साथ समानांतर में कैप्चा हल करने की गाइड.

aiohttp वाला रूप

ऊपर की हर बात लागू रहती है। सिर्फ़ तीन नाम बदलते हैं: session का प्रकार, body पढ़ने का तरीक़ा, और वह attribute जिसमें status code रहता है।

# pip install capskip aiohttp
import aiohttp
from capskip import AsyncCapSkip

async def submit_once(session, solver):
    async with session.get(PAGE_URL) as reply:
        html = await reply.text()

    match = SITEKEY_RE.search(html)
    result = await solver.recaptcha(sitekey=match.group(1), url=PAGE_URL)

    async with session.post(
        PAGE_URL,
        data={"g-recaptcha-response": result["code"]},
    ) as submitted:
        return submitted.status   # not status_code

async def main():
    solver = AsyncCapSkip(host="127.0.0.1", port=8080)
    async with aiohttp.ClientSession() as session:
        print(await submit_once(session, solver))

एक ClientSession अपना cookie jar ख़ुद रखता है, इसलिए एक-client वाला नियम ज्यों का त्यों लागू रहता है। aiohttp में HTTP/2 का समर्थन नहीं है, जिससे इस काम में आपका कुछ नहीं बिगड़ता।

प्रॉक्सी, और वह जाल जो सिर्फ़ HTTP clients को काटता है

अगर साइट यह जाँचती है कि token उसी पते से बना था जिससे उसे submit किया जा रहा है, तो हल और submit दोनों को एक ही प्रॉक्सी से निकलना होगा। CapSkip reCAPTCHA, Turnstile और GeeTest के लिए हर task पर एक प्रॉक्सी लेता है। Image कैप्चा प्रॉक्सी स्वीकार नहीं करते, और उन्हें इसकी ज़रूरत भी नहीं।

# Same exit address for the solve and for the submit.
result = await solver.recaptcha(
    sitekey=sitekey,
    url=PAGE_URL,
    proxy={"type": "HTTP", "uri": "user:[email protected]:3128"},
)

# httpx 0.26 and newer spell this proxy=. Before that it was
# proxies=, which 0.28 removed outright.
async with httpx.AsyncClient(proxy="http://user:[email protected]:3128") as client:
    await client.post(PAGE_URL, data={"g-recaptcha-response": result["code"]})

यहीं लोग फँसते हैं। CapSkip proxy types में SOCKS5 और SOCKS5H स्वीकार करता है, पर aiohttp सिर्फ़ HTTP प्रॉक्सी बोलता है और CONNECT tunnel के ज़रिए HTTPS, जबकि httpx SOCKS तभी करेगा जब socks extra install हो। सॉल्वर को ऐसी SOCKS5 प्रॉक्सी थमा दी जिसे आपका client इस्तेमाल ही नहीं कर सकता, तो हल एक पते से सफल होगा और submit दूसरे पते से निकलेगा, जो देखने में हूबहू ख़राब token जैसा लगता है पर होता नहीं। दोनों सिरों को ऐसे proxy type पर रखें जिसे वे दोनों बोल सकें। proxy types में से चुनाव यहाँ कवर किया गया है: हल करते समय प्रॉक्सी घुमाने की गाइड.

aiohttp की एक और ख़ास बात: requests के उलट, यह HTTP_PROXY और HTTPS_PROXY environment variables को नज़रअंदाज़ कर देता है, जब तक कि आप session को trust_env को True करके न बनाएँ। जिस प्रॉक्सी को आप लगा हुआ मान रहे थे, वह लगी ही नहीं थी, और किसी ने आपको बताया तक नहीं।

HTTP/2, और उसे चालू करने से क्या बदलता है

httpx HTTP/2 बोल सकता है, पर डिफ़ॉल्ट रूप से नहीं और extra के बिना नहीं: httpx को http2 extra के साथ install करें और client बनाते समय http2 को True पास करें। यह जान लेना ठीक रहेगा कि इससे तार पर आपका traffic दिखने में बदल जाता है, क्योंकि protocol negotiation और header का क्रम HTTP/1.1 से अलग होते हैं। यह हल करने का नहीं, fingerprinting का सवाल है और अलग विषय है, जिसका खाका यहाँ खींचा गया है: Python में TLS fingerprinting पर लिखा गया लेख.

सॉल्वर को किसी दूसरी मशीन पर चलाना

स्क्रिप्ट जगह बदलती रहती हैं। कोई container, कोई VPS या कोई scheduled worker वह मशीन नहीं है जिस पर सॉल्वर चल रहा है, और वहाँ loopback उसी container की ओर इशारा करता है, जहाँ कोई सुन ही नहीं रहा।

CapSkip में दो connection modes हैं। Local, 127.0.0.1 से bind होता है और सिर्फ़ उसी डिवाइस को जवाब देता है। Server, आपके network या public IP से bind होता है, जिससे कोई container, कोई VM या कोई hosted worker उसी Windows मशीन तक API के ज़रिए पहुँच जाता है। एक static public IP उस पते को स्थिर रखता है। दोनों ही सूरत में यह अब भी आपका अपना hardware है और अब भी बिना मीटर के चलता है, इसलिए किसी व्यस्त दिन की क़ीमत दोनों modes में एक जैसी रहती है।

# The SDK reads these three itself, so the same code works
# whether the solver is local or on another machine:
#   CAPSKIP_HOST=192.0.2.10
#   CAPSKIP_PORT=8080
#   CAPSKIP_API_KEY=your-key

solver = AsyncCapSkip()

जैसे ही सॉल्वर किसी network address पर सुनने लगे, key validation चालू कर दें, और हर worker को उसकी अपनी key दें ताकि किसी एक को बाक़ियों को छुए बिना रद्द किया जा सके। दोनों modes को यहाँ विस्तार से समझाया गया है: CapSkip सेटअप गाइड.

आम errors और उनका मतलब

आप जो देखते हैंकारणफिक्स
regex को कोई data-sitekey नहीं मिलताwidget JavaScript से inject होता है, इसलिए वह परोसे गए HTML में है ही नहींsitekey को ब्राउज़र में एक बार पढ़कर hardcode कर दें। यह सार्वजनिक और स्थिर है
form फिर से challenge के साथ वापस आता हैPOST के लिए दूसरा client बनाया गया, इसलिए session cookies खो गईंGET और POST दोनों के लिए एक ही client इस्तेमाल करें
वैध token अस्वीकार हो जाता हैहल और submit अलग-अलग पतों से निकलेदोनों पर एक ही प्रॉक्सी रखें, और ऐसा type जिसे दोनों सिरे बोल सकें
देर के बाद वैध token अस्वीकार हो जाता हैवह हल और submit के बीच queue में expire हो गयाजितना देर से हो सके हल करें और तुरंत post करें
aiohttp पर प्रॉक्सी नज़रअंदाज़ होती दिखती हैaiohttp डिफ़ॉल्ट रूप से proxy environment variables नहीं पढ़ताproxy= साफ़ तौर पर पास करें, या session को trust_env को True करके बनाएँ
httpx client constructor पर TypeErrorproxies= को httpx 0.28 में हटा दिया गयाproxy= इस्तेमाल करें, यही नाम 0.26 से चला आ रहा है
NetworkExceptionCapSkip चल नहीं रहा, या host और port गलत हैंऐप शुरू करें, या CAPSKIP_HOST को server के पते पर लगाएँ
ValidationExceptionऐसा argument जिसे SDK उस कैप्चा प्रकार के लिए स्वीकार नहीं करताप्रकार जाँचें। v2 पर action या v3 पर invisible भेजने से यह उठता है

FAQ

क्या reCAPTCHA v2 सचमुच बिना ब्राउज़र के काम करता है?

हाँ। साइट का server token को Google से सत्यापित कराता है, और वह जाँच token, sitekey और उस पते को देखती है जहाँ से वह आया। उस जाँच के पास यह पूछने का कोई ज़रिया ही नहीं कि string किस process ने बनाई। ब्राउज़र तो हमेशा से बस उस field को रखने की एक सुविधाजनक जगह भर था।

क्या SDK install करने से httpx मुझ पर थोप दिया जाता है?

हाँ, ऐसा ही होता है। requests, httpx और aiofiles इस package की घोषित dependencies हैं, इसलिए aiohttp वाले project में भी httpx उसके साथ आ जाता है। यह टकराव नहीं, बस environment में दूसरा HTTP client है, और आपके अपने कोड में उसे इस्तेमाल करने की कोई मजबूरी नहीं।

reCAPTCHA v3 के लिए यह कैसे करें?

version को v3 पास करें और वही action दें जो पेज इस्तेमाल करता है, फिर token को उसी field नाम के तहत post करें जिसमें साइट की अपनी script उसे लिखती है। वह नाम g-recaptcha-response की तरह मानकीकृत नहीं है, इसलिए उसे पेज से एक बार पढ़ लें। action का मेल खाना ज़रूरी है, वरना token असली होने के बावजूद जाँच server की तरफ़ फेल हो जाती है।

इसके लिए httpx इस्तेमाल करूँ या aiohttp?

दोनों में से कोई भी। httpx SDK के साथ पहले से install हो जाता है और सही extras के साथ HTTP/2 तथा SOCKS दोनों कर लेता है, जिससे यह वही विकल्प बन जाता है जो आपके environment में कुछ भी नया नहीं जोड़ता। aiohttp बहुत ऊँची concurrency पर तेज़ है और बहुत सारे मौजूदा crawlers इसी पर बने हैं। सॉल्वर वाला कोड दोनों के लिए एक जैसा है, इसलिए चुनाव कैप्चा वाले पहलू के आधार पर नहीं, अपने crawler की ख़ूबियों के आधार पर करें।

संक्षेप में

परोसे गए HTML से sitekey पढ़ें, उसे लोकल रूप से हल करें, फिर token को एक सामान्य form field की तरह उसी client पर post करें जिसने पेज fetch किया था। पूरे flow के लिए एक ही client, ताकि cookies बची रहें। हल और submit के लिए एक ही निकास पता, और ऐसा proxy type जिसे दोनों सिरे सचमुच बोल सकें। उसे तुरंत post करें, क्योंकि token की उम्र छोटी होती है।

Python की व्यापक कहानी यहाँ है: Python कैप्चा सॉल्वर पेज। reCAPTCHA v2 का विवरण यहाँ रहता है: reCAPTCHA v2 सॉल्वर पेज। Node.js, PHP और C# में भी वही तीन calls मौजूद हैं, और वे यहाँ सूचीबद्ध हैं: CAPTCHA solving SDK पेज.

इसे किसी बड़े crawl पर लगाने से पहले एक आख़िरी बात जान लेना ठीक रहेगा। CapSkip एक असीमित कैप्चा सॉल्वर है जो पहले से आपके अपने hardware पर चलता है, इसलिए पचास हज़ार challenges हल करने वाली crawl की लागत ठीक उतनी ही है जितनी पचास हल करने वाली crawl की।