Node.js SDK के साथ Crawlee में कैप्चा कैसे संभालें

crawlee captcha - How to Handle CAPTCHA in Crawlee with the Node.js SDK

Crawlee में कोई CAPTCHA hook नहीं है, और उसकी ज़रूरत भी नहीं है। Crawlee का कैप्चा आपके अपने ही handler के अंदर हल होता है: requestHandler, उसी request के बीच में जिसके लिए आपके पास पहले से एक browser page है। तीन चीज़ें इसे काम लायक बनाती हैं: solve खर्च करने से पहले widget को पहचानें, handler timeout बढ़ाएँ क्योंकि डिफ़ॉल्ट एक reCAPTCHA solve से भी छोटा है, और विफलता पर throw करें ताकि Crawlee आपके loop के बजाय अपनी queue के ज़रिए request दोबारा आज़माए। यह गाइड तीनों चीज़ें PlaywrightCrawler पर दिखाती है।

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

  • Node.js 18 या उससे नया, और एक Crawlee project जो पहले से कुछ crawl कर रहा हो
  • CapSkip चालू और पहुँच में हो। Local मोड उसी मशीन पर चल रहे ऑटोमेशन के लिए 127.0.0.1 port 8080 पर सुनता है, और Server मोड आपके network या public IP पर सुनता है ताकि किसी दूसरे box, VPS या container host पर चल रहा crawler उसे कॉल कर सके। दोनों मोड यहाँ बताए गए हैं: कनेक्शन सेटिंग्स
  • तीनों packages, एक साथ इंस्टॉल किए गए
# One install for the crawler, the browser and the solver client.
npm install crawlee playwright capskip

# Crawlee drives a real browser, so fetch one.
npx playwright install chromium

यहाँ दिए गए sample CommonJS में हैं, वही रूप जो CapSkip README दर्ज करता है। Crawlee 3 दोनों builds भेजता है, इसलिए कोई ESM project crawler के लिए import statements भी इस्तेमाल कर सकता है।

हल कहाँ होता है: requestHandler के अंदर

Scrapy में downloader middleware है और Selenium में वह wrapper जो आपने खुद बनाया है। Crawlee आपको page object सीधे देता है, इसलिए कोई interception परत लिखने की ज़रूरत ही नहीं। आप चुनौती पहचानते हैं, उसे हल करते हैं, और उसी function में आगे बढ़ जाते हैं।

पहले पहचानें। हर पेज पर solve चलाना उन पेजों पर क्षमता जलाता है जिन्हें कभी चुनौती दी ही नहीं गई, और इससे यह काम का संकेत भी छिप जाता है कि आप असल में कितनी बार ब्लॉक होते हैं।

// npm install crawlee playwright capskip
const { PlaywrightCrawler } = require('crawlee');
const { CapSkip } = require('capskip');

// Local mode. Point host at a server IP to share one solver.
const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });

async function solveIfChallenged(page, url, log) {
  const widget = page.locator('[data-sitekey]').first();
  if ((await widget.count()) === 0) return false;

  const sitekey = await widget.getAttribute('data-sitekey');
  log.info(`Solving sitekey ${sitekey}`);
  const result = await solver.recaptcha(sitekey, url);
  return result.code;   // the token
}

data-sitekey attribute reCAPTCHA v2 के लिए widget div पर होता है और Turnstile div पर भी, इसीलिए एक ही selector दोनों को कवर कर लेता है। reCAPTCHA v3 का कोई दिखने वाला widget नहीं होता, इसलिए वहाँ आप key को script URL में से पढ़ते हैं।

token डालें, फिर सबमिट करें

हल करने पर आपको एक token मिलता है। पेज अब भी उस token की उम्मीद उसी hidden field में करता है जिसे उसका अपना widget भरता, इसलिए उसे वहीं रखें और form को वैसे ही सबमिट करें जैसे कोई browser करता।

// The widget writes into a hidden textarea. Do the same.
await page.evaluate((token) => {
  const field = document.getElementById('g-recaptcha-response');
  field.value = token;
}, token);

// Then submit exactly as the page would, and wait for the result.
await Promise.all([
  page.waitForNavigation(),
  page.click('button[type=submit]'),
]);

कुछ पेज form पोस्ट करने के बजाय एक JavaScript callback कॉल करते हैं। अगर widget div पर कोई data-callback attribute है, तो कुछ भी क्लिक करने के बजाय उस function को token के साथ चलाएँ, क्योंकि हो सकता है click handler कभी चले ही नहीं।

सबसे पहले requestHandlerTimeoutSecs बढ़ाएँ

यही वह चीज़ है जो लोगों को फँसाती है, और लगता ऐसा है जैसे सॉल्वर की समस्या हो, जबकि होती नहीं।

PlaywrightCrawler हर request handler को 60 सेकंड डिफ़ॉल्ट रूप से देता है। एक reCAPTCHA v2 job पहले 15 से 20 सेकंड तक तैयार ही नहीं होता, v3 में 10 से 15 लगते हैं, और यह तब है जब आपने पेज लोड करने, token डालने और navigation का इंतज़ार करने का समय जोड़ा ही नहीं है। handler बीच solve में मारा जाता है, Crawlee एक timeout लॉग करता है, और request दोबारा queue पर चली जाती है ताकि यह सब फिर से हो।

