Scrapy é um dos frameworks Python mais poderosos para web scraping, mas sem a configuração correta de proxy, seus scrapers podem ser bloqueados em poucos minutos de operação. Neste guia, vou mostrar todas as maneiras de integrar proxies no Scrapy: desde a configuração mais simples até métodos avançados de rotação de endereços IP com tratamento automático de erros.
O material é baseado na experiência real de scraping de grandes plataformas de e-commerce e sites protegidos. Você receberá exemplos de código prontos para usar em seus projetos.
Por que Scrapy sem proxy recebe bloqueios
Sites modernos utilizam proteção em múltiplas camadas contra scraping. Mesmo que você tenha configurado o User-Agent e delays entre requisições, seu endereço IP pode ser identificado como automatizado por vários sinais:
- Frequência de requisições: um IP fazendo 100+ requisições por minuto — um claro sinal de bot
- Padrões de comportamento: navegação sequencial em páginas sem transições aleatórias
- Ausência de JavaScript: Scrapy não executa JS, o que é facilmente detectável
- Geolocalização: acesso a partir de um data center em vez de uma rede doméstica
O resultado é um banimento por IP por várias horas ou dias. Proteções especialmente agressivas são utilizadas por marketplaces (Amazon, Wildberries, Ozon), redes sociais e sites com Cloudflare. Proxies resolvem esse problema, distribuindo requisições entre múltiplos endereços IP.
Importante: Mesmo com proxies, é necessário respeitar os limites de taxa. A velocidade recomendada: 1-3 requisições por segundo por IP. Para scraping em alta velocidade, utilize um pool de 50+ proxies com rotação.
Configuração básica de proxy no Scrapy
A maneira mais simples é especificar o proxy diretamente nas configurações do spider. Este método é adequado para testes ou scraping de pequenos volumes de dados com um único servidor proxy.
Método 1: Através de meta em Request
import scrapy
class MySpider(scrapy.Spider):
name = 'example'
start_urls = ['https://example.com']
def start_requests(self):
proxy = 'http://username:password@proxy.example.com:8080'
for url in self.start_urls:
yield scrapy.Request(
url=url,
callback=self.parse,
meta={'proxy': proxy}
)
def parse(self, response):
# Sua lógica de scraping
self.log(f'Scraped {response.url} via {response.meta["proxy"]}')
O formato do proxy depende do protocolo e do método de autenticação:
http://proxy.example.com:8080— sem autenticaçãohttp://user:pass@proxy.example.com:8080— com login/senhasocks5://user:pass@proxy.example.com:1080— proxy SOCKS5
Método 2: Configuração global em settings.py
# settings.py
# Proxy HTTP para todas as requisições
HTTPPROXY_ENABLED = True
HTTPPROXY_AUTH_ENCODING = 'utf-8'
# Configuração através de variáveis de ambiente
HTTP_PROXY = 'http://username:password@proxy.example.com:8080'
HTTPS_PROXY = 'http://username:password@proxy.example.com:8080'
Este método é conveniente para testes rápidos, mas não é adequado para produção: não há rotação de IP, se o proxy falhar, todo o scraper para, e não é possível usar diferentes proxies para diferentes sites.
Criando um Middleware de Proxy personalizado
Para scraping em produção, é necessário um middleware próprio que gerencie o pool de proxies, trate erros e troque IPs automaticamente. Aqui está uma implementação básica:
# middlewares.py
import random
from scrapy import signals
from scrapy.exceptions import NotConfigured
class RandomProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
@classmethod
def from_crawler(cls, crawler):
# Carregamos a lista de proxies das configurações
proxy_list = crawler.settings.getlist('PROXY_LIST')
if not proxy_list:
raise NotConfigured('PROXY_LIST não configurada')
return cls(proxy_list)
def process_request(self, request, spider):
# Escolhemos um proxy aleatório do pool
proxy = random.choice(self.proxy_list)
request.meta['proxy'] = proxy
spider.logger.info(f'Usando proxy: {proxy}')
def process_exception(self, request, exception, spider):
# Em caso de erro, tentamos outro proxy
proxy = random.choice(self.proxy_list)
request.meta['proxy'] = proxy
spider.logger.warning(
f'Erro no proxy, trocando para: {proxy}'
)
return request
Agora configuramos o uso do middleware em settings.py:
# settings.py
# Lista de proxies (pode ser carregada de um arquivo ou API)
PROXY_LIST = [
'http://user1:pass1@proxy1.example.com:8080',
'http://user2:pass2@proxy2.example.com:8080',
'http://user3:pass3@proxy3.example.com:8080',
# ... adicione 50+ proxies para rotação eficaz
]
# Conectando o middleware
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.RandomProxyMiddleware': 350,
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 400,
}
# Tentativas de repetição em caso de erros
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429]
Rotação de proxies: três métodos funcionais
A escolha aleatória de proxies (como no exemplo acima) é a mais simples, mas não a mais eficaz. Vamos considerar três estratégias de rotação para diferentes cenários.
Método 1: Round-robin (rotação sequencial)
Proxies são escolhidos em círculo. Adequado para distribuir a carga uniformemente:
class RoundRobinProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
self.current_index = 0
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
return cls(proxy_list)
def process_request(self, request, spider):
# Pegamos o próximo proxy em círculo
proxy = self.proxy_list[self.current_index]
self.current_index = (self.current_index + 1) % len(self.proxy_list)
request.meta['proxy'] = proxy
Método 2: Rotação inteligente com blacklist
Monitoramos proxies problemáticos e os excluímos temporariamente da rotação:
import time
from collections import defaultdict
class SmartProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
self.proxy_errors = defaultdict(int)
self.blacklist = set()
self.blacklist_timeout = 300 # 5 minutos
self.blacklist_time = {}
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
return cls(proxy_list)
def get_working_proxies(self):
# Remove da blacklist proxies cujo tempo de banimento expirou
current_time = time.time()
expired = [
proxy for proxy, ban_time in self.blacklist_time.items()
if current_time - ban_time > self.blacklist_timeout
]
for proxy in expired:
self.blacklist.discard(proxy)
self.proxy_errors[proxy] = 0
# Retorna proxies funcionais
return [p for p in self.proxy_list if p not in self.blacklist]
def process_request(self, request, spider):
working_proxies = self.get_working_proxies()
if not working_proxies:
spider.logger.error('Todos os proxies estão na blacklist!')
return
proxy = random.choice(working_proxies)
request.meta['proxy'] = proxy
def process_response(self, request, response, spider):
# Se recebemos um bloqueio — adicionamos à blacklist
if response.status in [403, 429, 503]:
proxy = request.meta.get('proxy')
self.proxy_errors[proxy] += 1
if self.proxy_errors[proxy] >= 3:
self.blacklist.add(proxy)
self.blacklist_time[proxy] = time.time()
spider.logger.warning(
f'Proxy {proxy} banido por {self.blacklist_timeout}s'
)
return response
Método 3: Rotação através da API do provedor
Muitos provedores de proxy (incluindo proxies residenciais) oferecem um endpoint rotativo — uma URL que muda automaticamente o IP a cada requisição:
# settings.py
# Endpoint único com rotação automática
ROTATING_PROXY = 'http://username:password@rotating.proxy.com:8080'
# Middleware simples
class RotatingProxyMiddleware:
def __init__(self, proxy):
self.proxy = proxy
@classmethod
def from_crawler(cls, crawler):
proxy = crawler.settings.get('ROTATING_PROXY')
return cls(proxy)
def process_request(self, request, spider):
# Uma URL, mas cada requisição vai com um novo IP
request.meta['proxy'] = self.proxy
Este é o método mais conveniente para produção: não é necessário gerenciar um pool de proxies, o provedor cuida da qualidade dos IPs e substitui os problemáticos. Funciona especialmente bem com proxies residenciais, onde o pool de IPs pode atingir milhões de endereços.
Autenticação: login/senha vs lista de IPs permitidos
Provedores de proxy oferecem dois métodos de autenticação. A escolha afeta a velocidade de conexão e a conveniência da configuração.
Autenticação User:Pass
O login e a senha são passados na URL do proxy. O Scrapy automaticamente os converte em um cabeçalho HTTP Proxy-Authorization:
proxy = 'http://username:password@proxy.example.com:8080'
request.meta['proxy'] = proxy
# O Scrapy adicionará automaticamente o cabeçalho:
# Proxy-Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Prós: funciona de qualquer IP, fácil de trocar proxies no código.
Contras: pequeno overhead em cada requisição (~50-100ms), credenciais em texto claro no código.
Autenticação IP Whitelist
Você adiciona o IP do seu servidor na whitelist do provedor, não é necessária autenticação:
proxy = 'http://proxy.example.com:8080' # sem login/senha
request.meta['proxy'] = proxy
Prós: mais rápido em 50-100ms, mais seguro (sem credenciais no código).
Contras: funciona apenas a partir de IPs específicos, é necessário atualizar a whitelist ao trocar de servidor.
Recomendação para produção:
Utilize a whitelist de IPs para scraping de servidores dedicados (AWS, Google Cloud, Hetzner). Para desenvolvimento e testes a partir de uma máquina local — autenticação user:pass.
Tratamento de erros e troca automática de IP
Mesmo com proxies de qualidade, erros ocorrerão: timeouts, recusas de conexão, bloqueios. O tratamento correto de erros é crítico para a operação estável do scraper.
Tratamento de status HTTP
class ProxyMiddleware:
def process_response(self, request, response, spider):
# Códigos para os quais precisamos trocar o proxy e repetir
ban_codes = [403, 407, 429, 503]
if response.status in ban_codes:
proxy = request.meta.get('proxy')
spider.logger.warning(
f'Obtido {response.status} de {proxy}, tentando novamente...'
)
# Marcamos para retry com um novo proxy
request.meta['dont_retry'] = False
request.meta['proxy'] = self.get_new_proxy()
return request
return response
Tratamento de exceções de rede
from twisted.internet.error import TimeoutError, ConnectionRefusedError
from scrapy.exceptions import IgnoreRequest
class ProxyMiddleware:
def process_exception(self, request, exception, spider):
# Erros de conexão com o proxy
proxy_errors = (
TimeoutError,
ConnectionRefusedError,
ConnectionLost,
)
if isinstance(exception, proxy_errors):
proxy = request.meta.get('proxy')
spider.logger.error(
f'Falha de conexão com o proxy {proxy}: {exception}'
)
# Mudamos o proxy e tentamos novamente
request.meta['proxy'] = self.get_new_proxy()
return request
# Para outros erros, usamos o tratamento padrão
return None
Detecção de bloqueios pelo conteúdo
Alguns sites retornam HTTP 200, mas mostram um captcha ou uma página de bloqueio:
class ProxyMiddleware:
def process_response(self, request, response, spider):
# Sinais de bloqueio no conteúdo
ban_indicators = [
'captcha',
'acesso negado',
'bloqueado',
'tráfego incomum',
'verificação de robô',
]
body_text = response.text.lower()
if any(indicator in body_text for indicator in ban_indicators):
spider.logger.warning(
f'Página de bloqueio detectada de {request.meta.get("proxy")}'
)
# Mudamos o proxy e repetimos
request.meta['proxy'] = self.get_new_proxy()
return request
return response
Qual tipo de proxy escolher para Scrapy
A escolha do tipo de proxy depende do site-alvo, do orçamento e da velocidade de scraping necessária. Aqui está uma comparação das principais opções:
| Tipo de proxy | Velocidade | Custo | Quando usar |
|---|---|---|---|
| Proxy de data center | Alta (50-200ms) | Baixa ($1-3/IP) | Sites simples sem proteção, APIs, ferramentas internas |
| Proxies residenciais | Média (300-800ms) | Média ($5-15/GB) | E-commerce, redes sociais, sites com Cloudflare, geotargeting |
| Proxies móveis | Baixa (500-1500ms) | Alta ($50-150/IP) | Aplicativos móveis, Instagram, TikTok, máxima proteção |
Recomendações para escolha
Para scraping de marketplaces (Amazon, Wildberries, Ozon, AliExpress) — apenas proxies residenciais. Esses sites banem agressivamente data centers. É necessária rotação e geotargeting (por exemplo, IPs russos para Wildberries).
Para scraping de sites de notícias, blogs, fóruns — proxies de data center são adequados. A proteção é mínima, a velocidade e o baixo custo de tráfego são importantes.
Para scraping de sites com Cloudflare — proxies residenciais são obrigatórios. Data centers são detectados quase instantaneamente pelo Cloudflare. Adicione à Scrapy a biblioteca cloudscraper para contornar desafios de JS.
Para scraping do Google Search, ferramentas de SEO — proxies residenciais com geotargeting. O Google mostra resultados diferentes para diferentes países e cidades.
Dica: Comece com um pool de 10 proxies residenciais para testes. Se você receber bloqueios — aumente o pool para 50-100 IPs. Para scraping em alta velocidade (1000+ requisições/minuto), utilize um endpoint rotativo com um pool de 10.000+ IPs.
Técnicas avançadas: sessões e IPs fixos
Ao fazer scraping de alguns sites, é necessário manter um único IP durante toda a sessão (autenticação, carrinho de compras, formulários de múltiplas etapas). Veja como implementar sessões fixas no Scrapy.
IP fixo para um domínio
from urllib.parse import urlparse
class StickyProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
# Dicionário: domínio -> proxy
self.domain_proxy_map = {}
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
return cls(proxy_list)
def process_request(self, request, spider):
# Extraímos o domínio da URL
domain = urlparse(request.url).netloc
# Se já houver um proxy para este domínio — usamos
if domain in self.domain_proxy_map:
proxy = self.domain_proxy_map[domain]
else:
# Caso contrário, escolhemos um novo e lembramos
proxy = random.choice(self.proxy_list)
self.domain_proxy_map[domain] = proxy
spider.logger.info(f'Atribuído {proxy} a {domain}')
request.meta['proxy'] = proxy
IP fixo com timeout de sessão
Uma variante mais avançada: o proxy é vinculado ao domínio por um determinado tempo (por exemplo, 10 minutos), depois é trocado:
import time
from urllib.parse import urlparse
class SessionProxyMiddleware:
def __init__(self, proxy_list, session_timeout=600):
self.proxy_list = proxy_list
self.session_timeout = session_timeout # 10 minutos
# Dicionário: domínio -> (proxy, tempo de criação)
self.sessions = {}
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
timeout = crawler.settings.getint('PROXY_SESSION_TIMEOUT', 600)
return cls(proxy_list, timeout)
def get_proxy_for_domain(self, domain):
current_time = time.time()
# Verificamos se há uma sessão ativa
if domain in self.sessions:
proxy, created_at = self.sessions[domain]
# Se a sessão não expirou — usamos o mesmo proxy
if current_time - created_at < self.session_timeout:
return proxy
# Criamos uma nova sessão com um novo proxy
new_proxy = random.choice(self.proxy_list)
self.sessions[domain] = (new_proxy, current_time)
return new_proxy
def process_request(self, request, spider):
domain = urlparse(request.url).netloc
proxy = self.get_proxy_for_domain(domain)
request.meta['proxy'] = proxy
Integração com Cookie Middleware
Para sessões completas, é necessário sincronizar proxies e cookies. O Scrapy armazena cookies separadamente para cada domínio, mas ao trocar de proxy, é necessário limpar os cookies:
# settings.py
# Habilitamos o cookie middleware
COOKIES_ENABLED = True
COOKIES_DEBUG = False
# Middleware para sincronizar proxies e cookies
class ProxyCookieMiddleware:
def process_request(self, request, spider):
# Obtém o proxy atual
current_proxy = request.meta.get('proxy')
# Se o proxy mudou — limpamos os cookies
previous_proxy = request.meta.get('previous_proxy')
if previous_proxy and previous_proxy != current_proxy:
# Limpamos os cookies para este domínio
jar = spider.crawler.engine.downloader.middleware.middlewares[0].jars
domain = urlparse(request.url).netloc
if domain in jar:
jar[domain].clear()
spider.logger.info(f'Cookies limpos para {domain}')
request.meta['previous_proxy'] = current_proxy
Conclusão
A configuração correta de proxies no Scrapy é a base para um scraping estável sem bloqueios. Nós cobrimos todos os aspectos-chave: desde a integração básica até técnicas avançadas de rotação e gerenciamento de sessões.
Principais conclusões:
- Para produção, utilize um middleware personalizado com rotação inteligente e blacklist de IPs problemáticos
- Trate todos os tipos de erros: status HTTP, exceções de rede, bloqueios por conteúdo
- Escolha o tipo de proxy adequado para a tarefa: data centers para sites simples, residenciais para sites protegidos
- Para sites com autenticação, utilize sessões fixas vinculando o proxy ao domínio
- Comece com um pool de 10-50 proxies, escalando conforme o aumento da carga
Se você planeja fazer scraping de sites protegidos (marketplaces, redes sociais, sites com Cloudflare), recomendo usar proxies residenciais — eles oferecem máxima anonimidade e mínimo risco de bloqueios. Para scraping em alta velocidade, escolha provedores com endpoint rotativo e um pool de 10.000 endereços IP.
Todos os exemplos de código deste artigo foram testados no Scrapy 2.x e estão prontos para uso em produção. Adapte-os para suas necessidades e escale conforme o crescimento do projeto.