Gerenciando o Fluxo de Requisições e Respostas com Downloader Middlewares no Scrapy

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 objeto Request que está sendo processado.
  • spider: O objeto Spider ao qual esta requisição pertence.

O retorno deste método define o fluxo subsequente:

  • None: O Scrapy continua o processamento normalmente. Outros process_request de Middlewares com prioridade mais baixa serão chamados. Eventualmente, a requisição será enviada ao Downloader.
  • Response: O processamento da requisição é interrompido. Nenhum outro process_request ou process_exception será chamado para esta requisição. Em vez disso, a Response retornada será passada imediatamente de volta ao Engine, que então a encaminhará para os métodos process_response dos Downloader Middlewares (em ordem inversa de prioridade) e, por fim, para o Spider.
  • Request: O processamento da requisição atual é interrompido. A nova Request retornada é reenviada ao Agendador (Scheduler) para ser processada em um ciclo futuro. Nenhum outro process_request ou process_exception será chamado para a requisição original.
  • Levantar IgnoreRequest: Os métodos process_exception de todos os Downloader Middlewares são chamados. Se a exceção não for tratada, o método errback da 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 objeto Request original que gerou esta resposta.
  • response: O objeto Response que está sendo processado.
  • spider: O objeto Spider ao qual esta resposta pertence.

O retorno deste método também altera o fluxo:

  • Response: O Scrapy continua o processamento normalmente. A Response retornada será passada para os métodos process_response de Middlewares com prioridade mais alta (em ordem inversa).
  • Request: A Response atual é descartada. A nova Request retornada é reenviada ao Agendador (Scheduler) para ser processada em um ciclo futuro. Nenhum outro process_response será chamado para a resposta original.
  • Levantar IgnoreRequest: O método errback da 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 objeto Request que gerou a exceção.
  • exception: O objeto de exceção levantado.
  • spider: O objeto Spider ao qual esta requisição pertence.

As opções de retorno são:

  • None: O Scrapy continua o processamento, chamando os process_exception de 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. A Response retornada é passada para os métodos process_response de todos os Downloader Middlewares (em ordem inversa), e depois para o Spider.
  • Request: O processamento da exceção é interrompido. A nova Request retornada é 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.

Tags: Scrapy Python WebScraping middleware DataExtraction

Publicado em 7-28 03:51