Aviso legal: Este guia aborda coleta de dados públicos apenas. Sempre respeite o robots.txt, os Termos de Serviço dos sites, e legislações aplicáveis como o CFAA nos EUA e o GDPR na UE. Prefira APIs oficiais quando disponíveis. O uso de proxies para contornar bloqueios deve ser ético e proporcional.
Se você está construindo crawlers em Python com o Crawlee e seus requests começam a retornar 403 Forbidden após as primeiras 50 requisições, você não está sozinho. A rotação de proxy no Crawlee para Python é a diferença entre um crawler que coleta milhares de páginas e um que morre após minutos de execução. Este guia mostra como integrar proxies residenciais de forma idiomática usando ProxyConfiguration e SessionPool, com exemplos executáveis e padrões de produção.
Como funciona a rotação de proxy no Crawlee para Python
O Crawlee para Python (o port da stack Apify Crawlee) oferece uma arquitetura onde BeautifulSoupCrawler e PlaywrightCrawler operam sobre uma fila de requests unificada, com autoscaling automático e um SessionPool que vincula cookies, headers e fingerprints a IPs específicos. A classe ProxyConfiguration é o ponto de extensão idiomático para gerenciar proxies — ela gera URLs de proxy por sessão ou por requisição, e se integra diretamente ao SessionPool.
Quando você passa um ProxyConfiguration para o construtor do crawler, o framework automaticamente:
- Atribui um proxy a cada sessão no
SessionPool; - Mantém o mesmo IP para todas as requisições dentro de uma sessão (sticky session);
- Rotaciona para um novo IP quando a sessão é retirada via
session.retire(); - Repassa o proxy configurado para o BeautifulSoupCrawler (via
httpx) ou para o PlaywrightCrawler (via o contexto do browser).
Isso significa que a rotação de proxy não é um hack externo — é parte do ciclo de vida de sessões do framework.
Arquitetura do Crawlee: BeautifulSoupCrawler, PlaywrightCrawler e SessionPool
Antes de mergulhar na configuração de proxy, é importante entender como o Crawlee estrutura o processamento de requests.
BeautifulSoupCrawler vs PlaywrightCrawler
O BeautifulSoupCrawler usa httpx para fazer requisições HTTP e BeautifulSoup para parsing. É rápido — tipicamente 200-500ms por request — e ideal para sites estáticos ou que retornem HTML completo sem necessidade de execução de JavaScript.
O PlaywrightCrawler usa um browser headless (Chromium, Firefox, ou WebKit) via Playwright. É mais lento — 2-5 segundos por page load — mas necessário para SPAs (Single Page Applications) que renderizam conteúdo via JavaScript.
SessionPool: cookies, fingerprints e IPs
O SessionPool é o componente que torna a rotação de proxy eficaz. Cada sessão no pool carrega:
- Cookies acumulados durante a navegação;
- Headers HTTP consistentes (User-Agent, Accept, etc.);
- Um proxy vinculado — se você configurou
ProxyConfiguration, a sessão mantém o mesmo IP até ser retirada.
Isso é crítico: se você rotaciona IPs a cada request mas mantém os mesmos cookies, o site-alvo detecta a inconsistência (cookies de sessão A vindos de IP B) e bloqueia. O Crawlee resolve isso amarrando cookies ao IP dentro de cada sessão.
Autoscaling e concorrência
O Crawlee usa um AutoscaledPool que ajusta a concorrência dinamicamente com base na taxa de sucesso e na latência das requisições. Se a taxa de erro sobe, o pool reduz a concorrência automaticamente. Isso significa que proxies ruins não apenas geram erros — eles também reduzem o throughput do crawler inteiro.
ProxyConfiguration: a API idiomática do Crawlee
A classe ProxyConfiguration é o ponto de extensão correto para gerenciar proxies no Crawlee. Ela expõe o método new_url(), que retorna uma URL de proxy formatada para uma sessão específica.
Rotação round-robin vs sessões sticky
Existem duas estratégias principais:
Round-robin — cada chamada a new_url() sem session_id retorna um proxy diferente:
from crawlee.proxy_configuration import ProxyConfiguration
proxy_config = ProxyConfiguration(proxy_urls=[
'http://user:pass@gate.proxyhat.com:8080',
'http://user:pass@gate.proxyhat.com:8080',
'http://user:pass@gate.proxyhat.com:8080',
])
# Cada chamada retorna um proxy diferente
url1 = await proxy_config.new_url()
url2 = await proxy_config.new_url()
Sticky session — passe um session_id e o mesmo IP é mantido para aquela sessão:
# Mesmo session_id = mesmo IP
url = await proxy_config.new_url(session_id='abc123')
Para a maioria dos casos de produção, sessões sticky são superiores. Sites de e-commerce, redes sociais e plataformas de tickets vinculam carrinhos, sessões de login e tokens CSRF a IPs. Rotacionar IP a cada request quebra essas sessões.
Integrando ProxyConfiguration com o SessionPool
O Crawlee conecta automaticamente o ProxyConfiguration ao SessionPool. Quando você cria um crawler com proxy_configuration, o framework:
- Cria uma sessão no pool;
- Chama
proxy_configuration.new_url(session_id=session.id); - Vincula o proxy retornado à sessão;
- Usa esse proxy para todas as requisições da sessão;
- Quando
session.retire()é chamado, a sessão é descartada e uma nova é criada com um novo proxy.
Isso é o padrão idiomático — não há necessidade de gerenciar proxies manualmente no request_handler.
Por que proxies residenciais superam datacenter em sites com anti-bot
Sites protegidos por Cloudflare, DataDome, PerimeterX e Akamai Bot Manager usam listas de IPs de datacenter conhecidas. O Cloudflare mantém um banco de dados de ranges ASN associados a provedores de hospedagem (AWS, GCP, DigitalOcean, etc.) e aplica desafios automaticamente a esses IPs.
Proxies residenciais usam IPs de ISPs reais (Comcast, AT&T, Vodafone, Telefonica), tornando o tráfego indistinguível de visitantes humanos. A diferença é dramática:
| Métrica | Datacenter Proxy | Residencial Proxy |
|---|---|---|
| Taxa de bloqueio (Cloudflare) | 60-90% | 5-15% |
| Latência média | 50-200ms | 300-800ms |
| Custo por GB | $0.5-$2 | $3-$15 |
| Concorrência recomendada | 50-100 | 10-30 |
Estratégia de proxy em camadas (tiered fallback)
Para maximizar custo-eficiência, muitos crawlers de produção usam uma estratégia em camadas:
- Camada 1 — Datacenter: tenta primeiro; se receber 403, retira a sessão;
- Camada 2 — Residencial: faz retry com proxy residencial;
- Camada 3 — Mobile: último recurso para sites extremamente protegidos.
No Crawlee, isso pode ser implementado com múltiplas ProxyConfiguration instâncias e lógica de fallback no request_handler.
Exemplo executável: BeautifulSoupCrawler com ProxyConfiguration e ProxyHat
Aqui está um exemplo completo de um crawler que usa proxies residenciais do ProxyHat com sessões sticky por país:
import asyncio
import hashlib
from crawlee.beautifulsoup_crawler import BeautifulSoupCrawler
from crawlee.beautifulsoup_crawler import BeautifulSoupCrawlingContext
from crawlee.proxy_configuration import ProxyConfiguration
from crawlee.sessions import SessionPool
PROXYHAT_GATEWAY = 'gate.proxyhat.com'
PROXYHAT_PORT = 8080
PROXYHAT_USER = 'seu_usuario'
PROXYHAT_PASS = 'sua_senha'
def build_proxy_url(session_id: str, country: str = 'US') -> str:
"""Gera uma URL de proxy ProxyHat com país e sessão fixos."""
username = f'{PROXYHAT_USER}-country-{country}-sid-{session_id}'
return f'http://{username}:{PROXYHAT_PASS}@{PROXYHAT_GATEWAY}:{PROXYHAT_PORT}'
class ProxyHatConfiguration(ProxyConfiguration):
"""ProxyConfiguration customizada que gera URLs ProxyHat por sessão."""
async def new_url(self, session_id: str | None = None) -> str:
if session_id is None:
# Sem session_id = round-robin (sem flag de sessão)
username = f'{PROXYHAT_USER}-country-US'
return f'http://{username}:{PROXYHAT_PASS}@{PROXYHAT_GATEWAY}:{PROXYHAT_PORT}'
# Com session_id = sticky session
sid = hashlib.md5(session_id.encode()).hexdigest()[:12]
return build_proxy_url(sid, country='US')
async def main():
proxy_config = ProxyHatConfiguration()
crawler = BeautifulSoupCrawler(
proxy_configuration=proxy_config,
max_request_retries=3,
max_requests_per_crawl=500,
request_handler_timeout=30,
session_pool=SessionPool(
max_pool_size=20,
create_session_timeout=10,
),
)
@crawler.router.default_handler
async def handler(context: BeautifulSoupCrawlingContext) -> None:
status = context.http_response.status_code
context.log.info(
f'Status {status} | URL: {context.request.url} '
f'| Session: {context.session.id if context.session else "none"}'
)
if status == 403 or status == 429:
# Bloqueio detectado: retira a sessão e seu IP vinculado
if context.session:
context.session.retire()
context.request.retry_count += 1
raise RuntimeError(f'Bloqueado: {status}')
# Parsing normal
title = context.soup.find('title')
if title:
await context.push_data({
'url': context.request.url,
'title': title.text.strip(),
})
# Extrai links e enfileira
for link in context.soup.find_all('a', href=True):
await context.enqueue_links(
selector='a[href]',
label='LINK',
)
await crawler.run(['https://example.com'])
if __name__ == '__main__':
asyncio.run(main())
Observe os pontos-chave:
- O
ProxyHatConfigurationherda deProxyConfiguratione sobrescrevenew_url()— essa é a extensão idiomática do framework; - O
session_idé hasheado para criar um identificador estável no ProxyHat; - A flag
-country-US-sid-abc123no username instrui o gateway do ProxyHat a fixar o IP residencial para aquela sessão; - O
SessionPoolcommax_pool_size=20mantém até 20 IPs residenciais simultâneos; - O
session.retire()descarta a sessão e seu IP, forçando o Crawlee a criar uma nova com um novo proxy.
Usando curl para testar o gateway
Antes de rodar o crawler, valide a conexão com curl:
curl -x http://seu_usuario-country-US-sid-test123:sua_senha@gate.proxyhat.com:8080 \
https://httpbin.org/ip
Repita a chamada com o mesmo sid-test123 e você verá o mesmo IP. Mude o session ID e receberá um IP diferente.
Exemplo com PlaywrightCrawler e SOCKS5
Para sites que exigem rendering JavaScript, o PlaywrightCrawler é necessário. O ProxyHat também suporta SOCKS5:
from crawlee.playwright_crawler import PlaywrightCrawler
from crawlee.playwright_crawler import PlaywrightCrawlingContext
from crawlee.proxy_configuration import ProxyConfiguration
PROXYHAT_USER = 'seu_usuario'
PROXYHAT_PASS = 'sua_senha'
class ProxyHatSocks5Configuration(ProxyConfiguration):
async def new_url(self, session_id: str | None = None) -> str:
sid = session_id or 'default'
username = f'{PROXYHAT_USER}-country-US-sid-{sid}'
return f'socks5://{username}:{PROXYHAT_PASS}@gate.proxyhat.com:1080'
async def main():
proxy_config = ProxyHatSocks5Configuration()
crawler = PlaywrightCrawler(
proxy_configuration=proxy_config,
max_request_retries=2,
browser_type='chromium',
headless=True,
)
@crawler.router.default_handler
async def handler(context: PlaywrightCrawlingContext) -> None:
page = context.page
await page.wait_for_load_state('networkidle')
# Detecta CAPTCHA do Cloudflare
if await page.query_selector('#challenge-running'):
if context.session:
context.session.retire()
raise RuntimeError('Cloudflare challenge detectado')
title = await page.title()
await context.push_data({'url': context.request.url, 'title': title})
await crawler.run(['https://example.com'])
if __name__ == '__main__':
import asyncio
asyncio.run(main())
Padrões de produção: retry, concorrência e tratamento de erros
session.retire() no bloqueio
Quando um site retorna 403, 429, ou redireciona para uma página de CAPTCHA, você deve chamar session.retire(). Isso instrui o Crawlee a:
- Descartar a sessão atual (cookies, headers, IP);
- Criar uma nova sessão com um novo proxy do
ProxyConfiguration; - Fazer retry do request com a nova sessão (se
retry_count < max_request_retries).
Não use session.retire() para erros transitórios como timeouts de rede ou 500s do servidor — nesses casos, apenas deixe o retry normal acontecer.
max_request_retries e backoff
O parâmetro max_request_retries controla quantas vezes um request é retentado antes de ser marcado como falha. Para crawlers com proxies residenciais:
max_request_retries=3é um bom padrão;- Cada retry usa uma nova sessão (se a anterior foi retirada);
- O Crawlee aplica backoff exponencial automaticamente entre retries.
Concorrência via AutoscaledPool
O AutoscaledPool do Crawlee ajusta a concorrência com base na taxa de erro. Para proxies residenciais, recomendamos:
min_concurrency=5— evita overhead de warm-up;max_concurrency=20— proxies residenciais têm latência maior;max_tasks_per_minute=120— limita a taxa global.
Configurar concorrência muito alta com proxies residenciais gera mais bloqueios, não mais throughput.
Tratamento de erros no request_handler
@crawler.router.default_handler
async def handler(context: BeautifulSoupCrawlingContext) -> None:
try:
status = context.http_response.status_code
if status in (403, 429):
if context.session:
context.session.retire()
raise RuntimeError(f'Bloqueado: {status}')
if status >= 500:
# Erro do servidor: retry sem retirar sessão
raise RuntimeError(f'Erro do servidor: {status}')
# Processamento normal
data = extract_data(context.soup)
await context.push_data(data)
except Exception as e:
context.log.error(f'Erro em {context.request.url}: {e}')
raise # Re-raise para acionar retry do Crawlee
Quando NÃO usar browser crawling
Existe uma tendência a usar PlaywrightCrawler por padrão, mas isso é um erro comum. Browser crawling é 5-10x mais lento e consome muito mais recursos. Use PlaywrightCrawler apenas quando:
- O site é uma SPA que renderiza conteúdo via JavaScript (React, Vue, Angular);
- O site requer interação (cliques, scrolls, preenchimento de formulários);
- O site detecta requests sem execução de JavaScript.
Para 80% dos casos de scraping de dados públicos — SERP, e-commerce, blogs, fóruns — BeautifulSoupCrawler com proxies residenciais é suficiente e significativamente mais eficiente.
Considerações éticas e legais
Antes de qualquer implementação, considere:
- robots.txt: o Crawlee respeita robots.txt por padrão. Não desabilite isso sem motivo justificado;
- Termos de Serviço: muitos sites proíbem scraping automático em seus ToS. Violar ToS pode ter consequências legais;
- CFAA (EUA): o Computer Fraud and Abuse Act pode aplicar-se a acesso não autorizado a sistemas computacionais. Consulte um advogado se o alvo está nos EUA;
- GDPR (UE): dados pessoais coletados via scraping estão sujeitos ao GDPR. Não armazene PII sem base legal;
- Rate limits: mesmo com proxies, respeite limites razoáveis. 1-2 requests por segundo por domínio é um bom padrão;
- APIs oficiais: se o site oferece uma API, use-a. É mais estável, legal e frequentemente mais rápida.
ProxyHat: configuração e integração
O ProxyHat oferece proxies residenciais, mobile e datacenter com gateway unificado em gate.proxyhat.com. A integração com o Crawlee é direta via ProxyConfiguration.
Parâmetros do gateway
| Parâmetro | Valor |
|---|---|
| Gateway | gate.proxyhat.com |
| Porta HTTP | 8080 |
| Porta SOCKS5 | 1080 |
| Geo-targeting | -country-US, -country-DE-region-state_of_berlin-city-berlin |
| Sticky session | -sid-abc123 |
Consulte nossa documentação oficial para detalhes completos de autenticação e features. Para ver preços e planos, visite a página de pricing. Confira também nossos locations disponíveis em mais de 150 países.
Casos de uso
Os padrões deste guia aplicam-se diretamente a:
- Web scraping de e-commerce para price monitoring;
- SERP tracking para SEO e análise de concorrência;
- Coleta de dados públicos para treinamento de modelos de IA;
- QA automatizado de aplicações web em múltiplas geolocalizações.
Erros comuns e armadilhas
1. Rotacionar IP sem rotacionar sessão
Se você troca o proxy mas mantém a mesma sessão (cookies, User-Agent), o site detecta a inconsistência. Sempre use session.retire() ao trocar de proxy.
2. Concorrência muito alta com proxies residenciais
Proxies residenciais têm latência de 300-800ms. Configurar max_concurrency=100 gera timeouts e erros, não mais throughput. Mantenha entre 10-30.
3. Ignorar headers HTTP
O Crawlee envia headers padrão. Se todos os seus requests têm o mesmo User-Agent, o site detecta automação. Use httpx headers customizados ou o playwright_fingerprint_generator para variar.
4. Não monitorar a taxa de sucesso
Se sua taxa de sucesso cai abaixo de 90%, algo está errado — proxies ruins, concorrência muito alta, ou detecção anti-bot. Monitore via Statistics do Crawlee.
5. Usar datacenter proxies para sites protegidos
Cloudflare e DataDome bloqueiam IPs de datacenter em segundos. Não perca tempo tentando — vá direto para residenciais.






