Node.js में कैप्चा डिटेक्टर कैसे बनाएँ और कैप्चा कैसे हल करें

Node.js में कैप्चा डिटेक्टर पेज के HTML में विजेट की script और container पढ़ता है, उनसे sitekey निकालता है, और उस परिवार को उस CapSkip method से जोड़ता है जो उसे हल करता है। reCAPTCHA v2 बॉक्स, v3 script और Turnstile विजेट के लिए अलग-अलग options वाली तीन अलग कॉल चाहिए, और गलत अनुमान से एक हल बेकार जाता है और ऐसा नतीजा मिलता है जो काम का नहीं। यह गाइड करीब 165 लाइन का एक डिटेक्टर बनाती है जिसमें capskip के अलावा कोई पैकेज नहीं है। वह प्रकार, method और arguments लौटाता है, या साफ़ कह देता है कि वह पहचान नहीं पाया। CapSkip आपके अपने Windows PC पर चलता है और प्रति-हल कोई शुल्क नहीं लगता, इसलिए सौ पेज जाँचने वाली script पर कोई अतिरिक्त खर्च नहीं आता। Python में यही डिटेक्टर यहाँ है: कैप्चा टाइप डिटेक्शन गाइड.
आपको क्या चाहिए
- किसी Windows PC पर चल रहा CapSkip, जिसमें आपके ज़रूरी सॉल्वर चालू हों। पहली बार शुरू करने के चरण सेटअप गाइड में समझाए गए हैं।
- Node.js 20 या बाद का। नीचे का कोड Node.js 24.22.0 पर चला, और डिटेक्टर तथा solve वाला चरण 20.11.1 और 25.2.1 पर भी चले, capskip 1.3.0 के साथ। पैकेज को खुद Node.js 18 या बाद का चाहिए, लेकिन Node.js 18 यहाँ आज़माया नहीं गया।
- एक PowerShell prompt। कमांड PowerShell syntax में लिखी गई हैं।
- ऐसा पेज जो आपका अपना हो या जिसे ऑटोमेट करने की आपको अनुमति हो, और जिस पर कैप्चा लगा हो।
- सॉल्वर के लिए एक पता। Local मोड में CapSkip केवल उसी डिवाइस के लिए 127.0.0.1 पर जवाब देता है। Server मोड में वह आपके नेटवर्क पते या पब्लिक IP पर सुनता है, ताकि किसी दूसरे PC, VPS या होस्टेड प्लेटफ़ॉर्म पर चल रही script API के ज़रिए उसे कॉल कर सके। दोनों यहाँ सेट किए जाते हैं: कनेक्शन सेटिंग्स.
यह कैसे टेस्ट किया गया: डिटेक्टर को उन्हीं 66 सहेजे हुए पेजों पर चलाया गया जिन पर Python वाला चला था, और हर पेज पर वही नतीजा मिला। इनमें 59 हाथ से बनाए गए थे (करीब 40 वेंडरों के दस्तावेज़ के अनुसार हैं, बाकी मेरे गढ़े हुए edge cases हैं) और 7 सार्वजनिक पेजों के स्नैपशॉट हैं जो 29 अक्टूबर 2026 को लिए गए। हल असली capskip पैकेज से एक ऐसे mock CapSkip के सामने चलाए गए जो ऐप की input जाँचों की नकल करता है। असली ऐप नहीं चल रहा था, इसलिए नीचे कोई भी संख्या हल का समय नहीं है।
हल करने से पहले कैप्चा का प्रकार क्यों पहचानें
CapSkip में हर परिवार के लिए अलग method है: SDK में recaptcha, turnstile, geetest, altcha, capy, captchafox, friendlyCaptcha और इमेज के लिए 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: इंस्टॉल करें और पेज लाएँ
एक फ़ोल्डर बनाएँ, प्रोजेक्ट initialise करें और capskip इंस्टॉल करें। डिटेक्टर सिर्फ़ regular expressions इस्तेमाल करता है, और fetch कॉल उस fetch का इस्तेमाल करती हैं जो Node.js के साथ आता है।
npm init -y npm install capskip
Node.js में कैप्चा डिटेक्टर टेक्स्ट पर काम करता है, इसलिए उसे सिर्फ़ दो इनपुट चाहिए: पेज का HTML और उसका पता। सादा fetch वह दिखाता है जो सर्वर किसी भी JavaScript के चलने से पहले भेजता है। Google के reCAPTCHA डेमो पेज, Cloudflare का Turnstile डेमो और hCaptcha डेमो, सब अपना विजेट उसी HTML में रखते हैं। जो पेज विजेट JavaScript से जोड़ते हैं, उनमें ऐसा नहीं होता, और “जब HTML कुछ नहीं बताता” वाला सेक्शन उसी स्थिति को कवर करता है। Node.js का fetch cookies नहीं रखता, जो इमेज कैप्चा वाले चरण के लिए मायने रखता है, इसलिए नीचे की script उन्हें हाथ से आगे पास करती है।
चरण 2: सहायक फ़ंक्शन लिखें
यहाँ से चरण 9 तक सब कुछ detect.js नाम की एक ही फ़ाइल में सेव करें, एक के बाद एक ब्लॉक। पहला ब्लॉक छोटे readers लिखता है। कुछ न मिलने पर हर एक null लौटाता है, इसलिए छूटा हुआ attribute कभी exception नहीं फेंकता।
'use strict';
const SITEKEY_JS = /sitekey['"]?\s*:\s*['"]([^'"]+)/; // sitekey: '...' inside a render() call
const NAMED = { '&': '&', '<': '<', '>': '>', '"': '"', ''': "'" };
const unescapeHtml = (s) => s.replace(/&(?:#x([0-9a-f]+)|#(\d+)|[a-z]+);/gi, (m, hex, dec) =>
hex || dec ? String.fromCodePoint(parseInt(hex || dec, hex ? 16 : 10)) : NAMED[m.toLowerCase()] || m);
function grab(re, text) { // first capture group that matched, or null
const m = new RegExp(re.source, 'is').exec(text || '');
return m ? m.slice(1).find((g) => g !== undefined) ?? null : null;
}
const tag = (cls, h) => grab(new RegExp( // opening tag that has the CSS class cls
`(<\\w+[^>]*\\sclass\\s*=\\s*["'][^"']*(?<![\\w-])${cls}(?![\\w-])[^"']*["'][^>]*>)`), h);
function attr(t, name) { // attribute value from a tag string, either quote style, unescaped
const v = grab(new RegExp(`\\s${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`), t);
return v === null ? null : unescapeHtml(v);
}
const renderOpts = (call, js) => grab(new RegExp(`${call}\\([^,]+,\\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 को अलग-अलग इकट्ठा करता है। parse के बाद के दो helpers जवाब बनाते हैं: hit, और browser, जिसमें needsBrowser की वजह रहती है। तीसरा जवाब, unsupported, इस ब्लॉक के अंत की जाँचें बनाती हैं, जिसे अगला चरण hCaptcha वाली जाँच के साथ समझाता है।
function parse(html, url) {
const h = html.replace(/<!--.*?-->|<style\b.*?<\/style>/gis, '');
const srcs = [...h.matchAll(/<script\b[^>]*?\ssrc\s*=\s*["']([^"']+)/gi)].map((m) => m[1]).join(' ');
const js = [...h.matchAll(/<script\b[^>]*>(.*?)<\/script>/gis)].map((m) => m[1]).join('\n');
return { h, srcs: unescapeHtml(srcs), js, url };
}
const hit = (type, method, args) => ({ type, method, args });
const browser = (type, method, p, why) => ({ type, method, args: { url: p.url }, needsBrowser: why });
const UNSUPPORTED = { arkose: /arkoselabs\.com\/v2\/|funcaptcha\.com\/fc\//, geetest_v4: /gt4\.js|initGeetest4/ };
function unsupported(p) { // CapSkip cannot solve these: say so instead of misleading the reader
const type = Object.keys(UNSUPPORTED).find((k) => UNSUPPORTED[k].test(p.srcs + ' ' + p.js));
if (type) return { type, method: null, args: {}, unsupported: true };
}
const cfChallenge = (p) => /_cf_chl_opt\s*=\s*\{/.test(p.js) // the full-page "Just a moment..." page
? browser('turnstile_challenge', 'turnstile', p, 'data and pagedata (Cloudflare calls them cData and chlPageData) only exist at run time') : null;
function hcaptcha(p) { // before reCAPTCHA: a site that moved to hCaptcha may keep the g-recaptcha class
if (!/hcaptcha\.com\/1\/api\.js/.test(p.srcs + ' ' + p.js)) return null;
if (/\.setData\s*\(|rqdata/.test(p.js)) // Enterprise rqdata is issued per challenge
return browser('hcaptcha_enterprise', 'hcaptcha', p, 'rqdata is issued for one challenge at run time');
const w = tag('h-captcha', p.h) || tag('g-recaptcha', p.h);
const key = attr(w, 'data-sitekey') || grab(SITEKEY_JS, renderOpts('hcaptcha\\.render', p.js));
if (!key) return browser('hcaptcha', 'hcaptcha', p, 'no sitekey in the static HTML');
const args = { sitekey: key, url: p.url };
if (attr(w, 'data-size') === 'invisible') args.invisible = 1;
return hit('hcaptcha', 'hcaptcha', args);
}चरण 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 खोजता है और needsBrowser जवाब देता है। 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 मान देता है, उसे इसके बजाय needsBrowser मिलता है, क्योंकि वह मान एक ही challenge के लिए जारी होता है और जल्दी expire हो जाता है।
चरण 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 है।
function recaptcha(p) { // Enterprise flag first, then v3 (render=KEY), then invisible, then v2
const m = /\/recaptcha\/(api|enterprise)\.js(\?\S*)?/.exec(p.srcs);
if (!m) return null;
const ent = m[1] === 'enterprise' ? { enterprise: 1 } : {};
const render = grab(/[?&]render=([^&\s]+)/, m[2]); // a v3 sitekey, or "explicit" (a v2 page)
const t = tag('g-recaptcha', p.h), o = renderOpts('\\.render', p.js);
const button = (t || '').toLowerCase().startsWith('<button'), dataAction = attr(t, 'data-action');
const v3 = Boolean(render && render !== 'explicit') || Boolean(button && dataAction);
const key = (render !== 'explicit' ? render : null) || attr(t, 'data-sitekey') || grab(SITEKEY_JS, o);
if (!key) return browser('recaptcha', 'recaptcha', p, 'no sitekey in the static HTML (set by JavaScript)');
const small = attr(t, 'data-size') === 'invisible' || grab(/size['"]?\s*:\s*['"](invisible)/, o);
const invisible = !v3 && Boolean(button || small);
const action = dataAction || grab(/execute\(\s*[^,)]+,\s*\{\s*['"]?action['"]?\s*:\s*['"]([^'"]+)/, p.js);
const args = { sitekey: key, url: p.url, ...ent };
if (v3) Object.assign(args, { version: 'v3' }, action ? { action } : {});
if (invisible) args.invisible = 1;
const kind = 'recaptcha_' + (v3 ? 'v3' : invisible ? 'v2_invisible' : 'v2');
return hit(kind + (ent.enterprise ? '_enterprise' : ''), 'recaptcha', args);
}हर नतीजा एक ऐसी कॉल से जुड़ता है जिसे आप पहले से जानते हैं। Checkbox यानी sitekey, पेज URL और एक खाली options object के साथ solver.recaptcha। Invisible invisible को 1 पर सेट करके जोड़ता है, Enterprise enterprise को 1 पर सेट करके जोड़ता है, और v3 version को v3 पर सेट करके और action जोड़ता है। action का नाम data-action attribute से या पेज की execute कॉल से आता है; अगर HTML में दोनों में से कोई न हो, तो option छोड़ दिया जाता है और CapSkip अपना default verify इस्तेमाल करता है। इसके हर variant की जानकारी reCAPTCHA सॉल्वर पेज पर है।
चरण 5: Turnstile और GeeTest
Turnstile विजेट को पेज पर दो चीज़ें चाहिए: Cloudflare की api.js और एक sitekey। key cf-turnstile class वाले div के data-sitekey attribute में होती है, या turnstile.render कॉल में। जाँच के लिए script का होना ज़रूरी है, इसलिए किसी असंबंधित पेज पर भटकी हुई class का नाम इसे चालू नहीं कर सकता। यह data-action और data-cdata भी मौजूद होने पर पढ़ती है और आगे पास कर देती है। options की सूची Turnstile सॉल्वर पेज पर मिलती है।
GeeTest v3 आपको दो मान देता है, और उनमें से एक जाल है। gt मान साइट के लिए तय रहता है और पेज में हो सकता है। challenge मान एक ही बार चलता है, और साइटें उसे run time पर एक register endpoint से लाती हैं। अगर पेज में दोनों हों, तो डिटेक्टर उन्हें लौटा देता है। अगर उसमें सिर्फ़ initGeetest कॉल हो, तो वह needsBrowser जवाब देता है, क्योंकि पुराने HTML में मिला challenge तब तक बेकार हो चुका होगा।
function turnstile(p) { // widget only: implicit (div.cf-turnstile) or explicit (turnstile.render)
if (!p.srcs.includes('challenges.cloudflare.com/turnstile/v0/api.js')) return null;
const t = tag('cf-turnstile', p.h), o = renderOpts('turnstile\\.render', p.js);
const sitekey = attr(t, 'data-sitekey') || grab(SITEKEY_JS, o);
if (!sitekey) return browser('turnstile', 'turnstile', p, 'no sitekey in the static HTML');
const action = attr(t, 'data-action') || grab(/\baction['"]?\s*:\s*['"]([^'"]+)/, o);
const data = attr(t, 'data-cdata') || grab(/\bcData['"]?\s*:\s*['"]([^'"]+)/, o);
return hit('turnstile', 'turnstile', { sitekey, url: p.url, ...(action && { action }), ...(data && { data }) });
}
function geetest(p) { // v3 only: gt is static, challenge comes from the register endpoint
if (!(/initGeetest\s*\(/.test(p.js) || /\/gt(\.[\d.]+)?\.js/.test(p.srcs))) return null;
const gt = grab(/\bgt['"]?\s*:\s*['"]([0-9a-f]{32})/, p.js);
const challenge = grab(/\bchallenge['"]?\s*:\s*['"]([0-9a-f]{32,34})/, p.js);
if (!(gt && challenge)) return browser('geetest', 'geetest', p, 'gt and challenge come from the register endpoint');
return hit('geetest', 'geetest', { gt, challenge, url: p.url });
}चरण 6: ALTCHA और Capy
ALTCHA ने अपनी पीढ़ियों के बीच attribute के नाम बदले। वर्ज़न 1 और 2 challengeurl इस्तेमाल करते हैं, या inline challenge के लिए challengejson। वर्ज़न 3 और बाद के एक ही attribute challenge इस्तेमाल करते हैं, जो URL या challenge data, दोनों में से कुछ भी ले सकता है। जाँच तीनों पढ़ती है और relative URL को पेज के पते से जोड़ देती है। Node.js में options के नाम challengeUrl और challengeJson हैं।
इस जाँच के लिए Capy को विजेट div नहीं चाहिए। उसकी key PUZZLE_ से शुरू होती है और विजेट script के पते के k parameter में रहती है, और default के अलावा कोई host apiServer के रूप में पास किया जाता है।
function altcha(p) { // challengeurl / challengejson (v1, v2) or challenge (v3+)
const t = grab(/(<altcha-widget\b[^>]*>)/, p.h);
if (!t) return null;
const c = attr(t, 'challenge');
const asJson = attr(t, 'challengejson') || ((c || '').trimStart().startsWith('{') ? c : null);
const asUrl = attr(t, 'challengeurl') || (asJson ? null : c);
if (asJson) return hit('altcha', 'altcha', { url: p.url, challengeJson: asJson });
if (asUrl) return hit('altcha', 'altcha', { url: p.url, challengeUrl: new URL(asUrl, p.url).href });
return browser('altcha', 'altcha', p, 'no challenge attribute');
}
function capy(p) { // the widget script is .../puzzle/get_js/?k=PUZZLE_...
const m = /((?:https?:)?\/\/[^\s"']+?)\/puzzle\/get_js\/\S*?(PUZZLE_[\w-]+)/.exec(p.srcs);
if (!m) return null;
const host = m[1].startsWith('http') ? m[1] : 'https:' + m[1];
const more = host === 'https://jp.api.capy.me' ? {} : { apiServer: host };
return hit('capy', 'capy', { sitekey: m[2], url: p.url, ...more });
}चरण 7: CaptchaFox और Friendly Captcha
CaptchaFox दो build में आता है। सादा वाला cdn.captchafox.com से लोड होता है। दूसरा किसी होस्टिंग प्लेटफ़ॉर्म द्वारा साझा CDN पाथ के नीचे bundle किया जाता है, इसलिए डिटेक्टर उस पाथ में CaptchaFox पैकेज का नाम खोजता है, अकेले पाथ को नहीं, जिसे दूसरे विजेट भी इस्तेमाल करते हैं।
Friendly Captcha सबसे पेचीदा है। वर्ज़न 1 और वर्ज़न 2 एक ही div और एक ही sitekey namespace इस्तेमाल करते हैं, लेकिन protocol अलग हैं, और गलत वर्ज़न का टोकन साइट ठुकरा सकती है। script के नाम दोनों को अलग बताते हैं: वर्ज़न 2 @friendlycaptcha/sdk पैकेज लोड करता है, वर्ज़न 1 friendly-challenge। जब पेज कोई संकेत न दे, तो डिटेक्टर अनुमान लगाने से मना कर देता है और needsBrowser कहता है।
const MAM = 'mampkg/@mamdev/core.frontend.libs.captchafox';
function captchafox(p) { // plain build from cdn.captchafox.com, or the bundled build from s.uicdn.com
const t = tag('captchafox', p.h), key = grab(/captchafox[^"']*\/api\.js\?key=([^"'&]+)/, p.srcs);
if (!t && !key) return null;
const more = p.srcs.includes(MAM) ? { apiServer: 'https://s.uicdn.com/' + MAM + '/' } : {};
const sitekey = attr(t, 'data-sitekey') || key;
if (!sitekey) return browser('captchafox', 'captchafox', p, 'no sitekey in the static HTML');
return hit('captchafox', 'captchafox', { sitekey, url: p.url, ...more });
}
function friendly(p) { // v2 loads @friendlycaptcha/sdk (site.min.js), v1 loads friendly-challenge
const t = tag('frc-captcha', p.h);
if (!t) return null;
const v2End = attr(t, 'data-api-endpoint'), v1End = attr(t, 'data-puzzle-endpoint');
const v2 = v2End !== null || /friendlycaptcha\/sdk|site(\.compat)?\.min\.js/.test(p.srcs);
const v1 = v1End !== null || /friendly-challenge|widget(\.module)?\.min\.js/.test(p.srcs);
if (v2 === v1) return browser('friendly_captcha', 'friendlyCaptcha', p, 'cannot tell v1 from v2 in the HTML');
const server = v2End || v1End;
if (!attr(t, 'data-sitekey')) return browser('friendly_captcha', 'friendlyCaptcha', p, 'no sitekey in the static HTML');
return hit('friendly_captcha_' + (v2 ? 'v2' : 'v1'), 'friendlyCaptcha',
{ sitekey: attr(t, 'data-sitekey'), url: p.url, version: v2 ? 'v2' : 'v1', ...(server && { apiServer: server }) });
}इनमें से हर एक का अपना सॉल्वर पेज है, जिसमें उस 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 को संभावित मानें, पक्का नहीं।
वही ब्लॉक जाँचों को क्रम से सूचीबद्ध करता है और detect को परिभाषित करता है, जिसे चरण 9 के अंत में export किया जाता है। यही क्रम तर्क है: पहले unsupported vendors, फिर Cloudflare पेज, फिर hCaptcha, फिर बाकी हर परिवार, और इमेज का अनुमान तभी जब सूची को कुछ न मिला हो। detect फ़ंक्शन पहला नतीजा लौटाता है और जब पेज में एक से ज़्यादा परिवार हों, जैसे v3 script के साथ Turnstile बॉक्स, तो also सूची जोड़ देता है। सूची परिवारों के नाम बताती है, किसी दूसरी key वाला दूसरा reCAPTCHA विजेट नहीं।
function image(p) { // a guess: an <img> that says captcha, next to a text box that says captcha
const box = /<input\b(?![^>]*type\s*=\s*["']?(?:hidden|checkbox|submit))[^>]*(?:captcha|security.?code)/i;
const img = (p.h.match(/<img\b[^>]*>/gi) || []).find((i) => box.test(p.h) && /captcha/i.test(i) && attr(i, 'src'));
if (img) return hit('image', 'normal', { image: new URL(attr(img, 'src'), p.url).href });
}
const CHECKS = [unsupported, cfChallenge, hcaptcha, recaptcha, turnstile, geetest, altcha, capy, captchafox, friendly];
function detect(html, pageUrl) {
const p = parse(html, pageUrl);
const found = CHECKS.map((check) => check(p)).filter(Boolean);
const best = found[0] || image(p) || null; // the image guess comes last
if (found.length > 1) best.also = found.slice(1).map((r) => r.type);
return best;
}चरण 9: solve
आख़िरी ब्लॉक नतीजे को सही CapSkip method तक भेजता है। unsupported या needsBrowser चिह्नित किसी भी नतीजे को वह एक Error के साथ मना कर देता है। CapSkip methods अपने मुख्य मान position से लेते हैं और बाकी options object के रूप में, इसलिए एक छोटी table बताती है कि कौन सा मान कहाँ जाता है। Node.js options camelCase में हैं, एक अपवाद के साथ: अगर आप per-call polling interval जोड़ते हैं, तो नाम polling_interval है, क्योंकि per-call pollingInterval एक ValidationException फेंकता है। इमेज method कोई per-call option नहीं लेता, इसलिए उसके लिए pollingInterval को CapSkip constructor पर सेट करें। इमेज उन cookies के साथ डाउनलोड होती है जो आप पास करते हैं, क्योंकि fetch में cookie jar नहीं है और SDK का अपना URL डाउनलोड कोई cookie नहीं भेजता। वे cookies इमेज जिस भी host पर हो उसे जाती हैं, इसलिए अगर आपकी कैप्चा इमेज किसी दूसरे host पर है, तो cookie छोड़ दें।
SDK में अभी कोई hCaptcha कॉल नहीं है, इसलिए hCaptcha नतीजा इसके बजाय solveHcaptcha को जाता है। यह sitekey और पेज का पता method=hcaptcha के साथ in.php पर पोस्ट करता है, फिर token तैयार होने तक हर 5 सेकंड में res.php को poll करता है, जैसा कि API दस्तावेज़ों का hCaptcha सेक्शन बताता है। सॉल्वर का पता solve के आख़िरी argument में api के रूप में दें, और अगर API key validation चालू है तो अपनी API key को key के रूप में दें। नतीजे में token, code में होता है और जिस User-Agent से वह मिला, वह userAgent में।
const POSITIONAL = { normal: ['image'], recaptcha: ['sitekey', 'url'], turnstile: ['sitekey', 'url'],
geetest: ['gt', 'challenge', 'url'], altcha: ['url'], capy: ['sitekey', 'url'], captchafox: ['sitekey', 'url'],
friendlyCaptcha: ['sitekey', 'url'] };
async function solveHcaptcha(args, api, key) { // no SDK call yet: post to in.php, poll res.php
const form = { method: 'hcaptcha', sitekey: args.sitekey, pageurl: args.url, invisible: args.invisible || 0, json: 1 };
if (args.data) form.data = args.data;
if (key) form.key = key;
const job = await (await fetch(`${api}/in.php`, { method: 'POST', body: new URLSearchParams(form) })).json();
if (job.status !== 1) throw new Error(job.request);
for (let i = 0; i < 36; i++) { // a solve takes 10 to 30 seconds, so give up after 3 minutes
await new Promise((done) => setTimeout(done, 5000));
const q = new URLSearchParams({ action: 'get', id: job.request, json: 1, ...(key ? { key } : {}) });
const r = await (await fetch(`${api}/res.php?${q}`)).json();
if (r.request !== 'CAPCHA_NOT_READY') {
if (r.status !== 1) throw new Error(r.request);
return { code: r.request, userAgent: r.useragent };
}
}
throw new Error('hCaptcha was not solved within 3 minutes');
}
async function solve(info, solver, fetchOpts = {}, { api = 'http://127.0.0.1:8080', key } = {}) {
if (info.unsupported || info.needsBrowser) throw new Error(info.needsBrowser || `${info.type} is not supported`);
const args = { ...info.args };
if (info.method === 'hcaptcha') return solveHcaptcha(args, api, key);
if (info.method === 'normal' && !args.image.startsWith('data:')) { // fetch has no cookie jar: pass a Cookie header
const res = await fetch(args.image, fetchOpts);
if (!res.ok) throw new Error(`image download failed: HTTP ${res.status}`);
args.image = 'data:image/png;base64,' + Buffer.from(await res.arrayBuffer()).toString('base64');
}
const names = POSITIONAL[info.method];
const options = Object.fromEntries(Object.entries(args).filter(([k]) => !names.includes(k)));
return solver[info.method](...names.map((k) => args[k]), options);
}
module.exports = { detect, solve };Node.js में कैप्चा डिटेक्टर चलाएँ
इसे detect.js के बगल में use.js के रूप में सेव करें। यह आपका पेज लाता है, डिटेक्टर को जो मिला वह प्रिंट करता है, और CapSkip को तभी कॉल करता है जब कॉल करने के लिए कोई method हो और कुछ छूटा न हो। यह ब्राउज़र जैसा User-Agent भेजता है, क्योंकि कई साइटें Node.js का default वाला ठुकरा देती हैं। जब पेज 200 से जवाब न दे तो यह HTTP status प्रिंट करता है और आगे बढ़ता है, क्योंकि Cloudflare challenge पेज 403 या 503 के रूप में आता है। इसे पेज का पता quotes में रखकर चलाएँ, क्योंकि ampersand वाला पता PowerShell में parse error है। पोर्ट 8080 default है; अगर आपका अलग हो तो CAPSKIP_PORT एनवायरनमेंट वेरिएबल सेट करें, जिसे यह script खुद पढ़ती है। इस script में host 127.0.0.1 ही रहता है, इसलिए Server मोड के लिए host वाली लाइन बदलें।
$env:CAPSKIP_PORT = "9090" # only if your port is not 8080 node use.js "https://example.com/login"
ध्यान दें कि fetch cookies नहीं रखता, और यह script Set-Cookie सिर्फ़ आख़िरी response से पढ़ती है, इसलिए redirect पर सेट हुई cookie खो जाती है। उसे रखने के लिए fetch को redirect option manual पर सेट करके कॉल करें, redirect के जवाब से Set-Cookie पढ़ें, और Location का पता खुद माँगें। script खुद नीचे है।
'use strict';
const { CapSkip } = require('capskip');
const { detect, solve } = require('./detect');
async function main(pageUrl) {
const res = await fetch(pageUrl, { headers: { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/130.0 Safari/537.36' } });
if (!res.ok) console.error(`page answered HTTP ${res.status}`); // a challenge page is a 403 or 503, so keep going
const html = await res.text();
const info = detect(html, res.url); // res.url is the final address after any redirect
console.log(JSON.stringify(info, null, 2));
if (!info || info.unsupported || info.needsBrowser) return;
const cookie = res.headers.getSetCookie().map((c) => c.split(';')[0]).join('; ');
const host = '127.0.0.1', port = Number(process.env.CAPSKIP_PORT || 8080);
const solver = new CapSkip({ host, port });
console.log(await solve(info, solver, cookie ? { headers: { cookie } } : {}, { api: `http://${host}:${port}` }));
}
main(process.argv[2]).catch((err) => {
console.error(`${err.constructor.name}: ${err.message}`);
process.exit(1);
});reCAPTCHA v2 checkbox वाले पेज पर पहला print प्रकार, method और arguments दिखाता है, और दूसरा नतीजा दिखाता है। reCAPTCHA के लिए टोकन code फ़ील्ड में है; बाकी methods अलग फ़ील्ड इस्तेमाल करते हैं, जैसे Turnstile और hCaptcha टोकन के बगल में userAgent। reCAPTCHA टोकन फ़ॉर्म के g-recaptcha-response फ़ील्ड में जाता है, और hCaptcha टोकन h-captcha-response में। 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, needsBrowser सेट |
| v3 script के साथ Turnstile विजेट | type recaptcha_v3, also सूची में turnstile |
जब HTML कुछ नहीं बताता: ब्राउज़र कंसोल
कुछ पेज लोड होने के बाद JavaScript से अपना विजेट जोड़ते हैं। तब सादा fetch को कैप्चा मिलता ही नहीं, और डिटेक्टर ऐसे पेज के लिए भी null लौटाता है जिस पर कैप्चा है। दो मामले जाने-पहचाने और अपेक्षित हैं: कोड से जोड़ी गई 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 से जोड़ी गई | null | पेज को ब्राउज़र में लोड करें और console snippet चलाएँ |
| sitekey JavaScript से सेट की गई | needsBrowser | key लाइव पेज से पढ़ें |
| Cloudflare की पूरे पेज वाली जाँच | needsBrowser | मान ब्राउज़र में पढ़ें, cf_clearance गाइड देखें। पेज 403 या 503 के रूप में आता है, जिसे use.js रिपोर्ट करता है और फिर भी जाँचता है |
| HTML में विजेट के बिना CaptchaFox, Friendly Captcha या Capy की script | null | console snippet चलाएँ या पेज का source पढ़ें |
| विजेट के बिना ब्लॉक पेज (403, 503) | null | use.js पहले status प्रिंट करता है: ब्लॉक पेज में खोजने के लिए कोई कैप्चा नहीं होता |
| Arkose, GeeTest v4 | unsupported | CapSkip इन्हें हल नहीं कर सकता |
| rqdata सेट करने वाला hCaptcha Enterprise | needsBrowser | हर task के लिए live पेज से ताज़ा rqdata पढ़ें और उसे data के रूप में solveHcaptcha को दें |
| script के संकेत के बिना Friendly Captcha | needsBrowser | देखें कि पेज कौन सी script लोड करता है |
| captcha शब्द के बिना इमेज कैप्चा | null | उस साइट के लिए अपना नियम जोड़ें |
आम errors
| त्रुटि | कारण | फिक्स |
|---|---|---|
| ERROR_GOOGLEKEY | key शब्द explicit थी, या ऐसी key थी जो reCAPTCHA key नहीं है | key विजेट से पढ़ें, render parameter से नहीं |
| ERROR_CAPTCHA_UNSOLVABLE | फेल जॉब ऐसी ही दिखती है और जवाब कारण का नाम नहीं बताता। गलत reCAPTCHA वर्ज़न यहाँ खत्म हो सकता है, पर मैंने असली ऐप पर इसकी पुष्टि नहीं की | पहले पहचाना गया प्रकार जाँचें, फिर यह देखें: API दस्तावेज़ |
| ValidationException | ऐसा option जो method नहीं लेता, जैसे v2 कॉल पर action, हर कॉल पर pollingInterval, या इमेज method पर हर कॉल का कोई भी option | डिटेक्टर ने जो arguments लौटाए, उन्हें इस्तेमाल करें |
| NetworkException | CapSkip चल नहीं रहा, या host या port गलत है | CapSkip शुरू करें और कनेक्शन सेटिंग्स जाँचें, फिर यह देखें: |
| solve से error | नतीजा unsupported है या ब्राउज़र चाहिए, या CapSkip ने hCaptcha जॉब ठुकरा दी और संदेश उसका error code है | solve को कॉल करने से पहले पहले दो मामलों को संभालें, और तीसरे के लिए sitekey और पेज का पता जाँचें |
सॉल्वर को किसी server पर चलाना
डिटेक्टर को CapSkip से सिर्फ़ दो चीज़ें चाहिए: एक host और एक पोर्ट। Local मोड में वह 127.0.0.1 है और आपका अपना PC। Server मोड में CapSkip आपके नेटवर्क पते या पब्लिक IP पर सुनता है, इसलिए किसी दूसरे PC, VPS या होस्टेड प्लेटफ़ॉर्म पर चल रहा डिटेक्टर उसी API से उसे कॉल कर सकता है। use.js में host और पोर्ट बदल दें, कोड में और कुछ नहीं बदलता। स्थिर रिमोट एक्सेस के लिए 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 अनुमान की जगह needsBrowser क्यों लौटाता है?
क्योंकि गलत अनुमान पर एक पूरा हल बेकार जाता है और ऐसा टोकन मिलता है जिसे साइट बिना किसी संकेत के ठुकरा देती है। जब static HTML में sitekey, GeeTest challenge या Friendly Captcha वर्ज़न न हो, तो यह बता देना फेल हुई जॉब से तेज़ है।
क्या CapSkip hCaptcha हल करता है?
हाँ। CapSkip अपने HTTP API के ज़रिए method=hcaptcha के साथ hCaptcha के checkbox, invisible और Enterprise विजेट हल करता है, जैसा कि API दस्तावेज़ बताते हैं। Node.js SDK में अभी कोई hCaptcha कॉल नहीं है, इसलिए ऊपर वाला solveHcaptcha जॉब को खुद in.php पर पोस्ट करता है। FunCaptcha, Arkose और GeeTest v4 अब भी सपोर्टेड नहीं हैं, और डिटेक्टर इन्हें unsupported बताता है ताकि script उस पेज पर फेल होने के बजाय उसे छोड़ सके।
क्या कोई होस्टेड प्लेटफ़ॉर्म मेरे CapSkip के साथ डिटेक्टर इस्तेमाल कर सकता है?
हाँ, Server मोड के ज़रिए। CapSkip को अपने नेटवर्क या पब्लिक IP पर सुनने के लिए सेट करें, use.js में host और पोर्ट को उसी पते पर सेट करें, और डिटेक्टर तथा solve कॉल वैसे ही काम करते हैं जैसे एक PC पर। सॉल्वर आपके हार्डवेयर पर ही रहता है।
संक्षेप में
Node.js में कैप्चा डिटेक्टर इसलिए अपनी जगह बनाता है कि वह पहले चलता है: कैप्चा का प्रकार पहचानें, फिर हल करें: script और markup को तय क्रम में पढ़ें, arguments सही method को दें, और null, needsBrowser तथा unsupported को जवाब मानें, error नहीं। अपनी मशीन पर CapSkip के साथ किसी पेज को जाँचते समय कोई मीटर नहीं चल रहा होता, और इसी वजह से लोकल captcha सॉल्वर उस crawl के लिए व्यावहारिक बनता है जो हर कुछ मिनट में एक नई साइट से मिलता है। Node.js SDK के बारे में खुद जानना हो, तो यह पेज देखें: Node.js कैप्चा सॉल्वर पेज.
