O Scrapy continua sendo o cavalo de batalha para crawls em produção em 2026 — e ainda é o framework onde a configuração de proxy mais confunde as pessoas, porque há quatro lugares diferentes para plugar um proxy e três deles estão errados para a maioria dos projetos. Este guia te dá o certo: um pequeno middleware personalizado com roteamento por requisição, sessões sticky, detecção de banimento e comportamento de retentativa sensato.
Se você está usando requests/httpx/aiohttp puros, veja Como Rotacionar Proxies em Python. Este artigo é específico do Scrapy.
Para um gateway residencial rotativo, a configuração mínima viável é uma linha por requisição — sem necessidade de middleware:
def start_requests(self):
for url in self.urls:
yield scrapy.Request(
url,
meta={"proxy": "http://USERNAME:PASSWORD@us.jibaoproxy.com:913"},
)
O HttpProxyMiddleware embutido do Scrapy lê request.meta["proxy"] e cuida da autenticação a partir da URL. O gateway rotaciona o IP de saída para você. Se é só disso que você precisa, pare por aqui. O resto deste guia é para quando você precisa de controle: sessões sticky, roteamento por país, rotação consciente de banimentos e ajuste de concorrência.
Coloque isto em middlewares.py. Ele atribui sessões sticky por domínio, rotaciona em banimentos e marca cada requisição para que você possa depurar qual sessão buscou o quê:
import random
import string
GATEWAY = "us.jibaoproxy.com:913"
USERNAME = "USERNAME" # move to settings.py / env in real projects
PASSWORD = "PASSWORD"
def _new_session(n=8):
return "".join(random.choices(string.ascii_lowercase + string.digits, k=n))
class JibaoProxyMiddleware:
"""Sticky session per domain; rotate session on ban."""
def __init__(self):
self.sessions = {} # domain -> session id
def _proxy_url(self, session_id):
user = f"{USERNAME}-session-{session_id}"
return f"http://{user}:{PASSWORD}@{GATEWAY}"
def process_request(self, request, spider):
domain = request.url.split("/")[2]
session = self.sessions.setdefault(domain, _new_session())
request.meta["proxy"] = self._proxy_url(session)
request.meta["proxy_session"] = session
def rotate(self, domain):
"""Call when a session is burned."""
self.sessions[domain] = _new_session()
E um downloader middleware companheiro que detecta banimentos e refaz a requisição em uma sessão nova:
from scrapy.downloadermiddlewares.retry import RetryMiddleware
from scrapy.utils.response import response_status_message
BAN_CODES = {403, 429}
BAN_MARKERS = (b"captcha", b"access denied", b"unusual traffic")
class BanAwareRetryMiddleware(RetryMiddleware):
def process_response(self, request, response, spider):
banned = (
response.status in BAN_CODES
or any(m in response.body[:2048].lower() for m in BAN_MARKERS)
)
if banned:
domain = request.url.split("/")[2]
proxy_mw = spider.crawler.engine.downloader.middleware.middlewares
for mw in proxy_mw:
if hasattr(mw, "rotate"):
mw.rotate(domain) # burn the session
reason = response_status_message(response.status)
return self._retry(request, reason, spider) or response
return super().process_response(request, response, spider)
Conecte os dois em settings.py:
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.JibaoProxyMiddleware": 350,
"scrapy.downloadermiddlewares.retry.RetryMiddleware": None, # replace stock retry
"myproject.middlewares.BanAwareRetryMiddleware": 550,
}
RETRY_TIMES = 2
A prioridade importa: o middleware de proxy precisa rodar antes do HttpProxyMiddleware (750) do Scrapy, então qualquer valor abaixo de 750 funciona; 350 o mantém cedo e previsível.
| Tipo de crawl | Modo | Implementação |
|---|---|---|
| Coleta de páginas sem estado | Rotativo | Username simples, o gateway rotaciona por requisição |
| Login + crawl atrás de autenticação | Sticky por conta | -session-{account_id}, nunca rotacionar no meio do login |
| Listagens com muita paginação | Sticky por domínio, rotaciona em banimento | O middleware acima |
| Preços específicos por região | Rotativo + fixação de país | Parâmetros no estilo USERNAME-country-de |
Tratamento mais aprofundado desse trade-off: Sessões de Proxy Sticky vs Rotativas.
Os padrões do Scrapy são ajustados para crawling educado de IP único. Atrás de um pool rotativo você pode forçar muito mais — mas os limites por domínio ainda importam, porque o alvo vê o comportamento agregado:
# settings.py - sane starting point behind a residential pool
CONCURRENT_REQUESTS = 64
CONCURRENT_REQUESTS_PER_DOMAIN = 8 # what the target experiences
DOWNLOAD_DELAY = 0.25 # jitter applied per slot
RANDOMIZE_DOWNLOAD_DELAY = True # 0.5x-1.5x the delay
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 6.0
DOWNLOAD_TIMEOUT = 30
ROBOTSTXT_OBEY = True
Aumente CONCURRENT_REQUESTS_PER_DOMAIN só depois de observar sua taxa de 403 no nível atual por alguns milhares de requisições. Ir de 8 → 32 porque "os proxies rotacionam de qualquer jeito" é como as pessoas queimam GB em retentativas.
IMAGES_STORE para buscar através de um proxy de datacenter ou conexão direta se o CDN não se importar — mídia é a maior parte do seu GB e raramente é protegida.HTTPCACHE_ENABLED = True enquanto você itera nos parsers reproduz respostas do disco em vez de buscar de novo pelo pool. Essa única configuração normalmente reduz pela metade a conta de largura de banda de um projeto.COMPRESSION_ENABLED = True (padrão) — HTML com gzip é 5–10x menor na rede.407 Proxy Authentication RequiredAs credenciais não chegaram ao proxy. Coloque-as na URL (http://user:pass@host:port) em meta["proxy"] — o Scrapy faz o parse e define Proxy-Authorization para você. Definir o cabeçalho manualmente e usar credenciais na URL causa esquisitices de dupla autenticação; escolha uma.
TunnelError: Could not open CONNECT tunnelQuase sempre um host/porta digitado errado, ou um alvo HTTPS através de um endpoint que não permite CONNECT naquela porta. Verifique com curl -x fora do Scrapy primeiro.
Sua sessão sticky sobreviveu ao seu tempo de bem-vinda, ou sua taxa por domínio está quente demais. O middleware consciente de banimentos acima trata o primeiro caso; reduza CONCURRENT_REQUESTS_PER_DOMAIN para o segundo. Se for um alvo que verifica JA4, a própria pilha TLS do Scrapy pode ser o que te entrega — veja JA3/JA4 explicados para entender por que nenhum proxy resolve isso.
meta["proxy"] com a URL do gateway é toda a configuração.Sessões residenciais rotativas e sticky em um único gateway. 500MB de tráfego grátis para crawlear.
Começar Teste GrátisNovos usuários recebem 500MB ao se cadastrar, mais um bônus na primeira recarga. Oferta por tempo limitado.