Introdução aos Downloader Middlewares no Scrapy
Os Downloader Middlewares são componentes essenciais na arquitetura do Scrapy, atuando como "interceptores" entre o Motor (Engine) e o Downloader. Eles oferecem um ponto de controle poderoso para manipular tanto as requisições enviadas ao Downloader quanto as respostas recebidas antes que cheguem ao Spider. Essa capacidade de intercepção permite implementar funcionalidades avançadas sem alterar a lógica principal do seu spider.
A atuação dos Middlewares ocorre em duas fases cruciais:
- Pré-Requisição: Antes que uma requisição (
Request) obtida do Agendador (Scheduler) seja enviada ao Downloader para download, os Downloader Middlewares podem inspecioná-la e modificá-la. Isso é ideal para, por exemplo, alterar cabeçalhos, adicionar proxies ou lidar com autenticação. - Pós-Resposta: Após o Downloader ter processado a requisição e gerado uma resposta (
Response), mas antes que essa resposta seja encaminhada ao Spider para análise, os Middlewares podem interceptá-la. Aqui, é possível modificar o conteúdo da resposta, tratar redirecionamentos ou gerenciar cookies.
Graças a essa flexibilidade, os Downloader Middlewares são indispensáveis para recursos como a rotação de User-Agents, o uso de servidores proxy, o tratamento de cookies, a implementação de lógica de retry para requisições falhas e o manuseio de redirecionamentos complexos.
Configuração e Fluxo de Execução
O Scrapy já vem com uma série de Downloader Middlewares pré-configurados, ativados e ordenados por um valor de prioridade em settings.py (ou DOWNLOADER_MIDDLEWARES_BASE). Um número menor indica maior prioridade, ou seja, mais próximo do Engine.
DOWNLOADER_MIDDLEWARES_BASE = {
# Lado do Engine (menor prioridade, mais próximo do Engine)
'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500,
'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,
'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
# Lado do Downloader (maior prioridade, mais próximo do Downloader)
}
Quando você ativa um Downloader Middleware personalizado, ele é inserido nesta cadeia de processamento. A ordem de execução de seus métodos segue as seguintes regras:
process_request: Os métodos são chamados em ordem crescente de prioridade (do menor para o maior, mais próximo do Engine para o Downloader).process_response: Os métodos são chamados em ordem decrescente de prioridade (do maior para o menor, do Downloader de volta para o Engine).process_exception: Os métodos são chamados em ordem decrescente de prioridade (do maior para o menor, do Downloader de volta para o Engine) se uma exceção ocorrer durante o download.
Métodos Fundamentais para Downloader Middlewares Personalizados
Para criar seu próprio Downloader Middleware, você deve implementar um ou mais dos seguintes métodos:
process_request(request, spider)
Este método é chamado para cada requisição que passa pelo Downloader Middleware. Ele é o ponto ideal para modificar requisições antes que sejam enviadas.
request: O objetoRequestque está sendo processado.spider: O objetoSpiderao qual esta requisição pertence.
O retorno deste método define o fluxo subsequente:
None: O Scrapy continua o processamento normalmente. Outrosprocess_requestde Middlewares com prioridade mais baixa serão chamados. Eventualmente, a requisição será enviada ao Downloader.Response: O processamento da requisição é interrompido. Nenhum outroprocess_requestouprocess_exceptionserá chamado para esta requisição. Em vez disso, aResponseretornada será passada imediatamente de volta ao Engine, que então a encaminhará para os métodosprocess_responsedos Downloader Middlewares (em ordem inversa de prioridade) e, por fim, para o Spider.Request: O processamento da requisição atual é interrompido. A novaRequestretornada é reenviada ao Agendador (Scheduler) para ser processada em um ciclo futuro. Nenhum outroprocess_requestouprocess_exceptionserá chamado para a requisição original.- Levantar
IgnoreRequest: Os métodosprocess_exceptionde todos os Downloader Middlewares são chamados. Se a exceção não for tratada, o métodoerrbackda requisição é invocado. Se ainda não for tratada, a requisição é simplesmente ignorada.
process_response(request, response, spider)
Este método é invocado para cada resposta recebida pelo Downloader antes que ela seja passada ao Spider.
request: O objetoRequestoriginal que gerou esta resposta.response: O objetoResponseque está sendo processado.spider: O objetoSpiderao qual esta resposta pertence.
O retorno deste método também altera o fluxo:
Response: O Scrapy continua o processamento normalmente. AResponseretornada será passada para os métodosprocess_responsede Middlewares com prioridade mais alta (em ordem inversa).Request: AResponseatual é descartada. A novaRequestretornada é reenviada ao Agendador (Scheduler) para ser processada em um ciclo futuro. Nenhum outroprocess_responseserá chamado para a resposta original.- Levantar
IgnoreRequest: O métodoerrbackda requisição original é invocado. Se a exceção não for tratada, a requisição é ignorada.
process_exception(request, exception, spider)
Este método é chamado quando o Downloader ou qualquer process_request levanta uma exceção.
request: O objetoRequestque gerou a exceção.exception: O objeto de exceção levantado.spider: O objetoSpiderao qual esta requisição pertence.
As opções de retorno são:
None: O Scrapy continua o processamento, chamando osprocess_exceptionde outros Middlewares com prioridade mais alta (em ordem inversa), até que todos sejam chamados ou uma exceção seja tratada.Response: O processamento da exceção é interrompido. AResponseretornada é passada para os métodosprocess_responsede todos os Downloader Middlewares (em ordem inversa), e depois para o Spider.Request: O processamento da exceção é interrompido. A novaRequestretornada é reenviada ao Agendador (Scheduler) para ser processada em um ciclo futuro.
Exemplos Práticos de Downloader Middleware
Exemplo 1: Rotação de User-Agent
Alterar o User-Agent é uma prática comum para evitar bloqueios. Existem duas abordagens principais:
Opção A: Configuração Direta em settings.py
Para um User-Agent fixo, basta definir a variável USER_AGENT:
# settings.py
USER_AGENT = 'MeuSpiderPersonalizado (+https://meudominio.com)'
Opção B: Middleware Personalizado para Rotação Dinâmica
Para rotacionar entre múltiplos User-Agents, um middleware é a solução mais flexível. Primeiro, defina a classe do middleware:
# myproject/middlewares.py
import random
class AgenteUsuarioAleatorioMiddleware:
def __init__(self):
self.lista_agentes = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15',
'Mozilla/5.0 (Linux; Android 10; SM-G981B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.86 Mobile Safari/537.36',
]
def process_request(self, request, spider):
request.headers['User-Agent'] = random.choice(self.lista_agentes)
spider.logger.debug(f"User-Agent definido para: {request.headers['User-Agent']}")
Em seguida, ative este middleware em settings.py, atribuindo uma prioridade. Certifique-se de desativar o UserAgentMiddleware padrão do Scrapy, se desejar que apenas o seu funcione:
# settings.py
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.AgenteUsuarioAleatorioMiddleware': 543,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, # Desativa o middleware padrão
}
Após a execução, você notará que o cabeçalho User-Agent nas requisições será um dos valores aleatórios definidos na lista.
Exemplo 2: Configuração de Proxy
Para direcionar requisições através de um servidor proxy, você pode usar um middleware. Crie a classe ProxyMiddleware em myproject/middlewares.py:
# myproject/middlewares.py
class MiddlewareDeProxyEstatico:
def process_request(self, request, spider):
# Exemplo com proxy local de depuração
request.meta['proxy'] = 'http://127.0.0.1:8888'
spider.logger.debug(f"Proxy definido para: {request.meta['proxy']}")
Ative-o em settings.py, garantindo que sua prioridade esteja alinhada com suas necessidades:
# settings.py
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.AgenteUsuarioAleatorioMiddleware': 543,
'myproject.middlewares.MiddlewareDeProxyEstatico': 544,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
}
Compreendendo Retornos de process_request
Retornando um Objeto Request
Se um process_request retorna um objeto Request, o Scrapy reintroduz essa nova (ou modificada) requisição no pipeline de agendamento. Isso efetivamente reinicia o ciclo para essa requisição.
# myproject/middlewares.py
class MiddlewareDeNovaRequisicao:
def process_request(self, request, spider):
spider.logger.info(f"Processando requisição original: {request.url}")
# Retorna a própria requisição, causando um loop infinito se não houver condição de parada.
# Em um cenário real, você retornaria uma *nova* requisição com URL ou meta diferente.
return request
Ao ativar este middleware e sem uma lógica de parada, a requisição seria infinitamente reenviada ao agendador, resultando em um erro de recursão máxima como RecursionError: maximum recursion depth exceeded. Este comportamento é útil quando você precisa redirecionar uma requisição para outro URL ou adiar seu processamento.
Retornando um Objeto Response
Retornar um objeto Response de process_request é uma forma de "curto-circuitar" o processo de download. A resposta é criada diretamente no middleware e passa para os métodos process_response, sem nunca ir para o Downloader real.
# myproject/middlewares.py
from scrapy.http import HtmlResponse
class MiddlewareDeRespostaDireta:
def process_request(self, request, spider):
spider.logger.info(f"Interrompendo download para: {request.url}. Gerando resposta simulada.")
# Retorna uma Response simulada diretamente.
return HtmlResponse(
url=request.url,
status=200,
encoding='utf-8',
body='<html><body>Conteúdo da Resposta Injetada Pelo Middleware</body></html>'
)
Se você configurar este middleware e tentar buscar uma URL como https://httpbin.org/get, o corpo da resposta que o spider receberá será <html><body>Conteúdo da Resposta Injetada Pelo Middleware</body></html>, em vez do JSON real fornecido pelo serviço httpbin.
Modificando uma Response com process_response
O método process_response permite alterar a resposta após o download, mas antes que ela seja passada ao spider. Podemos, por exemplo, mudar o código de status HTTP.
# myproject/middlewares.py
class MiddlewareDeModificacaoDeResposta:
def process_response(self, request, response, spider):
spider.logger.info(f"Status original da resposta para {request.url}: {response.status}")
if response.status == 200:
response.status = 202 # Altera o status para "Accepted"
spider.logger.info(f"Novo status da resposta: {response.status}")
return response
Ative o middleware em settings.py:
# settings.py
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.MiddlewareDeModificacaoDeResposta': 700, # Prioridade adequada
}
Ao executar o spider, mesmo que o servidor web retorne um status 200 OK, o spider receberá um objeto Response com o status alterado para 202 Accepted, demonstrando a capacidade do middleware de transformar as respostas.