Middleware de captcha para Scrapy: resolva captchas no seu spider (2026)
O Scrapy é construído em torno de requisições e callbacks, então um captcha não se encaixa bem nele. A forma limpa de lidar com um é um middleware de downloader: ele vê cada resposta, percebe um desafio, obtém um token e deixa o spider enviar o formulário com esse token. Este guia constrói esse middleware para páginas PopularCaptcha, que a Scarnox resolve hoje em modo proxyless.
Comece com crédito de teste grátis, sem cartão e teste o código em uma página de teste antes de apontá-lo para qualquer outro lugar.
Como o middleware funciona
O middleware roda em cada resposta:
- Se a resposta não tem
data-sitekey, ele deixa a resposta passar. - Se tem, o middleware resolve o captcha e devolve uma cópia da mesma requisição, com o token guardado em
meta. - O Scrapy busca essa requisição de novo. O middleware vê o token em
metae deixa a resposta passar. - O spider lê o token de
metae envia o formulário comFormRequest.from_response.
A chamada de resolução roda em uma thread através de deferToThread, então não bloqueia o reactor do Scrapy. Outras requisições continuam andando enquanto o captcha é resolvido.
Configurações do projeto
Guarde a sua chave em uma variável de ambiente, e não no código:
# 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
O atraso de download e a concorrência única por domínio estão aí de propósito. Eles mantêm baixa a carga sobre o site e reduzem o número de desafios que você enfrenta.
O 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, # roda em uma thread de trabalho, não no 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):
# Já resolvido, ou um envio de formulário que criamos nós: deixe passar.
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:
# Devolver uma Request a reagenda. dont_filter evita o filtro de duplicatas.
return request.replace(
dont_filter=True,
meta={**request.meta, "captcha_token": token},
)
return deferToThread(self._solve, sitekey, response.url).addCallback(retry_with_token)
O Scrapy aceita retornar um Deferred de process_response. O callback transforma o token em uma requisição de nova tentativa, e o Scrapy a agenda como qualquer outra.
O 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]"):
# O Scrapy preenche os campos do próprio formulário; nós adicionamos o 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(),
}
Os seletores são exemplos. Troque-os pelos reais do site que você está raspando.
A flag skip_captcha é o que impede o middleware de resolver o envio do formulário de novo. Sem ela, cada envio custaria outra resolução.
Execute
export SCARNOX_API_KEY=scarnox_YOURAPIKEY
scrapy crawl search -O results.json
Acompanhe o log. Uma linha como PopularCaptcha on ..., solving significa que um desafio foi encontrado e um token foi solicitado. Se você vir essa linha em todas as páginas, desacelere. Desafios frequentes são sinal de que o site percebeu o rastreamento.
Tempo do token
Um token PopularCaptcha expira em cerca de dois minutos. O middleware solicita a nova tentativa assim que o token chega, e o spider envia na mesma cadeia de callbacks. Mantenha a cadeia curta. Não adicione downloads lentos nem processamento longo entre o token e o envio do formulário. Para mais detalhes sobre essa janela, veja expiração de token de captcha.
Erros
Uma resolução que falha levanta RuntimeError dentro da thread. O Scrapy registra isso como uma requisição com falha. Resoluções que falham não são cobradas, então tentar de novo é barato. Para uma política de novas tentativas com limites, use o RetryMiddleware embutido do Scrapy e envolva _solve em um laço curto. Nosso guia sobre tratamento de erros do solver de captcha lista os padrões que se sustentam em produção.
Faça scraping com responsabilidade
- Obedeça ao robots.txt. O
ROBOTSTXT_OBEY = Trueacima faz isso. Mantenha-o ativo. - Mantenha o atraso alto. Um desafio é sinal de que o site quer que você desacelere. Não contorne o atraso para terminar mais rápido.
- Leia os termos de serviço antes de rastrear. Alguns sites proíbem o acesso automatizado de forma direta.
- Trate dados pessoais com cuidado. Guarde apenas os campos de que precisa e proteja o que mantiver.
Para o contexto jurídico, veja a web scraping é legal.
Preços
A Scarnox cobra por resolução bem-sucedida, e resoluções que falham são gratuitas. Veja a página de preços para as tarifas. Comece com crédito de teste grátis, sem cartão.