Middleware de Proxy do Scrapy: Rotação de Proxies Sem Bloqueios
O Scrapy oferece suporte a proxies desde a versão 0.8, mas a documentação nunca explica como rotacionar, tentar novamente e sobreviver a bloqueios em larga escala. Aqui está a anatomia do middleware, o código e as configurações que decidem se sua coleta de dados será concluída.
O Scrapy oferece um middleware de proxy desde a versão 0.8, então 'o Scrapy suporta proxies' foi resolvido há quinze anos. O que a documentação nunca monta completamente é o cenário de produção: como o middleware de proxy embutido do Scrapy realmente processa credenciais, quando definir proxies por solicitação versus globalmente, e como rotacionar e se recuperar quando um alvo começa a bloquear saídas no meio da coleta. Este guia percorre toda a cadeia — o HttpProxyMiddleware embutido, meta por solicitação, um middleware de rotação personalizado com tratamento de bloqueios e as configurações que decidem se uma coleta de 100 mil páginas será concluída ou falhará às 3 da manhã.
Como funciona o middleware de proxy embutido do Scrapy
scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware está habilitado por padrão com prioridade 750 em DOWNLOADER_MIDDLEWARES_BASE. Ler seu código-fonte (cerca de 100 linhas no Scrapy 2.17) te informa tudo o que você precisa para depuração:
- Na inicialização, ele chama
urllib.request.getproxies(), então as variáveis de ambientehttp_proxy/https_proxyconfiguram um spider sem código.no_proxyé respeitado apenas para esquemas http e https. - Por solicitação,
request.meta['proxy']sempre prevalece sobre o ambiente. Defina comoNonepara forçar uma conexão direta para essa solicitação. - Credenciais embutidas na URL do proxy (
http://user:pass@host:port) são removidas, codificadas em Base64 e enviadas como um cabeçalhoProxy-Authorization: Basic. A codificação padrão é latin-1 e pode ser configurada viaHTTPPROXY_AUTH_ENCODING. - O middleware decodifica as credenciais antes de codificá-las, então codifique em URL caracteres especiais como
@em senhas — eles passam corretamente. - Se uma tentativa de nova conexão mudar o proxy, o middleware descarta o cabeçalho de autenticação obsoleto em vez de vazá-lo para o novo proxy — uma correção de higiene de credenciais no Scrapy moderno, e uma boa razão para não usar versões antigas.
Consequência prática: você quase nunca precisa de um pacote de proxy de terceiros. Qualquer coisa que coloque uma URL de proxy válida em request.meta['proxy'] antes da prioridade 750 obtém autenticação e manipulação de cabeçalho gratuitamente.
Proxies por solicitação com meta
A integração mais leve é definir meta diretamente no spider — útil quando apenas algumas solicitações precisam de um proxy, ou diferentes alvos precisam de diferentes países de saída:
import scrapy
class PricesSpider(scrapy.Spider):
name = "prices"
def start_requests(self):
yield scrapy.Request(
"https://example.com/product/1",
meta={"proxy": "http://USER:PASS@gate.quantumproxies.io:PORT"},
)
Meta por solicitação brilha quando a escolha do proxy é orientada por dados: roteie páginas de produtos alemãs por um nome de usuário direcionado à Alemanha, envie downloads de imagens por saídas de datacenter baratas enquanto o HTML vai por residenciais, ou escale uma URL teimosa para uma sessão mais persistente na segunda tentativa. Como meta sobrevive a tentativas e redirecionamentos, a decisão que você toma ao construir a solicitação a acompanha por todo o ciclo de download. Definir meta em milhares de solicitações manualmente não escala, no entanto — é para isso que os middlewares de downloader existem.
A configuração de produção mais simples: um gateway rotativo
Com proxies rotativos por trás de um único endpoint de gateway, a rotação acontece no lado do servidor: cada solicitação através da mesma URL sai de um IP diferente em um pool residencial de mais de 90 milhões abrangendo mais de 200 países. Seu middleware se reduz a três linhas, e não há lista para verificar a saúde, podar ou atualizar:
# middlewares.py
class RotatingGatewayMiddleware:
PROXY = "http://USER:PASS@gate.quantumproxies.io:PORT"
def process_request(self, request, spider):
request.meta["proxy"] = self.PROXY
# settings.py
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.RotatingGatewayMiddleware": 350,
}
A prioridade 350 importa: seu middleware deve ser executado antes do embutido em 750 para que o HttpProxyMiddleware ainda possa converter as credenciais no cabeçalho de autenticação. Este emparelhamento — rotação de gateway mais a maquinaria de nova tentativa padrão do Scrapy — é a maior confiabilidade por linha de código, porque cada nova tentativa viaja automaticamente por um novo IP de saída.

