Apify Actor (Node.js) के अंदर कैप्चा कैसे हल करें

apify captcha - How to Solve CAPTCHA Inside an Apify Actor (Node.js)

Apify में कैप्चा वाला step वही आम तीन चालें हैं, और एक चाल इस platform की अपनी है। sitekey पढ़ें, उसे हल करें, token को form के साथ वापस post करें। अतिरिक्त चाल यह तय करना है कि सॉल्वर कहाँ रहेगा, क्योंकि Actor आपके laptop पर नहीं चलता। वह Apify के infrastructure पर एक container में चलता है, इसलिए उसके अंदर का loopback पता उसी container का है और किसी का नहीं। यह एक फ़ैसला सही कर लें, बाकी बीस लाइनों का काम है।

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

  • Node.js 18 या उससे नया, Apify CLI, और एक Apify खाता।
  • Actor में apify और capskip packages इंस्टॉल किए हुए।
  • CapSkip किसी ऐसी मशीन पर Server mode में चल रहा हो जहाँ Actor पहुँच सके, उसका static public IP हो और key validation चालू हो। जब तक आप अपनी मशीन पर develop कर रहे हैं तब तक Local mode भी काम करता है। दोनों modes की जानकारी यहाँ है: कनेक्शन सेटिंग्स.
# Scaffold an Actor, then add the solver client.
apify create captcha-actor -t getting_started_node
cd captcha-actor
npm install capskip

यहाँ Server mode वैकल्पिक नहीं है

लोग यहीं सबसे ज़्यादा उलझते हैं, इसलिए यह बात पहले। Actors, Apify के workers पर चलते हैं। जब आपका Actor कोड 127.0.0.1:8080 से connection खोलता है तो वह अपने ही container से बात कर रहा होता है, जिसमें कोई सॉल्वर चल ही नहीं रहा, और call एक ऐसी connection error के साथ फेल होती है जो लगती है मानो सॉल्वर crash हो गया हो। कुछ भी crash नहीं हुआ। पता बस ग़लत मशीन के लिए local था।

CapSkip में ठीक इसी वजह से दो connection modes हैं। Local, 127.0.0.1 से bind होता है और सिर्फ़ उसी डिवाइस को जवाब देता है। Server, आपके नेटवर्क या public IP से bind होता है, जिससे कोई दूसरी मशीन, कोई VPS या Apify जैसा कोई hosted platform उसी सॉल्वर को API के ज़रिए कॉल कर सकता है। एक static public IP उस पते को हर run के बीच स्थिर रखता है।

साफ़ कह देना ज़रूरी है, क्योंकि यह सवाल उठता है: Server mode CapSkip को कोई metered cloud सेवा नहीं बना देता। यह तब भी आपकी अपनी मशीन है और तब भी असीमित। सिर्फ़ इतना बदलता है कि वह किस interface पर सुनता है। जैसे ही वह किसी नेटवर्क पते पर सुनने लगे, key validation चालू करें और Actor को उसकी अपनी key दें, ताकि उस key को बाकी किसी चीज़ को छेड़े बिना रद्द किया जा सके।

Step 1: सॉल्वर का पता एक secret input के रूप में declare करें

host को hardcode न करें। Apify का input schema encrypted fields को सहारा देता है, और सॉल्वर के पते तथा उसकी key का सही घर यही है, क्योंकि इससे values हर run पर सेट होती हैं, build में पकी हुई नहीं आतीं। Encryption, textfield, textarea और hidden editors के साथ काम करता है।

{
  "title": "CAPTCHA actor input",
  "type": "object",
  "schemaVersion": 1,
  "properties": {
    "targetUrl": {
      "title": "Target URL",
      "type": "string",
      "editor": "textfield"
    },
    "solverHost": {
      "title": "Solver host",
      "type": "string",
      "editor": "textfield",
      "isSecret": true
    },
    "solverKey": {
      "title": "Solver API key",
      "type": "string",
      "editor": "textfield",
      "isSecret": true
    }
  },
  "required": ["targetUrl", "solverHost"]
}

वह फ़ाइल .actor फ़ोल्डर में actor.json के बग़ल में जाती है, और values आपके कोड तक input object के ज़रिए पहुँचती हैं।

Step 2: host को runtime पर चुनें

आप एक ऐसा Actor चाहते हैं जो दोनों जगह काम करे: लोकल रूप से चलाते समय 127.0.0.1 से बात करे, और deploy होने के बाद आपके सर्वर से। SDK ठीक इसी काम के लिए एक boolean देता है। Actor.isAtHome() तब true लौटाता है जब कोड Apify platform पर चल रहा हो, और तब false जब न चल रहा हो।

// npm install apify capskip
import { Actor } from 'apify';
import { CapSkip } from 'capskip';

await Actor.init();
const input = await Actor.getInput();

// Local run talks to the loopback address. A platform run
// talks to the server address that came in as a secret.
const solver = new CapSkip({
  host: Actor.isAtHome() ? input.solverHost : '127.0.0.1',
  port: 8080,
  apiKey: input.solverKey,
});