// npm install crawlee playwright capskip
const crawler = new PlaywrightCrawler({
  // 60 is the default and it is shorter than a v2 solve plus a submit.
  requestHandlerTimeoutSecs: 180,

  // Three tries per URL, which is Crawlee's default and the right one.
  maxRequestRetries: 3,

  async requestHandler({ page, request, log }) {
    // your handler
  },
});

180 सेकंड एक समझदार ऊपरी सीमा है। यह एक सामान्य solve से लगभग दस गुना है, और जानबूझकर SDK की अपनी 300 सेकंड वाली reCAPTCHA polling सीमा से नीचे रहती है, ताकि सचमुच अटके request को Crawlee छोड़ दे, न कि वह पूरे पाँच मिनट तक एक browser slot रोके रखे। अगर आप चाहते हैं कि पहले solver client ही हार माने, तो recaptchaTimeout को अपने handler timeout से कम कर दें।

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

एक फ़ाइल, एक crawler, एक solve path। run कॉल में अपना start URL डाल दें।

// npm install crawlee playwright capskip
const { PlaywrightCrawler, Dataset } = require('crawlee');
const { CapSkip } = require('capskip');

const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });

const crawler = new PlaywrightCrawler({
  requestHandlerTimeoutSecs: 180,
  maxRequestRetries: 3,

  async requestHandler({ page, request, log }) {
    const widget = page.locator('[data-sitekey]').first();

    if ((await widget.count()) > 0) {
      const sitekey = await widget.getAttribute('data-sitekey');
      const result = await solver.recaptcha(sitekey, request.loadedUrl);

      await page.evaluate((token) => {
        document.getElementById('g-recaptcha-response').value = token;
      }, result.code);

      await Promise.all([
        page.waitForNavigation(),
        page.click('button[type=submit]'),
      ]);
      log.info(`Cleared the challenge on ${request.loadedUrl}`);
    }

    await Dataset.pushData({ url: request.loadedUrl, title: await page.title() });
  },
});

await crawler.run(['https://example.com/page-with-recaptcha']);

solve आपकी अपनी मशीन पर चलता है, इसलिए ऊपर वाले retry बजट की कीमत घड़ी के समय के अलावा कुछ नहीं है। मीटर वाली सेवा से यही व्यावहारिक फ़र्क़ है, जहाँ हर URL पर तीन कोशिशें खर्च की एक मद बन जाती हैं।

queue को दोबारा कोशिश करने दें, अपना loop न बनाएँ

मन यही करता है कि solve को एक for loop में लपेट दें। ऐसा न करें। Crawlee में पहले से एक retry सिस्टम है जो request queue, session pool और proxy configuration को जानता है, और handler के अंदर हाथ से बनाया गया loop इन तीनों के लिए अदृश्य रहता है।

इसके बजाय throw करें। जो handler throw करता है वह request को वापस queue पर भेज देता है, और Crawlee उसे ज़्यादा से ज़्यादा maxRequestRetries बार, हर बार एक नए browser context के साथ, दोबारा चलाता है।

// npm install capskip
const { ApiException, NetworkException, TimeoutException } = require('capskip');

const crawler = new PlaywrightCrawler({
  requestHandlerTimeoutSecs: 180,

  // Runs between retries, while attempts remain.
  errorHandler({ request, log }, error) {
    log.warning(`Retry ${request.retryCount} for ${request.url}: ${error.message}`);
  },

  // Runs once, after the last attempt fails.
  failedRequestHandler({ request, log }) {
    log.error(`Gave up on ${request.url}`);
  },
});

कौन सा exception निकला, यह बताता है कि बदलना क्या है। NetworkException का मतलब है CapSkip पहुँच में नहीं था, इसलिए साइट को दोष देने से पहले host और port जाँचें। TimeoutException का मतलब है polling window खत्म हो गई और पेज शायद आपकी सोच से कठिन चुनौती परोस रहा है। ApiException अपने साथ लौटाया गया error code लाता है, और यही वह है जिसे URL के साथ लॉग करना काम का है।

crawler और सॉल्वर को अलग-अलग मशीनों पर चलाना

Crawlee अपनी ही और नकलें चलाकर scale करता है, और अलग-अलग boxes पर बैठा crawl बेड़ा सब मिलकर 127.0.0.1 से बात नहीं कर सकता। जवाब Server मोड है: CapSkip loopback के बजाय आपके network या public IP पर सुनता है, और हर worker उसी पते की ओर इशारा करता है।

// npm install capskip
const { CapSkip } = require('capskip');

// Same client, different address. Nothing else in the code changes.
const solver = new CapSkip({
  host: process.env.CAPSKIP_HOST || '127.0.0.1',
  port: Number(process.env.CAPSKIP_PORT || 8080),
});