Um middleware de proxy rotativo personalizado com tratamento de bloqueios
Se você gerenciar sua própria lista de proxies — endereços de ISP estáticos ou um pool misto — o padrão clássico (popularizado pelo pacote scrapy-proxies, que recomendava RETRY_TIMES = 10 porque proxies de lista gratuita falham com tanta frequência) é: escolher aleatoriamente por solicitação, expulsar em sinais de bloqueio, re-enfileirar a solicitação:
import random
class ProxyListMiddleware:
BAN_CODES = {403, 429}
def __init__(self, proxies):
self.proxies = list(proxies)
@classmethod
def from_crawler(cls, crawler):
return cls(crawler.settings.getlist("PROXY_LIST"))
def process_request(self, request, spider):
if self.proxies and "proxy" not in request.meta:
request.meta["proxy"] = random.choice(self.proxies)
def process_response(self, request, response, spider):
if response.status in self.BAN_CODES:
bad = request.meta.get("proxy")
if bad in self.proxies and len(self.proxies) > 1:
self.proxies.remove(bad)
spider.logger.warning("Evicted %s (%d left)", bad, len(self.proxies))
return request.replace(dont_filter=True) # re-queue on a new proxy
return response
Observe tudo o que este middleware deve fazer que um gateway faz de graça: rastrear a saúde do pool, expulsar IPs queimados, re-enfileirar solicitações. Ele também encolhe sob fogo — um pool de 20 IPs estáticos pode evaporar em minutos em um alvo agressivo. O manual mais amplo de anti-bloqueio (ritmo, cabeçalhos, disciplina de sessão) está em nosso checklist de evitação de bloqueio de IP.
Configurações do Scrapy que decidem o resultado
O middleware coloca o proxy; as configurações decidem se a coleta se comporta. Estas são as mais importantes:
# settings.py — a sane baseline for proxied crawls
RETRY_TIMES = 5
RETRY_HTTP_CODES = [429, 403, 500, 502, 503, 504, 408]
CONCURRENT_REQUESTS = 32
CONCURRENT_REQUESTS_PER_DOMAIN = 8
DOWNLOAD_TIMEOUT = 30
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 4.0
- RETRY_HTTP_CODES — adicione 429 e 403 para que bloqueios suaves acionem uma nova tentativa (e, com rotação, um novo IP) em vez de um item falhado.
- RETRY_TIMES — 3-5 é adequado para um pool de qualidade; precisar de 10 é um sinal de que o próprio pool está insalubre.
- CONCURRENT_REQUESTS_PER_DOMAIN — o verdadeiro botão de cortesia. A rotação distribui a carga entre os IPs, mas o alvo ainda vê o volume total.
- DOWNLOAD_TIMEOUT — os 180 segundos padrão permitem que uma saída lenta ocupe um slot de concorrência por três minutos. 30 é suficiente.
- AUTOTHROTTLE — adapta o ritmo à latência observada; combina bem com rotação em alvos com limite de taxa. Ainda afundando em 429s? Nosso guia de limite de taxa cobre orçamentos de concorrência em detalhes.

Detecção de bloqueio além dos códigos de status
Alvos sofisticados não enviam 403s honestos. Eles servem 200s contendo uma página CAPTCHA, uma grade de produtos vazia ou um modelo simplificado. Spiders robustos verificam o conteúdo, não apenas o status — verifique por um elemento que só existe na página real, e re-enfileire quando ele estiver ausente. Se as páginas voltarem estruturalmente vazias mesmo através de bons proxies, o conteúdo provavelmente é renderizado no lado do cliente; veja por que scrapers retornam páginas vazias. E quando um domínio derrota HTTP simples, independentemente do IP, entregue esse domínio a uma Scraper API que renderiza JavaScript e retorna HTML ou markdown limpo — o Scrapy consome como qualquer outra resposta, e você mantém seu pipeline.
def parse(self, response):
if not response.css("div.product-grid"):
# 200 OK but the real content is missing: soft ban or JS wall
yield response.request.replace(dont_filter=True)
return
for product in response.css("div.product-grid article"):
yield {"name": product.css("h2::text").get()}
Perguntas frequentes
O Scrapy suporta proxies nativamente?
Sim. HttpProxyMiddleware vem habilitado com prioridade 750: ele lê as variáveis de ambiente http_proxy / https_proxy e respeita request.meta['proxy'] por solicitação, incluindo credenciais user:pass@, que ele converte em um cabeçalho Proxy-Authorization. Você só escreve código para decidir qual proxy cada solicitação recebe.
Como defino um proxy para uma única solicitação no Scrapy?
Passe no meta da solicitação: scrapy.Request(url, meta={'proxy': 'http://user:pass@host:port'}). Meta sempre substitui proxies em nível de ambiente, e definir o valor como None força essa única solicitação a ir direto — útil para misturar tráfego com e sem proxy em um spider.
Como rotaciono proxies no Scrapy?
Ou escreva um middleware de downloader que escolha um proxy por solicitação de uma lista e expulse os bloqueados, ou aponte cada solicitação para um endpoint de gateway rotativo que atribui um novo IP de saída no lado do servidor. A abordagem do gateway precisa de um middleware de três linhas, sem verificações de saúde, e transforma cada nova tentativa do Scrapy em uma rotação gratuitamente.
Por que meu proxy do Scrapy retorna 407?
O proxy rejeitou a autenticação. Verifique se as credenciais estão dentro da URL do proxy no meta, se caracteres especiais estão codificados em URL, e se o IP do seu servidor está na lista branca se seu plano usa autenticação por IP. Se as credenciais contiverem caracteres não ASCII, defina HTTPPROXY_AUTH_ENCODING para corresponder ao seu provedor.
O padrão a lembrar: defina meta['proxy'] cedo, deixe o middleware embutido em 750 fazer a autenticação, adicione 429 e 403 aos seus códigos de nova tentativa, e prefira rotação no lado do servidor em vez de cuidar de listas. Se partes da sua coleta precisarem de JavaScript, avalie o custo de navegadores headless versus solicitações HTTP antes de optar por um — a maioria dos projetos Scrapy precisa de IPs melhores, não de um navegador.