Actor में बाकी किसी को इस फ़र्क़ का पता होने की ज़रूरत नहीं। वही एक code path दोनों सँभाल लेता है, और नाकाम deploy का मतलब अब constants बदलना नहीं रहता।

Step 3: sitekey पढ़ें, उसे हल करें, token post करें

sitekey host document पर data-sitekey attribute के रूप में बैठी होती है, इसलिए एक सादा fetch और एक regular expression उसे बिना ब्राउज़र शुरू किए निकाल लेते हैं। किसी paid platform पर यह मायने रखता है: बिना ब्राउज़र वाले Actor को कहीं कम memory चाहिए, और Apify का बिल memory गुणा समय पर बनता है। नीचे दिया गया Actor.fail() run को ख़त्म कर देता है, return नहीं करता, इसीलिए उसके बाद का कोड यह मान सकता है कि match सफल रहा था।

// Fetch the form page and lift the sitekey out of it.
const html = await (await fetch(input.targetUrl)).text();
const match = html.match(/data-sitekey="([^"]+)"/);

if (!match) {
  await Actor.fail('No sitekey on the page. Did the widget render?');
}

const result = await solver.recaptcha(match[1], input.targetUrl);
const token = result.code;   // the g-recaptcha-response value

फिर form को उस field में token डालकर submit करें जिसकी साइट को उम्मीद है। reCAPTCHA v2 में छिपे textarea का नाम g-recaptcha-response होता है, और ज़्यादातर forms उसे उसी नाम से post करते हैं। अगर पेज token किसी callback को सौंप देता है, तो जाँच लें कि वह callback असल में submit क्या करता है।

// The token travels as an ordinary form field.
const body = new URLSearchParams({
  email: 'someone@example.com',
  'g-recaptcha-response': token,
});

const posted = await fetch(input.targetUrl, { method: 'POST', body });
await Actor.pushData({ url: input.targetUrl, status: posted.status });

Turnstile और GeeTest के अपने methods उसी client पर हैं, और दोनों का आकार एक जैसा है। Turnstile एक user agent भी लौटाता है जिसे challenge पेजों पर token के साथ भेजना पड़ता है। पूरी parameter सूचियाँ यहाँ हैं: CapSkip API डॉक्यूमेंटेशन.

पूरा चलने वाला उदाहरण

पूरा Actor, src/main.js के रूप में। Actor एक ES module है जिसमें top level await चलता है, इसलिए कोई wrapper function नहीं है, और आख़िर में Actor.exit() ही वह चीज़ है जो dataset को flush करती है और run को साफ़ ढंग से बंद करती है।

// npm install apify capskip
import { Actor } from 'apify';
import { CapSkip, NetworkException, TimeoutException } from 'capskip';

await Actor.init();

const input = await Actor.getInput();
const solver = new CapSkip({
  host: Actor.isAtHome() ? input.solverHost : '127.0.0.1',
  port: 8080,
  apiKey: input.solverKey,
});

try {
  const html = await (await fetch(input.targetUrl)).text();
  const match = html.match(/data-sitekey="([^"]+)"/);
  if (!match) throw new Error('No sitekey found on the page.');

  const result = await solver.recaptcha(match[1], input.targetUrl);

  const body = new URLSearchParams({
    'g-recaptcha-response': result.code,
  });
  const posted = await fetch(input.targetUrl, { method: 'POST', body });

  await Actor.pushData({ url: input.targetUrl, status: posted.status });
} catch (err) {
  if (err instanceof NetworkException) {
    await Actor.fail('Cannot reach the solver. Check Server mode and the host.');
  }
  if (err instanceof TimeoutException) {
    await Actor.fail('The solve outlasted recaptchaTimeout.');
  }
  throw err;
}

await Actor.exit();

connection जैसी दिखने वाली दोनों exceptions को अलग अलग पकड़ना उन छह लाइनों के लायक है। Actor.fail(), दरअसल exit code 1 और एक संदेश के साथ Actor.exit() ही है, इसलिए run FAILED के रूप में ख़त्म होता है और log में एक वाक्य बता देता है कि setup का कौन सा आधा हिस्सा टूटा। इसके बिना, एक ऐसा सॉल्वर जो बस पहुँच से बाहर है और एक ऐसा solve जो सचमुच पढ़ा ही नहीं जा सका, दोनों एक जैसा लाल run देते हैं।

Deploy से पहले उसे लोकल रूप से चलाना

Actor को पहले अपनी मशीन पर चलाएँ, सॉल्वर को Local mode में रखकर। वहाँ isAtHome() false लौटाता है, इसलिए कोड बिना आपके कुछ बदले 127.0.0.1 की तरफ़ जाता है, और network वाला पड़ाव जोड़ने से पहले आप sitekey पढ़ना और form post करना साबित कर लेते हैं।

# Reads INPUT from storage/key_value_stores/default.
apify run

जब वह पास हो जाए, तो CapSkip को Server mode में बदलें, वह पता नोट करें जिस पर वह अब सुन रहा है, और उसी पते को platform पर solverHost input में डाल दें। बदली सिर्फ़ एक string है।

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

