Middleware captcha per Scrapy: risolvi i captcha nel tuo spider (2026)
Scrapy è costruito attorno a richieste e callback, quindi un captcha non si incastra bene al suo interno. Il modo pulito per gestirlo è un middleware del downloader: vede ogni risposta, nota una sfida, ottiene un token e lascia che lo spider invii il modulo con quel token. Questa guida costruisce quel middleware per le pagine PopularCaptcha, che Scarnox risolve oggi in modalità proxyless.
Inizia con il credito di prova gratuito, senza carta e prova il codice su una pagina di test prima di puntarlo verso qualsiasi altro sito.
Come funziona il middleware
Il middleware viene eseguito su ogni risposta:
- Se la risposta non ha un
data-sitekey, lascia passare la risposta. - Se ce l'ha, il middleware risolve il captcha e restituisce una copia della stessa richiesta con il token memorizzato in
meta. - Scrapy esegue di nuovo quella richiesta. Il middleware vede il token in
metae lascia passare la risposta. - Lo spider legge il token da
metae invia il modulo conFormRequest.from_response.
La chiamata di risoluzione viene eseguita in un thread tramite deferToThread, quindi non blocca il reactor di Scrapy. Le altre richieste continuano a procedere mentre il captcha viene risolto.
Impostazioni del progetto
Metti la tua chiave in una variabile d'ambiente, non nel codice:
# 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
Il ritardo di download e la concorrenza singola per dominio sono lì apposta. Mantengono basso il carico sul sito e riducono il numero di sfide che incontri.
Il 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, # gira in un thread di lavoro, non nel 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):
# Già risolto, oppure un invio del modulo che abbiamo costruito noi: lascia passare.
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:
# Restituire una Request la rimette in coda. dont_filter evita il filtro dei duplicati.
return request.replace(
dont_filter=True,
meta={**request.meta, "captcha_token": token},
)
return deferToThread(self._solve, sitekey, response.url).addCallback(retry_with_token)
Restituire un Deferred da process_response è supportato da Scrapy. La callback trasforma il token in una richiesta da ritentare, e Scrapy la pianifica come qualsiasi altra.
Lo 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 compila i campi del modulo; noi aggiungiamo il token.
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(),
}
I selettori sono segnaposto. Sostituiscili con quelli reali del sito di cui fai scraping.
Il flag skip_captcha è ciò che impedisce al middleware di risolvere di nuovo l'invio del modulo. Senza di esso, ogni invio costerebbe un'altra risoluzione.
Esegui
export SCARNOX_API_KEY=scarnox_YOURAPIKEY
scrapy crawl search -O results.json
Osserva il log. Una riga come PopularCaptcha on ..., solving indica che è stata trovata una sfida e che è stato richiesto un token. Se vedi quella riga su ogni pagina, rallenta. Le sfide frequenti sono un segnale che il sito ha notato il crawl.
Tempistica del token
Un token PopularCaptcha scade in circa due minuti. Il middleware richiede il nuovo tentativo non appena arriva il token, e lo spider invia nella stessa catena di callback. Mantieni la catena breve. Non aggiungere download lenti o elaborazioni lunghe tra il token e l'invio del modulo. Per approfondire la finestra, vedi scadenza del token captcha.
Errori
Una risoluzione fallita genera un RuntimeError all'interno del thread. Scrapy la registrerà come richiesta fallita. Le risoluzioni fallite non vengono addebitate, quindi ritentare costa poco. Per una politica di nuovi tentativi con limiti, usa il RetryMiddleware integrato di Scrapy e avvolgi _solve in un breve ciclo. La nostra guida sulla gestione degli errori del solver captcha elenca i pattern che reggono in produzione.
Scraping responsabile
- Rispetta robots.txt.
ROBOTSTXT_OBEY = Truequi sopra lo fa. Tienilo attivo. - Mantieni alto il ritardo. Una sfida è un segnale che il sito vuole che rallenti. Non aggirare il ritardo per finire più in fretta.
- Leggi i termini di servizio prima di fare il crawl. Alcuni siti vietano del tutto l'accesso automatizzato.
- Gestisci con cura i dati personali. Conserva solo i campi che ti servono, e proteggi quelli che tieni.
Per il quadro legale, vedi il web scraping è legale?.
Tariffe
Scarnox addebita per ogni risoluzione riuscita, e le risoluzioni fallite non vengono addebitate. Consulta le tariffe attuali per i prezzi. Inizia con il credito di prova gratuito, senza carta.