पेज पर कैप्चा का प्रकार कैसे पहचानें और उसे कैसे हल करें (Python)

किसी पेज पर कैप्चा का प्रकार पहचानने के लिए, पेज के HTML में विजेट की script और container पढ़ें, उनसे sitekey निकालें, और उस परिवार को उस CapSkip method से जोड़ें जो उसे हल करता है। reCAPTCHA v2 बॉक्स, v3 script और Turnstile विजेट के लिए अलग-अलग options वाली तीन अलग कॉल चाहिए, और गलत अनुमान से पूरा एक हल बेकार चला जाता है और आपको ऐसा token मिलता है जिसे आप इस्तेमाल नहीं कर सकते। यह गाइड Python में करीब 175 लाइन का एक डिटेक्टर बनाती है, जो प्रकार, method और arguments लौटाता है, या साफ़ कह देता है कि वह पहचान नहीं पाया। CapSkip आपके अपने Windows PC पर चलता है और प्रति-हल कोई शुल्क नहीं लगता, इसलिए सौ पेज जाँचने वाली script पर कोई अतिरिक्त खर्च नहीं आता।
आपको क्या चाहिए
- किसी Windows PC पर चल रहा CapSkip, जिसमें आपके ज़रूरी सॉल्वर चालू हों। पहली बार शुरू करने के चरण सेटअप गाइड में समझाए गए हैं।
- Python 3.10 या बाद का। नीचे का कोड Python 3.14.0 और 3.11.7 पर, requests 2.33.0 और 2.34.2 तथा capskip 1.3.0 के साथ चलाया गया।
- एक PowerShell prompt। कमांड PowerShell syntax में लिखी गई हैं।
- ऐसा पेज जो आपका अपना हो या जिसे ऑटोमेट करने की आपको अनुमति हो, और जिस पर कैप्चा लगा हो।
- सॉल्वर के लिए एक पता। Local मोड में CapSkip केवल उसी डिवाइस के लिए 127.0.0.1 पर जवाब देता है। Server मोड में वह आपके नेटवर्क पते या पब्लिक IP पर सुनता है, ताकि किसी दूसरे PC, VPS या होस्टेड प्लेटफ़ॉर्म पर चल रही script API के ज़रिए उसे कॉल कर सके। दोनों यहाँ सेट किए जाते हैं: कनेक्शन सेटिंग्स.
यह कैसे टेस्ट किया गया: डिटेक्टर को 66 सहेजे हुए पेजों पर चलाया गया और हर पेज पर अपेक्षित नतीजा मिला। इनमें 59 हाथ से बनाए गए थे (करीब 40 वेंडरों के दस्तावेज़ के अनुसार हैं, बाकी मेरे गढ़े हुए edge cases हैं) और 7 सार्वजनिक पेजों के स्नैपशॉट हैं जो 29 अक्टूबर 2026 को लिए गए। हल असली capskip पैकेज से एक ऐसे mock CapSkip (टेस्ट के लिए बनाया गया नकली CapSkip) के सामने चलाए गए जो ऐप की input जाँचों की नकल करता है। असली ऐप नहीं चल रहा था, इसलिए नीचे कोई भी संख्या हल का समय नहीं है।
हल करने से पहले कैप्चा का प्रकार क्यों पहचानें
CapSkip में हर परिवार के लिए अलग method है: SDK में recaptcha, turnstile, geetest, altcha, capy, captchafox, friendly_captcha और इमेज के लिए normal, और साथ में HTTP API पर hcaptcha, जिसे SDK अभी wrap नहीं करता। reCAPTCHA के अंदर भी options मायने रखते हैं। Invisible, Enterprise और v3 एक ही कॉल पर लगने वाले flags हैं। गलत flags भेजने पर सबसे अच्छी स्थिति में जॉब फेल हो जाती है, और सबसे बुरी में साइट ऐसा टोकन ठुकरा देती है और कारण का कोई संकेत नहीं देती। असली ऐप गलत reCAPTCHA वर्ज़न पर इनमें से क्या करता है, यह मैं जाँच नहीं पाया। दोनों हालत में हल खर्च हो जाता है।
बाकी गलतियाँ तुरंत और साफ़ दिखती हैं। जिस पेज पर reCAPTCHA explicit तरीके से रेंडर होता है, उससे sitekey पढ़ने पर शब्द explicit मिलता है, और CapSkip उसे ERROR_GOOGLEKEY के साथ ठुकरा देता है। ERROR_GOOGLEKEY और ERROR_PAGEURL गाइड इस error को दूसरी तरफ़ से समझाती है। कैप्चा का प्रकार भरोसे के साथ पहचानने के लिए डिटेक्टर को संकेत एक तय क्रम में जाँचने होते हैं, और वही क्रम दोनों तरह की गलतियों से बचाता है। जो स्क्रैपर एक से ज़्यादा साइट से मिलेगा, उससे पहले बनाने लायक पहली चीज़ भी डिटेक्टर ही है। हर प्रकार की पृष्ठभूमि जाननी हो, तो कैप्चा क्या है गाइड बिना कोड के इन परिवारों को समझाती है।
चरण 1: इंस्टॉल करें और पेज लाएँ
एक खाली फ़ोल्डर में virtual environment बनाएँ और दोनों पैकेज इंस्टॉल करें। कमांड एनवायरनमेंट के अपने Python को पाथ से बुलाती हैं, इसलिए उसे activate करने की ज़रूरत नहीं।
py -m venv .venv .\.venv\Scripts\python.exe -m pip install capskip requests
टेक्स्ट से कैप्चा का प्रकार पहचानने के लिए डिटेक्टर को सिर्फ़ दो इनपुट चाहिए: पेज का HTML और उसका पता। पेज को requests Session से लाएँ और उसी Session को रखें, क्योंकि इमेज कैप्चा वाला चरण बाद में तस्वीर उन्हीं cookies के साथ डाउनलोड करता है। सादा requests fetch वह दिखाता है जो सर्वर किसी भी JavaScript के चलने से पहले भेजता है। Google के reCAPTCHA डेमो पेज, Cloudflare का Turnstile डेमो और hCaptcha डेमो, सब अपना विजेट उसी HTML में रखते हैं। जो पेज विजेट JavaScript से जोड़ते हैं, उनमें ऐसा नहीं होता, और “जब HTML कुछ नहीं बताता” वाला सेक्शन उसी स्थिति को कवर करता है।
चरण 2: सहायक फ़ंक्शन लिखें
यहाँ से चरण 9 तक सब कुछ detect.py नाम की एक ही फ़ाइल में सेव करें, एक के बाद एक ब्लॉक। पहला ब्लॉक ज़रूरी चीज़ें import करता है और चार छोटे readers लिखता है। कुछ न मिलने पर हर एक None लौटाता है, इसलिए छूटा हुआ attribute कभी error नहीं उठाता।
import base64, html as htmllib, re, time
from types import SimpleNamespace as NS
from urllib.parse import urljoin
import requests
SITEKEY_JS = r"sitekey['\"]?\s*:\s*['\"]([^'\"]+)" # sitekey: '...' inside a render() call
def grab(pattern, text): # first capture group that matched, or None
m = re.search(pattern, text or "", re.I | re.S)
return next(g for g in m.groups() if g is not None) if m else None
def tag(cls, h): # opening tag with CSS class cls (and not g-recaptcha-response)
pat = r'(<\w+[^>]*\sclass\s*=\s*["\'][^"\']*(?<![\w-])%s(?![\w-])[^"\']*["\'][^>]*>)'
return grab(pat % cls, h)
def attr(t, name): # attribute value from a tag string, either quote style, HTML-unescaped
v = grab(r'\s%s\s*=\s*(?:"([^"]*)"|\'([^\']*)\')' % name, t)
return None if v is None else htmllib.unescape(v)
def render_opts(call, js): # first 400 characters of the options in call(el, {...})
return grab(call + r"\([^,]+,\s*\{(.{0,400})", js)grab फ़ंक्शन पहला capture group लौटाता है जो मैच हुआ। tag फ़ंक्शन CSS class से opening tag ढूँढता है और ऐसी class को ठुकरा देता है जिसके लंबे नाम के अंदर बस वह नाम आता हो, इसलिए cf-turnstile-response नाम की class Turnstile विजेट नहीं है। attr फ़ंक्शन दोनों तरह के quotes में लिखा attribute पढ़ता है और HTML entities को unescape करता है, जो attribute में रखे challenge के लिए मायने रखता है। बिना quotes वाले attributes नहीं पढ़े जाते।
दूसरा ब्लॉक पेज को ऐसी चीज़ में बदलता है जिसे सारी जाँचें साझा कर सकें। वह comments और style ब्लॉक हटा देता है, ताकि विजेट की class का ज़िक्र करने वाला CSS नियम मैच न हो सके, और script के पतों तथा inline JavaScript को अलग-अलग इकट्ठा करता है। आख़िरी दो helpers दोनों संभावित जवाब बनाते हैं: hit (सफल पहचान), या needs_browser जवाब।
def parse(html, url):
h = re.sub(r"<!--.*?-->|<style\b.*?</style>", "", html, flags=re.S | re.I)
srcs = " ".join(re.findall(r'<script\b[^>]*?\ssrc\s*=\s*["\']([^"\']+)', h, re.I))
js = "\n".join(re.findall(r"<script\b[^>]*>(.*?)</script>", h, re.S | re.I))
return NS(h=h, srcs=htmllib.unescape(srcs), js=js, url=url)
def hit(kind, method, **args):
return {"type": kind, "method": method, "args": args}
def browser(kind, method, p, why): # the static HTML is not enough, so do not guess
return {"type": kind, "method": method, "args": {"url": p.url}, "needs_browser": why}चरण 3: hCaptcha, और जो CapSkip हल नहीं कर सकता
उन पेजों से शुरू करें जिनका सही जवाब ना है। CapSkip FunCaptcha या Arkose को हल नहीं करता, और GeeTest v4 भी GeeTest method के दायरे से बाहर है, जो v3 के लिए है। अगर डिटेक्टर इन्हें छोड़ दे, तो Arkose बॉक्स वाला पेज अगली जाँच में चला जाएगा और ऐसी चीज़ के रूप में लौट सकता है जो वह है नहीं। इसलिए यह जाँच सबसे पहले चलती है और बिना method वाला नतीजा लौटाती है।
यही ब्लॉक Cloudflare की पूरे पेज वाली जाँच, यानी “Just a moment” वाले बीच के पेज, को भी संभालता है। वह पेज Turnstile विजेट नहीं है। turnstile method को उससे दो मान चाहिए, data और pagedata, और सादा fetch उन्हें नहीं रखता। डिटेक्टर पेज के inline challenge options खोजता है और needs_browser जवाब देता है। cf_clearance गाइड बताती है कि challenge पेज पार हो जाने के बाद आगे क्या होता है।
इसके बाद, reCAPTCHA से पहले, hCaptcha आता है। CapSkip इसे हल करता है, चाहे विजेट checkbox हो, invisible हो या Enterprise, लेकिन जो साइट reCAPTCHA से hCaptcha पर गई है वह अक्सर अपने विजेट पर g-recaptcha class रहने देती है, और सिर्फ़ hCaptcha की script ही दोनों में फ़र्क बताती है। यह जाँच sitekey को विजेट से या hcaptcha.render कॉल से पढ़ती है, और जब विजेट पर data-size का मान invisible हो तो invisible जोड़ देती है। जो Enterprise पेज hcaptcha.setData के ज़रिए विजेट को rqdata मान देता है, उसे इसके बजाय needs_browser मिलता है, क्योंकि वह मान एक ही challenge के लिए जारी होता है और जल्दी expire हो जाता है।
UNSUPPORTED = {"arkose": r"arkoselabs\.com/v2/|funcaptcha\.com/fc/", "geetest_v4": r"gt4\.js|initGeetest4"}
def unsupported(p): # CapSkip cannot solve these: say so instead of misleading the reader
for kind, pat in UNSUPPORTED.items():
if re.search(pat, p.srcs + " " + p.js):
return {"type": kind, "method": None, "args": {}, "unsupported": True}
def cf_challenge(p): # the full-page "Just a moment..." interstitial, not a widget
if re.search(r"_cf_chl_opt\s*=\s*\{", p.js):
why = "data and pagedata (Cloudflare calls them cData and chlPageData) only exist at run time"
return browser("turnstile_challenge", "turnstile", p, why)
def hcaptcha(p): # before reCAPTCHA: a site that moved to hCaptcha may keep the g-recaptcha class
if not re.search(r"hcaptcha\.com/1/api\.js", p.srcs + " " + p.js):
return None
if re.search(r"\.setData\s*\(|rqdata", p.js): # Enterprise rqdata is issued per challenge
return browser("hcaptcha_enterprise", "hcaptcha", p, "rqdata is issued for one challenge at run time")
w = tag("h-captcha", p.h) or tag("g-recaptcha", p.h)
key = attr(w, "data-sitekey") or grab(SITEKEY_JS, render_opts(r"hcaptcha\.render", p.js))
if not key:
return browser("hcaptcha", "hcaptcha", p, "no sitekey in the static HTML")
args = {"sitekey": key, "url": p.url}
if attr(w, "data-size") == "invisible":
args["invisible"] = 1
return hit("hcaptcha", "hcaptcha", **args)चरण 4: reCAPTCHA, सही क्रम में
reCAPTCHA में सबसे ज़्यादा सावधानी चाहिए, क्योंकि कई CapSkip variants दो script नामों को साझा करते हैं। सवालों का क्रम ही उन्हें अलग रखता है। पहले script का नाम: enterprise.js का मतलब Enterprise flag है, api.js का नहीं। फिर script के पते में render parameter। render= के बाद की key का मतलब v3 है, लेकिन render= के बाद शब्द explicit का मतलब है कि यह v2 पेज अपना विजेट कोड से रेंडर करता है, और उसे key की तरह कभी इस्तेमाल नहीं करना चाहिए। फिर markup। g-recaptcha class और data-action attribute वाला button Google की v3 button binding है। data-action के बिना वाला button, या data-size को invisible पर सेट करने वाला कोई भी element, invisible v2 विजेट है। उस class वाली बाकी हर चीज़ checkbox है।
def recaptcha(p): # Enterprise flag first, then v3 (render=KEY), then v2
m = re.search(r"/recaptcha/(api|enterprise)\.js(\?\S*)?", p.srcs)
if not m:
return None
ent = {"enterprise": 1} if m.group(1) == "enterprise" else {}
render = grab(r"[?&]render=([^&\s]+)", m.group(2)) # a v3 sitekey, or "explicit" (a v2 page)
t, o = tag("g-recaptcha", p.h), render_opts(r"\.render", p.js)
button, action = (t or "").lower().startswith("<button"), attr(t, "data-action")
v3 = bool(render and render != "explicit") or bool(button and action)
key = render if render != "explicit" else None
key = key or attr(t, "data-sitekey") or grab(SITEKEY_JS, o)
if not key:
return browser("recaptcha", "recaptcha", p, "no sitekey in the static HTML (set by JavaScript)")
small = attr(t, "data-size") == "invisible" or grab(r"size['\"]?\s*:\s*['\"](invisible)", o)
invisible = not v3 and bool(button or small)
action = action or grab(r"execute\(\s*[^,)]+,\s*\{\s*['\"]?action['\"]?\s*:\s*['\"]([^'\"]+)", p.js)
args = {"sitekey": key, "url": p.url, **ent}
if v3:
args.update(version="v3", **({"action": action} if action else {}))
if invisible:
args["invisible"] = 1
kind = "recaptcha_" + ("v3" if v3 else "v2_invisible" if invisible else "v2")
return hit(kind + ("_enterprise" if ent else ""), "recaptcha", **args)हर नतीजा एक ऐसी कॉल से जुड़ता है जिसे आप पहले से जानते हैं। Checkbox यानी sitekey और पेज URL के साथ solver.recaptcha। Invisible invisible=1 जोड़ता है, Enterprise enterprise=1 जोड़ता है, और v3 version="v3" और action जोड़ता है। action का नाम data-action attribute से या पेज की execute कॉल से आता है; अगर HTML में दोनों में से कोई न हो, तो argument छोड़ दिया जाता है और CapSkip अपना default verify इस्तेमाल करता है। इसके हर variant की जानकारी reCAPTCHA सॉल्वर पेज पर है।
चरण 5: Turnstile
Turnstile विजेट को पेज पर दो चीज़ें चाहिए: Cloudflare की api.js और एक sitekey। key cf-turnstile class वाले div के data-sitekey attribute में होती है, या turnstile.render कॉल में। जाँच के लिए script का होना ज़रूरी है, इसलिए किसी असंबंधित पेज पर भटकी हुई class का नाम इसे चालू नहीं कर सकता। यह data-action और data-cdata भी मौजूद होने पर पढ़ती है और आगे पास कर देती है। options की सूची Turnstile सॉल्वर पेज पर मिलती है।
def turnstile(p): # widget only: implicit (div.cf-turnstile) or explicit (turnstile.render)
if "challenges.cloudflare.com/turnstile/v0/api.js" not in p.srcs:
return None
t, o = tag("cf-turnstile", p.h), render_opts(r"turnstile\.render", p.js)
key = attr(t, "data-sitekey") or grab(SITEKEY_JS, o)
if not key:
return browser("turnstile", "turnstile", p, "no sitekey in the static HTML")
action = attr(t, "data-action") or grab(r"\baction['\"]?\s*:\s*['\"]([^'\"]+)", o)
cdata = attr(t, "data-cdata") or grab(r"\bcData['\"]?\s*:\s*['\"]([^'\"]+)", o)
extra = {k: v for k, v in (("action", action), ("data", cdata)) if v}
return hit("turnstile", "turnstile", sitekey=key, url=p.url, **extra)चरण 6: GeeTest और ALTCHA
GeeTest v3 आपको दो मान देता है, और उनमें से एक जाल है। gt मान साइट के लिए तय रहता है और पेज में हो सकता है। challenge मान एक ही बार चलता है, और साइटें उसे run time पर एक register endpoint से लाती हैं। अगर पेज में दोनों हों, तो डिटेक्टर उन्हें लौटा देता है। अगर उसमें सिर्फ़ initGeetest कॉल हो, तो वह needs_browser जवाब देता है, क्योंकि पुराने HTML में मिला challenge तब तक बेकार हो चुका होगा।
ALTCHA ने अपनी पीढ़ियों के बीच attribute के नाम बदले। वर्ज़न 1 और 2 challengeurl इस्तेमाल करते हैं, या inline challenge के लिए challengejson। वर्ज़न 3 और बाद के एक ही attribute challenge इस्तेमाल करते हैं, जो URL या challenge data, दोनों में से कुछ भी ले सकता है। जाँच तीनों पढ़ती है और relative URL को पेज के पते से जोड़ देती है।
def geetest(p): # v3 only: gt is static, challenge comes from the register endpoint
if not (re.search(r"initGeetest\s*\(", p.js) or re.search(r"/gt(\.[\d.]+)?\.js", p.srcs)):
return None
gt = grab(r"\bgt['\"]?\s*:\s*['\"]([0-9a-f]{32})", p.js)
challenge = grab(r"\bchallenge['\"]?\s*:\s*['\"]([0-9a-f]{32,34})", p.js)
if not (gt and challenge):
return browser("geetest", "geetest", p, "gt and challenge come from the register endpoint")
return hit("geetest", "geetest", gt=gt, challenge=challenge, url=p.url)
def altcha(p): # challengeurl / challengejson (v1, v2) or challenge (v3+)
t = grab(r"(<altcha-widget\b[^>]*>)", p.h)
if not t:
return None
c = attr(t, "challenge")
as_json = attr(t, "challengejson") or (c if (c or "").lstrip().startswith("{") else None)
as_url = attr(t, "challengeurl") or (None if as_json else c)
if as_json:
return hit("altcha", "altcha", url=p.url, challenge_json=as_json)
if as_url:
return hit("altcha", "altcha", url=p.url, challenge_url=urljoin(p.url, as_url))
return browser("altcha", "altcha", p, "no challenge attribute")चरण 7: Capy, CaptchaFox और Friendly Captcha
ये तीनों आसानी से मिल जाते हैं और आसानी से थोड़ा गलत भी हो जाते हैं। इस जाँच के लिए Capy को विजेट div नहीं चाहिए। उसकी key PUZZLE_ से शुरू होती है और विजेट script के पते के k parameter में रहती है, और default के अलावा कोई host api_server के रूप में पास किया जाता है। CaptchaFox दो build में आता है। सादा वाला cdn.captchafox.com से लोड होता है। दूसरा किसी होस्टिंग प्लेटफ़ॉर्म द्वारा साझा CDN पाथ के नीचे bundle किया जाता है, इसलिए डिटेक्टर उस पाथ में CaptchaFox पैकेज का नाम खोजता है, अकेले पाथ को नहीं, जिसे दूसरे विजेट भी इस्तेमाल करते हैं।
Friendly Captcha सबसे पेचीदा है। वर्ज़न 1 और वर्ज़न 2 एक ही div और एक ही sitekey namespace इस्तेमाल करते हैं, लेकिन protocol अलग हैं, और गलत वर्ज़न का टोकन CapSkip स्वीकार कर लेता है और साइट ठुकरा देती है। script के नाम दोनों को अलग बताते हैं: वर्ज़न 2 @friendlycaptcha/sdk पैकेज लोड करता है, वर्ज़न 1 friendly-challenge। जब पेज कोई संकेत न दे, तो डिटेक्टर अनुमान लगाने से मना कर देता है और needs_browser कहता है।
def capy(p): # the widget script is .../puzzle/get_js/?k=PUZZLE_...
m = re.search(r"((?:https?:)?//[^\s\"']+?)/puzzle/get_js/\S*?(PUZZLE_[\w-]+)", p.srcs)
if m:
host = m.group(1) if m.group(1).startswith("http") else "https:" + m.group(1)
more = {} if host == "https://jp.api.capy.me" else {"api_server": host}
return hit("capy", "capy", sitekey=m.group(2), url=p.url, **more)
MAM = "mampkg/@mamdev/core.frontend.libs.captchafox"
def captchafox(p): # plain build from cdn.captchafox.com, or the bundled build from s.uicdn.com
t, key = tag("captchafox", p.h), grab(r"captchafox[^\"']*/api\.js\?key=([^\"'&]+)", p.srcs)
if t or key:
more = {"api_server": "https://s.uicdn.com/" + MAM + "/"} if MAM in p.srcs else {}
sitekey = attr(t, "data-sitekey") or key
if not sitekey:
return browser("captchafox", "captchafox", p, "no sitekey in the static HTML")
return hit("captchafox", "captchafox", sitekey=sitekey, url=p.url, **more)
def friendly(p): # v2 loads @friendlycaptcha/sdk (site.min.js), v1 loads friendly-challenge
t = tag("frc-captcha", p.h)
if not t:
return None
v2_end, v1_end = attr(t, "data-api-endpoint"), attr(t, "data-puzzle-endpoint")
server = v2_end or v1_end
v2 = v2_end is not None or re.search(r"friendlycaptcha/sdk|site(\.compat)?\.min\.js", p.srcs)
v1 = v1_end is not None or re.search(r"friendly-challenge|widget(\.module)?\.min\.js", p.srcs)
if bool(v2) == bool(v1):
return browser("friendly_captcha", "friendly_captcha", p, "cannot tell v1 from v2 in the HTML")
if not attr(t, "data-sitekey"):
return browser("friendly_captcha", "friendly_captcha", p, "no sitekey in the static HTML")
more = {"api_server": server} if server else {}
return hit("friendly_captcha_" + ("v2" if v2 else "v1"), "friendly_captcha",
sitekey=attr(t, "data-sitekey"), url=p.url, version="v2" if v2 else "v1", **more)इनमें से हर एक का अपना सॉल्वर पेज है, जिसमें उस method के options दिए हैं:
- Capy Puzzle सॉल्वर पेज: Capy के जवाब में लौटने वाले तीन मान।
- CaptchaFox सॉल्वर पेज: CaptchaFox method के options।
- Friendly Captcha सॉल्वर पेज: Friendly Captcha method।
चरण 8: इमेज कैप्चा, एक अनुमान
इमेज कैप्चा में खोजने के लिए कोई vendor script नहीं होती, इसलिए यह जाँच एक अनुमान है और तभी चलती है जब और कुछ मैच न हुआ हो। जाँच को ऐसी इमेज चाहिए जिसके tag में captcha लिखा हो, और पेज में कहीं ऐसा टेक्स्ट बॉक्स जिसके tag में captcha या security code लिखा हो। हर उस मामले में यह चूक गई जहाँ markup में ये शब्द नहीं थे, और बनाया हुआ canvas इसे दिखता ही नहीं। hit को संभावित मानें, पक्का नहीं।
def image(p): # a guess: an <img> that says captcha, next to a text box that says captcha
box = r"<input\b(?![^>]*type\s*=\s*[\"']?(?:hidden|checkbox|submit))[^>]*(?:captcha|security.?code)"
for img in re.findall(r"<img\b[^>]*>", p.h, re.I):
if re.search(box, p.h, re.I) and re.search(r"captcha", img, re.I) and attr(img, "src"):
return hit("image", "normal", image=urljoin(p.url, attr(img, "src")))चरण 9: जाँचें, detect और solve
आख़िरी ब्लॉक जाँचों को क्रम से सूचीबद्ध करता है और detect तथा solve को उपलब्ध कराता है, detect_all एक helper के रूप में। यही क्रम तर्क है: पहले unsupported vendors, फिर Cloudflare पेज, फिर hCaptcha, फिर बाकी हर परिवार, और इमेज का अनुमान तभी जब सूची को कुछ न मिला हो। detect फ़ंक्शन पहला नतीजा लौटाता है और जब पेज में एक से ज़्यादा परिवार हों, जैसे v3 script के साथ Turnstile बॉक्स, तो also सूची जोड़ देता है। सूची परिवारों के नाम बताती है, किसी दूसरी key वाला दूसरा reCAPTCHA विजेट नहीं। solve फ़ंक्शन नतीजे को सही CapSkip method तक भेजता है। unsupported या needs_browser चिह्नित किसी भी नतीजे को वह RuntimeError के साथ मना कर देता है, और इमेज को आपके अपने Session से डाउनलोड करता है ताकि cookies मेल खाएँ।
SDK में अभी कोई hCaptcha कॉल नहीं है, इसलिए hCaptcha नतीजा इसके बजाय solve_hcaptcha को जाता है। यह sitekey और पेज का पता method=hcaptcha के साथ in.php पर पोस्ट करता है, फिर token तैयार होने तक हर 5 सेकंड में res.php को poll करता है, जैसा कि API दस्तावेज़ों का hCaptcha सेक्शन बताता है। सॉल्वर का पता api के रूप में दें, और अगर API key validation चालू है तो अपनी API key को key के रूप में दें। नतीजे में token, code में होता है और जिस User-Agent से वह मिला, वह userAgent में।
CHECKS = [unsupported, cf_challenge, hcaptcha, recaptcha, turnstile, geetest, altcha, capy, captchafox, friendly]
def detect_all(html, page_url):
p = parse(html, page_url)
return [r for r in (check(p) for check in CHECKS) if r] or [r for r in [image(p)] if r]
def detect(html, page_url):
found = detect_all(html, page_url)
if len(found) > 1:
found[0]["also"] = [r["type"] for r in found[1:]]
return found[0] if found else None
def solve_hcaptcha(args, api, key=None): # no SDK call yet: post to in.php, poll res.php
form = {"method": "hcaptcha", "sitekey": args["sitekey"], "pageurl": args["url"],
"invisible": args.get("invisible", 0), "data": args.get("data"), "key": key, "json": 1}
job = requests.post(api + "/in.php", data=form, timeout=30).json()
if job["status"] != 1:
raise RuntimeError(job["request"])
for _ in range(36): # a solve takes 10 to 30 seconds, so give up after 3 minutes
time.sleep(5)
q = {"key": key, "action": "get", "id": job["request"], "json": 1}
r = requests.get(api + "/res.php", params=q, timeout=30).json()
if r["request"] != "CAPCHA_NOT_READY":
if r["status"] != 1:
raise RuntimeError(r["request"])
return {"code": r["request"], "userAgent": r.get("useragent")}
raise TimeoutError("hCaptcha was not solved within 3 minutes")
def solve(info, solver, session=None, api="http://127.0.0.1:8080", key=None):
if info.get("unsupported") or info.get("needs_browser"):
raise RuntimeError(info.get("needs_browser") or info["type"] + " is not supported by CapSkip")
args = dict(info["args"])
if info["method"] == "hcaptcha":
return solve_hcaptcha(args, api, key)
if info["method"] == "normal": # fetch the image with YOUR session so the cookie matches it
src = args.pop("image")
if not src.startswith("data:"):
r = (session or requests).get(src, timeout=30)
r.raise_for_status()
src = "data:image/png;base64," + base64.b64encode(r.content).decode()
args["file"] = src
return getattr(solver, info["method"])(**args)इसे चलाएँ
इसे detect.py के बगल में use.py नाम से सेव करें और एनवायरनमेंट के अपने Python से चलाएँ, जैसे .\.venv\Scripts\python.exe use.py। पोर्ट 8080 default है; अगर आपका अलग हो तो CapSkip की कनेक्शन सेटिंग्स में दिखा पोर्ट इस्तेमाल करें। script आपका पेज लाती है, डिटेक्टर को जो मिला वह प्रिंट करती है, और CapSkip को तभी कॉल करती है जब कॉल करने के लिए कोई method हो और कुछ छूटा न हो।
import requests
from capskip import CapSkip
from detect import detect, solve
URL = "https://example.com/login" # a page of yours with a CAPTCHA
HOST, PORT = "127.0.0.1", 8080 # from CapSkip's connection settings
session = requests.Session()
html = session.get(URL, timeout=30).text
info = detect(html, URL)
print(info)
if info and info["method"] and not info.get("needs_browser"):
solver = CapSkip(host=HOST, port=PORT)
result = solve(info, solver, session, api=f"http://{HOST}:{PORT}")
print(result)reCAPTCHA v2 checkbox वाले पेज पर पहला print प्रकार, method और arguments दिखाता है, और दूसरा नतीजा दिखाता है जिसके code फ़ील्ड में टोकन होता है। mock वाले रन में यह ऐसा दिखा:
| पेज | detect यह लौटाता है |
|---|---|
| reCAPTCHA v2 checkbox | type recaptcha_v2, method recaptcha, sitekey और url |
| hCaptcha विजेट | type hcaptcha, method hcaptcha, sitekey और url |
| Cloudflare “Just a moment” पेज | type turnstile_challenge, needs_browser सेट |
| v3 script के साथ Turnstile विजेट | type recaptcha_v3, also सूची में turnstile |
जब HTML कुछ नहीं बताता: ब्राउज़र कंसोल
कुछ पेज लोड होने के बाद JavaScript से अपना विजेट जोड़ते हैं। तब सादा fetch को कैप्चा मिलता ही नहीं, और डिटेक्टर ऐसे पेज के लिए भी None लौटाता है जिस पर कैप्चा है। दो मामले जाने-पहचाने और अपेक्षित हैं: कोड से जोड़ी गई Google या Cloudflare script, और कोड से सेट की गई sitekey। इनके लिए पेज को Chrome में खोलें, F12 दबाएँ और यह Console में पेस्ट करें। अगर Chrome पेस्ट रोक दे, तो पहले allow pasting टाइप करें और Enter दबाएँ। यह उन resources से sitekeys पढ़ता है जिन्हें पेज पहले ही लोड कर चुका है, इसलिए यह वही देखता है जो ब्राउज़र ने बनाया।
(() => {
const iframes = [...document.querySelectorAll('iframe')].map((f) => f.src);
const urls = performance.getEntriesByType('resource').map((r) => r.name).concat(iframes);
const found = new Map(), add = (o) => found.set(JSON.stringify(o), o);
for (const u of urls) {
let m;
if ((m = u.match(/recaptcha\/(?:api2|(enterprise))\/anchor\?.*?\bk=([\w-]+)/)))
add({ type: 'recaptcha', sitekey: m[2], enterprise: !!m[1], invisible: /size=invisible/.test(u) });
else if ((m = u.match(/recaptcha\/(?:api|enterprise)\.js\?.*?\brender=([\w-]+)/)) && m[1] !== 'explicit')
add({ type: 'recaptcha v3', sitekey: m[1], enterprise: /enterprise\.js/.test(u) });
else if ((m = u.match(/challenges\.cloudflare\.com\/.*turnstile\/.*\/(0x4[\w-]+|[123]x0+[A-Za-z]+)\//)))
add({ type: 'turnstile', sitekey: m[1] });
else if ((m = u.match(/hcaptcha\.com\/captcha\/.*[#&?]sitekey=([\w-]+)/))) add({ type: 'hcaptcha', sitekey: m[1] });
else if (/arkoselabs\.com\/fc\//.test(u)) add({ type: 'arkose: CapSkip cannot solve' });
}
if (window._cf_chl_opt) add({ type: 'cloudflare challenge page' });
document.querySelectorAll('altcha-widget').forEach((e) => add({ type: 'altcha', challenge: e.getAttribute('challenge') || e.getAttribute('challengeurl') }));
document.querySelectorAll('.h-captcha[data-sitekey]').forEach((e) => add({ type: 'hcaptcha', sitekey: e.dataset.sitekey }));
document.querySelectorAll('.frc-captcha, .captchafox').forEach((e) => add({ type: e.classList.contains('captchafox') ? 'captchafox' : 'frc-captcha', sitekey: e.dataset.sitekey }));
const v3 = [...found.values()].filter((o) => o.type === 'recaptcha v3').map((o) => o.sitekey); // a v3 badge also adds an invisible anchor frame
const res = [...found.values()].filter((o) => !(o.type === 'recaptcha' && v3.includes(o.sitekey)));
return res.length ? res : 'no known CAPTCHA found (GeeTest, Capy and CaptchaFox scripts are not checked)';
})();इसे reCAPTCHA v2 पेज, एक invisible पेज, एक v3 पेज, एक Turnstile पेज और hCaptcha demo पर sitekey और flags मिल गए, और बिना कैप्चा वाले पेज पर इसने एक संदेश प्रिंट किया। तीन सीमाएँ हैं: ब्राउज़र default रूप से सिर्फ़ करीब 250 resource entries रखता है, इसलिए लंबे समय से चल रहा single page app शुरुआती entries खो सकता है, और snippet Friendly Captcha वर्ज़न 1 और वर्ज़न 2 में फ़र्क नहीं कर सकता, और वह GeeTest, Capy या CaptchaFox की scripts नहीं खोजता। Cloudflare की पूरे पेज वाली जाँच पर यह resource के पते से sitekey दिखा सकता है, लेकिन data और pagedata मान नहीं, जिनके लिए render कॉल पर hook चाहिए। वह hook यहाँ टेस्ट नहीं किया गया।
जहाँ पहचान विफल होती है
| स्थिति | detect क्या लौटाता है | क्या करें |
|---|---|---|
| विजेट script JavaScript से जोड़ी गई | कोई नहीं | पेज को ब्राउज़र में लोड करें और console snippet चलाएँ |
| sitekey JavaScript से सेट की गई | needs_browser | key लाइव पेज से पढ़ें |
| Cloudflare की पूरे पेज वाली जाँच | needs_browser | मान ब्राउज़र में पढ़ें, cf_clearance गाइड देखें |
| HTML में विजेट के बिना CaptchaFox, Friendly Captcha या Capy की script | कोई नहीं | console snippet चलाएँ या पेज का source पढ़ें |
| साइट की ओर से ही आया 403 या 503 पेज | कोई नहीं | पहले status code प्रिंट करें: ब्लॉक पेज में खोजने के लिए कोई कैप्चा नहीं होता |
| Arkose, GeeTest v4 | unsupported | CapSkip इन्हें हल नहीं कर सकता |
| rqdata सेट करने वाला hCaptcha Enterprise | needs_browser | हर task के लिए live पेज से ताज़ा rqdata पढ़ें और उसे data के रूप में solve_hcaptcha को दें |
| script के संकेत के बिना Friendly Captcha | needs_browser | देखें कि पेज कौन सी script लोड करता है |
| captcha शब्द के बिना इमेज कैप्चा | कोई नहीं | उस साइट के लिए अपना नियम जोड़ें |
आम errors
| त्रुटि | कारण | फिक्स |
|---|---|---|
| ERROR_GOOGLEKEY | key शब्द explicit थी, या ऐसी key थी जो reCAPTCHA key नहीं है | key विजेट से पढ़ें, render parameter से नहीं |
| ERROR_CAPTCHA_UNSOLVABLE | कोई भी फेल जॉब ऐसी ही दिखती है, गलत reCAPTCHA वर्ज़न समेत, और वह कारण का नाम नहीं बताती | पहले पहचाना गया प्रकार जाँचें, फिर यह देखें: API दस्तावेज़ |
| ValidationException | ऐसा option जो method नहीं लेता, जैसे v2 कॉल पर action | डिटेक्टर ने जो arguments लौटाए, उन्हें इस्तेमाल करें |
| NetworkException | CapSkip चल नहीं रहा, या host या port गलत है | CapSkip शुरू करें और कनेक्शन सेटिंग्स जाँचें, फिर यह देखें: |
| solve से RuntimeError | नतीजा unsupported या needs_browser है, या CapSkip ने hCaptcha जॉब ठुकरा दी और संदेश उसका error code है | solve को कॉल करने से पहले पहले दो मामलों को संभालें, और तीसरे के लिए sitekey और पेज का पता जाँचें |
सॉल्वर को किसी server पर चलाना
डिटेक्टर को CapSkip से सिर्फ़ दो चीज़ें चाहिए: एक host और एक पोर्ट। Local मोड में वह 127.0.0.1 है और आपका अपना PC। Server मोड में CapSkip आपके नेटवर्क पते या पब्लिक IP पर सुनता है, इसलिए किसी दूसरे PC, VPS या होस्टेड प्लेटफ़ॉर्म पर चल रहा डिटेक्टर उसी API से उसे कॉल कर सकता है। use.py में ऊपर HOST और PORT बदल दें, कोड में और कुछ नहीं बदलता। स्थिर रिमोट एक्सेस के लिए static पब्लिक IP सुझाई जाती है। Server मोड फिर भी आपका अपना हार्डवेयर है और फिर भी बिना मीटर वाला है। ध्यान दें कि Server मोड पोर्ट को किसी भी पते के लिए खोल देता है, इसलिए जिन पतों को वहाँ पहुँचना नहीं चाहिए उनके लिए Windows Firewall में block rule जोड़ें। मोड बदलने का तरीका जानने के लिए कनेक्शन सेटिंग्स देखें।
FAQ
क्या मैं बिना कोड के बता सकता हूँ कि कोई साइट कौन सा कैप्चा इस्तेमाल करती है?
हाँ। DevTools खोलें, Network टैब पर जाएँ, पेज रीलोड करें और script के पते देखें। google.com/recaptcha या recaptcha.net की script reCAPTCHA है, challenges.cloudflare.com Turnstile है, और js.hcaptcha.com hCaptcha है। ऊपर का Console snippet यही खोज आपके लिए करता है।
detect अनुमान की जगह needs_browser क्यों लौटाता है?
क्योंकि गलत अनुमान पर एक पूरा हल बेकार जाता है और ऐसा टोकन मिलता है जिसे साइट बिना किसी संकेत के ठुकरा देती है। जब static HTML में sitekey, GeeTest challenge या Friendly Captcha वर्ज़न न हो, तो यह बता देना फेल हुई जॉब से जल्दी निपटता है।
क्या CapSkip hCaptcha हल करता है?
हाँ। CapSkip अपने HTTP API के ज़रिए method=hcaptcha के साथ hCaptcha के checkbox, invisible और Enterprise विजेट हल करता है, जैसा कि API दस्तावेज़ बताते हैं। Python SDK में अभी कोई hCaptcha कॉल नहीं है, इसलिए ऊपर वाला solve_hcaptcha जॉब को खुद in.php पर पोस्ट करता है। FunCaptcha, Arkose और GeeTest v4 अब भी सपोर्टेड नहीं हैं, और डिटेक्टर इन्हें unsupported बताता है ताकि script उस पेज पर फेल होने के बजाय उसे छोड़ सके।
क्या कोई होस्टेड प्लेटफ़ॉर्म मेरे CapSkip के साथ डिटेक्टर इस्तेमाल कर सकता है?
हाँ, Server मोड के ज़रिए। CapSkip को अपने नेटवर्क या पब्लिक IP पर सुनने के लिए सेट करें, use.py में HOST और PORT को उसी पते पर सेट करें, और डिटेक्टर तथा solve कॉल वैसे ही काम करते हैं जैसे एक PC पर। सॉल्वर आपके हार्डवेयर पर ही रहता है।
संक्षेप में
पहले कैप्चा का प्रकार पहचानें, फिर हल करें: script और markup को तय क्रम में पढ़ें, arguments सही method को दें, और None, needs_browser तथा unsupported को जवाब मानें, error नहीं। अपनी मशीन पर CapSkip के साथ किसी पेज को जाँचते समय कोई मीटर नहीं चल रहा होता, और इसी वजह से असीमित कैप्चा सॉल्वर उस crawl के लिए व्यावहारिक बनता है जो हर कुछ मिनट में एक नई साइट से मिलता है। Python SDK के बारे में खुद जानना हो, तो यह पेज देखें: Python कैप्चा सॉल्वर पेज.