आप जो देखते हैंकारणफिक्स
platform पर NetworkException, लोकल पर कभी नहींActor ने 127.0.0.1 को कॉल किया और अपने ही container तक पहुँच गयासॉल्वर को Server mode में करें और उसका पता पास करें
ERROR_WRONG_USER_KEYkey validation चालू है और Actor ने ग़लत key भेजीkey को secret input के रूप में सेट करें और उसे input से पढ़ें
ERROR_GOOGLEKEYregular expression किसी से मेल नहीं खाया और एक ख़ाली key बाहर चली गईउस पर एक solve ख़र्च करने से पहले match जाँच लें
Run status TIMED-OUTrun का timeout, fetch और solve और post के जोड़ से छोटा हैActor के default run options में timeout बढ़ाएँ
TimeoutExceptionsolve recaptchaTimeout से ज़्यादा लंबा चलाउसे डिफ़ॉल्ट 300 सेकंड से ऊपर बढ़ाएँ
form ऐसे token को अस्वीकार करता है जो ठीक हल हुआ थाsolve और post के बीच token पुराना पड़ गयाpost करने से ठीक पहले हल करें, run की शुरुआत में नहीं

FAQ

क्या कोई Apify Actor सचमुच मेरी अपनी मशीन पर चल रहे सॉल्वर तक पहुँच सकता है?

हाँ, उसी API के ज़रिए जिससे वह किसी भी internal service तक पहुँचता। Server mode CapSkip को loopback पते के बजाय आपके नेटवर्क या public IP पर सुनने लगाता है, इसलिए Actor उसे किसी भी दूसरे HTTP endpoint की तरह कॉल करता है। static public IP की सलाह इसलिए दी जाती है कि पता हर run के बीच बदले नहीं, और port खोलने से पहले key validation चालू होनी चाहिए।

क्या यह Apify पर किसी Crawlee crawler के अंदर काम करता है?

करता है, और तीनों चालें एक जैसी ही रहती हैं। फ़र्क़ यह है कि वे बैठती कहाँ हैं: sitekey पढ़ना और token inject करना request handler के अंदर जाते हैं, और सॉल्वर client एक बार उसके बाहर बनाया जाता है ताकि हर request एक ही instance साझा करे। crawler से जुड़ी बारीकियाँ, जिनमें यह भी शामिल है कि आपको solve को अपने retry loop में क्यों नहीं लपेटना चाहिए, यहाँ कवर की गई हैं: Crawlee कैप्चा गाइड.

क्या मुझे Apify Proxy के ज़रिए हल करना चाहिए?

सिर्फ़ तब जब साइट उस IP को score करती हो जिसने हल किया। प्रॉक्सी का सहारा reCAPTCHA, Turnstile और GeeTest के लिए मौजूद है, और वह तब मायने रखता है जब token की जाँच उसी पते के ख़िलाफ़ होती है जिसने उसे माँगा था। पूरे सॉल्वर को प्रॉक्सी से गुज़ारने के बजाय प्रॉक्सी को solve वाली call पर भेजें, ताकि जब आप चाहें तो fetch और solve अलग अलग exits इस्तेमाल कर सकें। इसके tradeoffs इस गाइड में बताए गए हैं: कैप्चा प्रॉक्सी रोटेशन.

Actor को कितनी memory देनी चाहिए?

आप जितना सोचते हैं उससे कम, बशर्ते आप ब्राउज़र छोड़ दें। ऊपर वाला Actor HTML fetch करता है, एक network call का इंतज़ार करता है और एक form post करता है, यानी वह अपनी ज़्यादातर ज़िंदगी ख़ाली बैठा रहता है और उसे Chromium जैसी कोई भारी footprint नहीं चाहिए। ब्राउज़र की तरफ़ हाथ तभी बढ़ाएँ जब पेज JavaScript चलाए बिना अपनी sitekey देने को तैयार न हो। हल करने का काम दोनों ही हालत में सॉल्वर वाली मशीन पर होता है, हालाँकि उस इंतज़ार के दौरान आपका Actor भी चलता रहता है।

संक्षेप में

सॉल्वर को Server mode में रखें, उसका पता और key encrypted inputs के रूप में रखें, और Actor.isAtHome() को उस पते और 127.0.0.1 में से चुनने दें। फिर sitekey पढ़ें, उसे हल करें, और token को g-recaptcha-response के रूप में post करें। Puppeteer और Playwright समेत Node की व्यापक तस्वीर के लिए देखें: Node.js कैप्चा सॉल्वर पेज। इसी समस्या का crawling वाला पहलू यहाँ कवर किया गया है: वेब स्क्रैपिंग पेज.

Actor को बड़ा करने से पहले एक नतीजा साफ़ कह देना ठीक रहेगा। चूँकि यह कैप्चा बायपास उस हार्डवेयर पर चलता है जो पहले से आपका है, इसलिए Actor को दस runs से एक हज़ार runs तक बढ़ाने पर आपका Apify बिल बढ़ता है और आपका solving बिल जहाँ था वहीं रहता है।