SDK खुद ही environment से CAPSKIP_HOST और CAPSKIP_PORT पढ़ लेता है, इसलिए ऊपर वाला fallback सिर्फ़ उस container के लिए अतिरिक्त सावधानी है जो इनके बिना शुरू होता है। सॉल्वर वाले box के लिए एक static public IP की सलाह दी जाती है, और setup के चरण यहाँ दिए गए हैं: कनेक्शन सेटिंग्स। यह अब भी आपका ही hardware है और अब भी बिना मीटर का, इसलिए बदला सिर्फ़ इतना है कि process कहाँ चलती है।

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

लक्षणकारणफिक्स
requestHandler 60s के बाद timeout हो गयाडिफ़ॉल्ट handler timeout एक solve से छोटा हैrequestHandlerTimeoutSecs को 180 पर सेट करें
solve सफल होता है, पेज फिर भी ब्लॉक करता हैtoken तो चला गया लेकिन form कभी सबमिट ही नहीं हुआdata-callback attribute है या नहीं यह देखें और उसे कॉल करें
ERROR_GOOGLEKEYsitekey attribute खाली था या ग़लत element से पढ़ा गयाहल करने से पहले उसका मान लॉग करें; v3 keys script URL में होती हैं
ERROR_PAGEURLhandler ने एक relative या redirect हुआ URL पास कियाrequest.loadedUrl इस्तेमाल करें, जो redirects के बाद वाला URL है
हर request पर NetworkExceptioncrawler सॉल्वर तक पहुँच ही नहीं पा रहाLocal मोड सिर्फ़ loopback है; किसी दूरस्थ worker के लिए Server मोड पर जाएँ
हर URL तीन बार दोबारा आज़माया गया, फिर छोड़ दिया गयाhandler solve से पहले ही throw कर देता हैerrorHandler का लॉग पढ़ें; पहली विफलता ही असली है

Parameter के नाम और पूरी error code सूची यहाँ है: API डॉक्युमेंटेशन.

FAQ

क्या यह CheerioCrawler के साथ काम करता है?

आंशिक रूप से। CheerioCrawler में कोई browser नहीं है, इसलिए न कोई page object है और न widget का अपना JavaScript चलाने का कोई तरीक़ा। आप फिर भी HTML में से sitekey निकाल सकते हैं, उसे हल कर सकते हैं, और token को form body के साथ खुद पोस्ट कर सकते हैं। यह एक सादे form submit के लिए काफ़ी है और किसी भी ऐसी चीज़ के लिए काफ़ी नहीं जो callback की उम्मीद करती हो। जहाँ चुनौती मिलने की संभावना हो वहाँ PlaywrightCrawler इस्तेमाल करें।

क्या मुझे इसके बजाय preNavigationHook में हल करना चाहिए?

नहीं। Pre-navigation hooks पेज लोड होने से पहले चलते हैं, इसलिए वहाँ पहचानने को कुछ होता ही नहीं। Post-navigation hooks ज़्यादा नज़दीक हैं, लेकिन request handler वही जगह है जहाँ आपके पास पहले से page, redirects के बाद वाला URL और logger मौजूद हैं। solve को वहीं रखें और hooks को cookies और headers के लिए रहने दें।

क्या crawler किसी hosted platform पर चल सकता है जबकि सॉल्वर घर पर ही रहे?

हाँ, CapSkip को Server मोड में रखकर। crawler को सॉल्वर के पते तक एक रास्ता चाहिए, इसलिए घरेलू कनेक्शन के लिए एक static public IP और एक खुला port चाहिए, और VPS आसान विकल्प है। client कोड दोनों हालात में एक जैसा रहता है: सिर्फ़ host का मान बदलता है।

एक crawl कितने समानांतर solves चला सकता है?

Crawlee अपनी concurrency खुद autoscale करता है, और हर handler अपने solve का अलग से इंतज़ार करता है, इसलिए client पर कोई queue कॉन्फ़िगर करने की ज़रूरत नहीं। SDK 250 मिलीसेकंड पर polling शुरू करता है और pollingInterval की ऊपरी सीमा तक धीमा होता जाता है, जिससे तेज़ solve तब भी तेज़ रहता है जब कई एक साथ चल रहे हों। अपनी Crawlee concurrency को सॉल्वर से नहीं, बल्कि इस हिसाब से मिलाएँ कि लक्ष्य साइट कितना बर्दाश्त करती है।

संक्षेप में

widget को पहचानें, request handler में हल करें, handler timeout को 180 सेकंड तक बढ़ाएँ, और throw करें ताकि queue दोबारा कोशिश करे। सॉल्वर को खुद चलाना ही तीन बार retry को खर्च का फ़ैसला नहीं, बल्कि एक वाजिब डिफ़ॉल्ट बनाता है, और यही बात एक local कैप्चा बायपास का किसी भी crawl में कहीं भी इस्तेमाल करने पर भी लागू होती है। Node.js इंटीग्रेशन गाइड client setup को कवर करती है, Playwright गाइड में वे browser-side विवरण हैं जो Crawlee को विरासत में मिलते हैं, और वेब स्क्रैपिंग के लिए कैप्चा हल करना पूरे crawl में session handling को कवर करती है। Python में यही पैटर्न देखने के लिए पढ़ें Scrapy middleware वाली पोस्ट.