Scrapy captcha-middleware: captchas oplossen in je spider (2026)
Scrapy is opgebouwd rond requests en callbacks, dus een captcha past daar niet netjes in. De nette manier om er een af te handelen is een downloader-middleware: die ziet elke response, merkt een uitdaging op, haalt een token op, en laat de spider het formulier met dat token versturen. Deze gids bouwt die middleware voor PopularCaptcha-pagina's, die Scarnox vandaag oplost op een proxyless basis.
Begin met gratis proefkrediet, zonder creditcard en probeer de code op een testpagina voordat je hem op iets anders richt.
Hoe de middleware werkt
De middleware draait op elke response:
- Als de response geen
data-sitekeyheeft, geeft hij de response door. - Als dat wel zo is, lost de middleware de captcha op en geeft hij een kopie van hetzelfde request terug, met het token opgeslagen in
meta. - Scrapy haalt dat request opnieuw op. De middleware ziet het token in
metaen laat de response door. - De spider leest het token uit
metaen verstuurt het formulier metFormRequest.from_response.
De solve-aanroep draait in een thread via deferToThread, dus hij blokkeert de reactor van Scrapy niet. Andere requests blijven doorlopen terwijl de captcha wordt opgelost.
Projectinstellingen
Zet je sleutel in een omgevingsvariabele, niet in de code:
# 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
De download-vertraging en de enkele concurrency per domein staan er met een reden. Ze houden de belasting van de site laag en verminderen het aantal captchas dat je ziet.
De middleware
# 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, # draait in een worker-thread, niet in de 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):
# Al opgelost, of een formulierverzending die we zelf hebben gebouwd: doorlaten.
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:
# Een Request teruggeven plant het opnieuw in. dont_filter omzeilt het duplicaatfilter.
return request.replace(
dont_filter=True,
meta={**request.meta, "captcha_token": token},
)
return deferToThread(self._solve, sitekey, response.url).addCallback(retry_with_token)
Scrapy ondersteunt het teruggeven van een Deferred vanuit process_response. De callback maakt van het token een opnieuw uitgevoerd request, en Scrapy plant het in zoals elk ander request.
De 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 vult de eigen velden van het formulier in; wij voegen het token toe.
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(),
}
De selectors zijn placeholders. Vervang ze door de echte van de site die je scrapet.
De vlag skip_captcha zorgt ervoor dat de middleware de formulierverzending niet opnieuw oplost. Zonder die vlag zou elke verzending nog een solve kosten.
Draaien
export SCARNOX_API_KEY=scarnox_YOURAPIKEY
scrapy crawl search -O results.json
Houd de log in de gaten. Een regel zoals PopularCaptcha op ..., wordt opgelost betekent dat er een uitdaging is gevonden en dat er een token is aangevraagd. Als je die regel op elke pagina ziet, vertraag dan. Veelvuldige uitdagingen zijn een teken dat de site je crawl heeft opgemerkt.
Timing van het token
Een PopularCaptcha-token verloopt na ongeveer twee minuten. De middleware vraagt het opnieuwgeplande request aan zodra het token binnen is, en de spider verstuurt in dezelfde keten van callbacks. Houd de keten kort. Voeg geen trage downloads of lange verwerking toe tussen het token en het verzenden van het formulier. Voor meer over dat venster, zie verlopen captchatokens.
Fouten
Een mislukte solve gooit een RuntimeError binnen de thread. Scrapy logt dat als een mislukt request. Mislukte solves worden niet in rekening gebracht, dus opnieuw proberen is goedkoop. Voor een retrybeleid met limieten gebruik je de ingebouwde RetryMiddleware van Scrapy en omring je _solve met een korte lus. Onze gids over captcha solver-foutafhandeling somt de patronen op die in productie standhouden.
Scrape verantwoord
- Respecteer robots.txt.
ROBOTSTXT_OBEY = Truehierboven doet dit. Laat het aan. - Houd de vertraging hoog. Een uitdaging is een teken dat de site wil dat je langzamer gaat. Werk de vertraging niet om om sneller klaar te zijn.
- Lees de gebruiksvoorwaarden voordat je crawlt. Sommige sites verbieden geautomatiseerde toegang volledig.
- Behandel persoonsgegevens zorgvuldig. Bewaar alleen de velden die je nodig hebt, en bescherm wat je bewaart.
Voor de juridische achtergrond, zie is webscraping legaal.
Prijzen
Scarnox rekent per geslaagde solve, en mislukte solves zijn gratis. Zie actuele prijzen voor de tarieven. Begin met gratis proefkrediet, zonder creditcard.