Scrapy-Captcha-Middleware: Captchas in deinem Spider lösen (2026)
Scrapy ist um Anfragen und Callbacks herum gebaut, daher passt ein Captcha nicht ohne Weiteres hinein. Der saubere Weg ist eine Downloader-Middleware: Sie sieht jede Antwort, erkennt eine Herausforderung, holt ein Token, und der Spider sendet das Formular mit diesem Token ab. Dieser Leitfaden baut diese Middleware für PopularCaptcha-Seiten, die Scarnox heute proxyless löst.
Starte mit kostenlosem Testguthaben, ohne Karte und probiere den Code auf einer Testseite, bevor du ihn auf andere Ziele richtest.
Wie die Middleware funktioniert
Die Middleware läuft bei jeder Antwort:
- Hat die Antwort kein
data-sitekey, reicht sie die Antwort unverändert weiter. - Ist es vorhanden, löst die Middleware das Captcha und gibt eine Kopie derselben Anfrage zurück, mit dem Token in
meta. - Scrapy ruft diese Anfrage erneut ab. Die Middleware sieht das Token in
metaund lässt die Antwort durch. - Der Spider liest das Token aus
metaund sendet das Formular mitFormRequest.from_responseab.
Der Lösungsaufruf läuft über deferToThread in einem Thread, sodass er den Reaktor von Scrapy nicht blockiert. Andere Anfragen laufen weiter, während das Captcha gelöst wird.
Projekteinstellungen
Lege deinen Schlüssel in eine Umgebungsvariable, nicht in den 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
Die Download-Verzögerung und die einzelne Parallelität pro Domain sind bewusst gesetzt. Sie halten die Last der Seite niedrig und verringern die Anzahl der Captchas, denen du begegnest.
Die 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, # läuft in einem Worker-Thread, nicht im Reaktor
)
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):
# Bereits gelöst oder ein selbst gebauter Formular-Submit: durchreichen.
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:
# Eine zurückgegebene Request plant sie neu ein. dont_filter umgeht den Duplikatfilter.
return request.replace(
dont_filter=True,
meta={**request.meta, "captcha_token": token},
)
return deferToThread(self._solve, sitekey, response.url).addCallback(retry_with_token)
Scrapy unterstützt es, aus process_response ein Deferred zurückzugeben. Der Callback macht aus dem Token eine erneut ausgeführte Anfrage, und Scrapy plant sie wie jede andere ein.
Der 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 füllt die eigenen Felder des Formulars aus; wir fügen das Token hinzu.
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(),
}
Die Selektoren sind Platzhalter. Ersetze sie durch die echten der Seite, die du scrapst.
Das Flag skip_captcha verhindert, dass die Middleware den Formular-Submit erneut löst. Ohne dieses Flag würde jeder Submit eine weitere Lösung kosten.
Ausführen
export SCARNOX_API_KEY=scarnox_YOURAPIKEY
scrapy crawl search -O results.json
Behalte das Log im Blick. Eine Zeile wie PopularCaptcha on ..., solving bedeutet, dass eine Herausforderung gefunden und ein Token angefordert wurde. Siehst du diese Zeile bei jeder Seite, werde langsamer. Häufige Herausforderungen sind ein Zeichen dafür, dass die Seite deinen Crawl bemerkt hat.
Timing des Tokens
Ein PopularCaptcha-Token läuft in etwa zwei Minuten ab. Die Middleware stellt die Wiederholung sofort ein, sobald das Token eintrifft, und der Spider sendet in derselben Kette von Callbacks ab. Halte die Kette kurz. Füge zwischen Token und Formular-Submit keine langsamen Downloads oder lange Verarbeitung ein. Mehr zu diesem Zeitfenster findest du unter Ablauf von Captcha-Tokens.
Fehler
Eine fehlgeschlagene Lösung löst im Thread einen RuntimeError aus. Scrapy protokolliert das als fehlgeschlagene Anfrage. Fehlgeschlagene Lösungen werden nicht berechnet, deshalb ist eine Wiederholung günstig. Für eine Wiederholungsstrategie mit Grenzen nutze den eingebauten RetryMiddleware von Scrapy und umschließe _solve mit einer kurzen Schleife. Unser Leitfaden zur Fehlerbehandlung bei Captcha-Lösungen listet die Muster auf, die im Produktivbetrieb halten.
Verantwortungsvoll scrapen
- Respektiere robots.txt.
ROBOTSTXT_OBEY = Trueoben erledigt das. Lass es eingeschaltet. - Halte die Verzögerung hoch. Ein Captcha ist ein Zeichen dafür, dass die Seite möchte, dass du langsamer wirst. Umgehe die Verzögerung nicht, um schneller fertig zu werden.
- Lies die Nutzungsbedingungen, bevor du crawlst. Manche Seiten verbieten automatisierten Zugriff ganz.
- Behandle personenbezogene Daten sorgfältig. Speichere nur die Felder, die du brauchst, und schütze, was du behältst.
Zum rechtlichen Hintergrund siehe Ist Web-Scraping legal?.
Preise
Scarnox rechnet pro erfolgreicher Lösung ab, und fehlgeschlagene Lösungen sind kostenlos. Die Raten findest du auf der Preisseite. Starte mit kostenlosem Testguthaben, ohne Karte.