Scrapy कैप्चा मिडलवेयर: अपने spider में कैप्चा हल करें (2026)
Scrapy रिक्वेस्ट और callbacks के इर्द-गिर्द बना है, इसलिए कैप्चा उसमें साफ़ तरीके से फिट नहीं बैठता। इसे संभालने का सही तरीका एक downloader middleware है: वह हर रिस्पॉन्स देखता है, चैलेंज पहचानता है, टोकन लेता है, और spider को उसी टोकन के साथ फॉर्म सबमिट करने देता है। यह गाइड PopularCaptcha पेजों के लिए ऐसा middleware बनाती है, जिसे Scarnox आज proxyless आधार पर हल करता है।
मुफ्त ट्रायल क्रेडिट के साथ शुरू करें, कार्ड की ज़रूरत नहीं और किसी और चीज़ पर इंगित करने से पहले कोड को एक टेस्ट पेज पर आज़माएं।
मिडलवेयर कैसे काम करता है
मिडलवेयर हर रिस्पॉन्स पर चलता है:
- अगर रिस्पॉन्स में
data-sitekeyनहीं है, तो वह रिस्पॉन्स आगे जाने देता है। - अगर है, तो मिडलवेयर कैप्चा हल करता है और उसी रिक्वेस्ट की एक कॉपी लौटाता है, जिसके
metaमें टोकन रखा होता है। - Scrapy वह रिक्वेस्ट दोबारा लाता है। मिडलवेयर
metaमें टोकन देखता है और रिस्पॉन्स आगे जाने देता है। - spider
metaसे टोकन पढ़ता है औरFormRequest.from_responseसे फॉर्म सबमिट करता है।
सॉल्व कॉल deferToThread के ज़रिए एक थ्रेड में चलती है, इसलिए वह Scrapy के reactor को ब्लॉक नहीं करती। कैप्चा हल होते समय दूसरी रिक्वेस्ट आगे बढ़ती रहती हैं।
प्रोजेक्ट सेटिंग्स
अपनी key कोड में नहीं, environment variable में रखें:
# settings.py
import os
SCARNOX_API_KEY = os.environ["SCARNOX_API_KEY"]
SCARNOX_TIMEOUT = 185
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.PopularCaptchaMiddleware": 543,
}
ROBOTSTXT_OBEY = True
DOWNLOAD_DELAY = 3
CONCURRENT_REQUESTS_PER_DOMAIN = 1
डाउनलोड देरी और हर डोमेन पर एक समवर्ती रिक्वेस्ट जानबूझकर रखी गई हैं। ये साइट का लोड कम रखती हैं और आपको दिखने वाले चैलेंजों की संख्या घटाती हैं।
मिडलवेयर
# myproject/middlewares.py
import requests
from scrapy import Request
from scrapy.http import Response
from twisted.internet.threads import deferToThread
API_URL = "https://api.scarnox.com/api/tasks/create"
class PopularCaptchaMiddleware:
def __init__(self, api_key: str, timeout: int):
self.api_key = api_key
self.timeout = timeout
@classmethod
def from_crawler(cls, crawler):
return cls(
api_key=crawler.settings.get("SCARNOX_API_KEY"),
timeout=crawler.settings.getint("SCARNOX_TIMEOUT", 185),
)
def _solve(self, sitekey: str, page_url: str) -> str:
r = requests.post(
API_URL,
headers={"Authorization": f"Bearer {self.api_key}"},
json={
"captcha_type": "PopularCaptchaTaskProxyless",
"site_key": sitekey,
"site_url": page_url,
},
timeout=self.timeout, # वर्कर थ्रेड में चलता है, reactor में नहीं
)
data = r.json()
if data.get("errorId") != 0:
raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
return data["solution"]["token"]
def process_response(self, request: Request, response: Response, spider):
# पहले से हल, या हमारे खुद बनाए फॉर्म सबमिट: आगे जाने दें।
if request.meta.get("captcha_token") or request.meta.get("skip_captcha"):
return response
sitekey = response.css("[data-sitekey]::attr(data-sitekey)").get()
if not sitekey:
return response
spider.logger.info("PopularCaptcha on %s, solving", response.url)
def retry_with_token(token: str) -> Request:
# Request लौटाने से वह दोबारा शेड्यूल होता है। dont_filter dupe filter से बचाता है।
return request.replace(
dont_filter=True,
meta={**request.meta, "captcha_token": token},
)
return deferToThread(self._solve, sitekey, response.url).addCallback(retry_with_token)
process_response से Deferred लौटाना Scrapy में समर्थित है। callback टोकन को एक दोबारा होने वाली रिक्वेस्ट में बदलता है, और Scrapy उसे किसी और रिक्वेस्ट की तरह शेड्यूल करता है।
spider
# myproject/spiders/search.py
import scrapy
from scrapy.http import FormRequest
class SearchSpider(scrapy.Spider):
name = "search"
start_urls = ["https://search.example.com/search?q=web+scraping"]
def parse(self, response):
token = response.meta.get("captcha_token")
if token and response.css("[data-sitekey]"):
# Scrapy फॉर्म के अपने फ़ील्ड भरता है; हम टोकन जोड़ते हैं।
yield FormRequest.from_response(
response,
formdata={
"h-captcha-response": token,
"g-recaptcha-response": token,
},
callback=self.parse_results,
meta={"skip_captcha": True},
dont_filter=True,
)
return
yield from self.parse_results(response)
def parse_results(self, response):
for item in response.css("div.result"):
yield {
"title": item.css("h3::text").get(),
"url": item.css("a::attr(href)").get(),
}
selectors प्लेसहोल्डर हैं। उन्हें उस साइट के असली selectors से बदलें जिसे आप स्क्रैप कर रहे हैं।
skip_captcha फ्लैग ही वह चीज़ है जो मिडलवेयर को फॉर्म सबमिशन फिर से हल करने से रोकती है। इसके बिना हर सबमिट पर एक और सॉल्व का खर्च आता।
चलाएं
export SCARNOX_API_KEY=scarnox_YOURAPIKEY
scrapy crawl search -O results.json
लॉग देखें। PopularCaptcha on ..., solving जैसी एक पंक्ति का मतलब है कि चैलेंज मिला और टोकन मांगा गया। अगर यह पंक्ति हर पेज पर दिखे, तो धीमे हों। बार-बार आने वाले चैलेंज इस बात का संकेत हैं कि साइट ने क्रॉल को नोटिस कर लिया है।
टोकन का समय
PopularCaptcha टोकन लगभग दो मिनट में समाप्त होता है। मिडलवेयर टोकन आते ही रिट्राई का अनुरोध करता है, और spider उसी callbacks की श्रृंखला में सबमिट करता है। श्रृंखला छोटी रखें। टोकन और फॉर्म सबमिट के बीच धीमे डाउनलोड या लंबी प्रोसेसिंग न जोड़ें। इस विंडो के बारे में अधिक जानकारी के लिए कैप्चा टोकन की समाप्ति देखें।
एरर
विफल सॉल्व थ्रेड के अंदर RuntimeError उठाता है। Scrapy उसे एक विफल रिक्वेस्ट के रूप में लॉग करेगा। विफल सॉल्व का शुल्क नहीं लगता, इसलिए रिट्राई सस्ती है। सीमाओं वाली रिट्राई नीति के लिए Scrapy का बिल्ट-इन RetryMiddleware इस्तेमाल करें और _solve को एक छोटे लूप में लपेटें। उत्पादन में टिकने वाले पैटर्न के लिए हमारी गाइड कैप्चा सॉल्वर एरर हैंडलिंग देखें।
ज़िम्मेदारी से स्क्रैप करें
- robots.txt का पालन करें। ऊपर
ROBOTSTXT_OBEY = Trueयही करता है। इसे चालू रखें। - देरी ऊंची रखें। चैलेंज इस बात का संकेत है कि साइट चाहती है आप धीमे हों। जल्दी खत्म करने के लिए देरी को बायपास न करें।
- क्रॉल से पहले सेवा की शर्तें पढ़ें। कुछ साइटें स्वचालित एक्सेस पर पूरी तरह रोक लगाती हैं।
- व्यक्तिगत डेटा सावधानी से संभालें। केवल वही फ़ील्ड रखें जिनकी ज़रूरत है, और जो रखें उसे सुरक्षित रखें।
कानूनी पृष्ठभूमि के लिए क्या वेब स्क्रैपिंग कानूनी है देखें।
कीमत
Scarnox हर सफल सॉल्व पर शुल्क लेता है, और विफल सॉल्व मुफ्त हैं। दरों के लिए मौजूदा कीमतें देखें। मुफ्त ट्रायल क्रेडिट के साथ शुरू करें, कार्ड की ज़रूरत नहीं।