Rotación de proxies en Crawlee para Python: por qué importa y cómo implementarla
Si construye crawlers en Python con Crawlee, tarde o temprano se enfrentará a bloqueos por IP, desafíos CAPTCHA y rate limiting. La rotación de proxies en Crawlee para Python no es solo cambiar de IP en cada petición: se trata de integrar la rotación de forma idiomática con el SessionPool, el ProxyConfiguration y el pool de autoscaling que el framework ya proporciona. Esta guía cubre cómo cablear proxies residenciales de ProxyHat a Crawlee, mantener sesiones pegadas a IPs estables y retirar sesiones cuando un objetivo bloquea.
Antes de profundizar, una advertencia importante: esta guía asume que extrae datos públicos de sitios que lo permiten. Respete robots.txt, los términos de servicio y las leyes aplicables como el CFAA en EE. UU. o el GDPR en la UE. Siempre prefiera APIs oficiales cuando estén disponibles.
Arquitectura de Crawlee: request queue, autoscaling y SessionPool
Crawlee para Python organiza el scraping en capas que conviene entender antes de tocar proxies:
- Request queue unificada: tanto
BeautifulSoupCrawlercomoPlaywrightCrawlerconsumen la misma cola de peticiones. Esto permite mezclar crawlers HTTP ligeros con navegadores headless en el mismo proyecto. - AutoscaledPool: gestiona la concurrencia automáticamente según el uso de CPU/memoria. Puede configurar
min_concurrencyymax_concurrencypara controlar el ritmo. - SessionPool: cada sesión agrupa cookies, fingerprints y —críticamente— una dirección IP asociada vía
ProxyConfiguration. Cuando una sesión se bloquea, se retira y se crea una nueva con una IP distinta.
El SessionPool es el componente que vincula todo. Si rota IPs sin respetar el ciclo de vida de las sesiones, pierde cookies y fingerprints que el sitio ya aceptaba, lo que genera bloqueos evitables.
SessionPool y ProxyConfiguration: la relación clave
Cuando pasa un ProxyConfiguration al crawler, Crawlee llama a proxy_configuration.new_url(session_id=...) para cada sesión nueva. El session_id se mantiene estable durante toda la vida de esa sesión, lo que significa que la IP asociada también lo es —siempre que su proveedor de proxies soporte sesiones pegajosas.
Este es el patrón idiomático: una sesión = una IP residencial estable. No rota IPs por petición a ciegas; rota por sesión, y retira la sesión cuando detecta un bloqueo.
ProxyConfiguration en Crawlee: rotación por sesión vs round-robin
La clase ProxyConfiguration es el punto de extensión idiomático para proxies en Crawlee. Acepta una lista de URLs de proxy o una función generadora, y expone new_url(session_id=...) que devuelve una URL de proxy para esa sesión.
Hay dos estrategias principales:
| Estrategia | Cómo funciona | Cuándo usarla | Pros | Contras |
|---|---|---|---|---|
| Rotación por sesión (sticky) | Una IP por sesión; nueva IP al retirar la sesión | Login, carritos, dashboards, scraping con estado | Mantiene cookies y fingerprints consistentes | Requiere proveedor con sesiones pegajosas |
| Round-robin por petición | IP distinta en cada request sin vincular a sesión | Scraping masivo sin estado, SERP tracking simple | Distribución de carga simple | Pierde cookies entre peticiones; más fácil de detectar |
Para la mayoría de objetivos con protección anti-bot (Cloudflare, DataDome, PerimeterX), la rotación por sesión es superior porque los sistemas de detección analizan la consistencia entre IP, cookies y comportamiento.
Por qué los proxies residenciales superan a los datacenter en objetivos protegidos
Los proxies datacenter son rápidos y baratos, pero sus rangos de IP están etiquetados como hosting en bases de datos como ASN de RIPE/APNIC. Cloudflare y DataDome filtran estos rangos agresivamente. Un proxy residencial usa IPs asignadas a ISPs reales (movistar, comcast, vodafone), por lo que aparecen como tráfico de usuarios legítimos.
Datos concretos del sector: los proxies datacenter típicos tienen una tasa de éxito del 40-60% en sitios con Cloudflare activo, mientras que los residenciales alcanzan 85-95% en los mismos objetivos. La latencia es mayor —300-800ms vs 50-100ms en datacenter— pero la fiabilidad compensa.
Una estrategia de producción es proxy en niveles: usar residenciales para el primer intento y caer a datacenter rotativo para peticiones menos sensibles o reintentos de bajo riesgo. Esto equilibra coste y fiabilidad.
Implementación: BeautifulSoupCrawler con ProxyConfiguration y ProxyHat
Veamos un ejemplo completo. Usaremos BeautifulSoupCrawler con ProxyConfiguration apuntando a ProxyHat, generando usernames por sesión con el formato user-country-US-session-abc123.
Paso 1: Definir el ProxyConfiguration
from crawlee import ProxyConfiguration
from crawlee.beautifulsoup_crawler import BeautifulSoupCrawler
import uuid
PROXYHAT_GATEWAY = "gate.proxyhat.com:8080"
PROXYHAT_USER = "user"
PROXYHAT_PASS = "pass"
def build_proxy_url(session_id: str, country: str = "US") -> str:
"""Genera una URL de proxy residencial con sesión pegajosa."""
username = f"{PROXYHAT_USER}-country-{country}-session-{session_id}"
return f"http://{username}:{PROXYHAT_PASS}@{PROXYHAT_GATEWAY}"
# ProxyConfiguration que genera URLs dinámicas por sesión
proxy_config = ProxyConfiguration(
proxy_urls=[
# URL base; el session_id se inyecta en new_url
f"http://{PROXYHAT_USER}:{PROXYHAT_PASS}@{PROXYHAT_GATEWAY}"
],
)
Paso 2: Subclasear ProxyConfiguration para sesiones pegajosas
El patrón idiomático en Crawlee es heredar de ProxyConfiguration y sobrescribir new_url para inyectar el session_id en el username de ProxyHat:
from crawlee import ProxyConfiguration
class ProxyHatProxyConfiguration(ProxyConfiguration):
"""ProxyConfiguration que genera URLs residenciales por sesión."""
def __init__(self, user: str, password: str, country: str = "US", **kwargs):
self._user = user
self._password = password
self._country = country
super().__init__(proxy_urls=["http://placeholder"], **kwargs)
async def new_url(self, session_id: str | None = None) -> str:
sid = session_id or uuid.uuid4().hex[:12]
username = f"{self._user}-country-{self._country}-session-{sid}"
return f"http://{username}:{self._password}@gate.proxyhat.com:8080"
Paso 3: Cablear al crawler con SessionPool
from crawlee.beautifulsoup_crawler import BeautifulSoupCrawler, BeautifulSoupCrawlingContext
from crawlee.sessions import SessionPool
import uuid
proxy_config = ProxyHatProxyConfiguration(
user="user",
password="pass",
country="US",
)
crawler = BeautifulSoupCrawler(
proxy_configuration=proxy_config,
max_request_retries=3,
max_session_rotations=5,
max_concurrency=10,
request_handler_timeout=60,
)
@crawler.router.default_handler
async def handler(context: BeautifulSoupCrawlingContext) -> None:
session = context.session
context.log.info(
f"Procesando {context.request.url} "
f"con sesión {session.id if session else 'N/A'}"
)
# Extraer datos
title = context.soup.find("title")
if title:
await context.push_data({
"url": context.request.url,
"title": title.get_text(strip=True),
})
# Detectar bloqueos por contenido
if context.soup.find(text=lambda t: t and "Access Denied" in t):
context.log.warning(f"Bloqueo detectado en {context.request.url}")
if session:
session.retire()
raise RuntimeError("Bloqueo detectado, retirando sesión")
await crawler.run(["https://example.com"])
Este patrón es el núcleo de la rotación idiomática: session.retire() descarta la sesión actual, y Crawlee crea una nueva que llamará a proxy_config.new_url() con un session_id distinto, obteniendo una IP residencial nueva.
PlaywrightCrawler con proxies residenciales
Para objetivos que requieren renderizado JavaScript o que detectan HTTP simple, use PlaywrightCrawler. La integración de proxies es idéntica:
from crawlee.playwright_crawler import PlaywrightCrawler, PlaywrightCrawlingContext
playwright_crawler = PlaywrightCrawler(
proxy_configuration=proxy_config, # mismo ProxyConfiguration
max_request_retries=3,
max_concurrency=5, # menor concurrencia: headless es costoso
browser_type="chromium",
headless=True,
request_handler_timeout=90,
)
@playwright_crawler.router.default_handler
async def browser_handler(context: PlaywrightCrawlingContext) -> None:
page = context.page
await page.goto(context.request.url, wait_until="domcontentloaded")
# Verificar si Cloudflare challenge está presente
content = await page.content()
if "Just a moment" in content or "cf-challenge" in content:
context.log.warning(f"Cloudflare challenge en {context.request.url}")
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 playwright_crawler.run(["https://example.com"])
Con Playwright, la concurrencia debe ser menor: cada navegador consume 150-300 MB de RAM. Un servidor con 8 GB RAM maneja cómodamente 5-8 concurrencias headless.
Patrones de producción: manejo de errores, concurrencia y reintentos
Retirar sesiones en bloqueos
El método session.retire() es la herramienta principal para rotar IPs en respuesta a bloqueos. Llámelo cuando detecte:
- Páginas de error 403, 429 o 503.
- Contenido con "Access Denied", "Just a moment", o títulos de challenge.
- Redirecciones a páginas de CAPTCHA.
- Timeouts repetidos (posible IP en blacklist temporal).
Configurar max_request_retries y max_session_rotations
Crawlee reintenta peticiones fallidas hasta max_request_retries veces. Cada reintento puede usar una sesión nueva si la anterior se retiró. max_session_rotations limita cuántas veces se rota la sesión antes de abandonar la petición:
crawler = BeautifulSoupCrawler(
proxy_configuration=proxy_config,
max_request_retries=4, # reintentos por petición
max_session_rotations=3, # rotaciones de sesión por reintento
max_concurrency=15,
request_handler_timeout=45,
)
Concurrencia via AutoscaledPool
El AutoscaledPool interno ajusta la concurrencia según recursos. Para proxies residenciales, configure max_concurrency con cuidado: demasiada concurrencia desde la misma IP puede disparar rate limits incluso con sesiones válidas. Una regla práctica: 5-10 concurrencias por pool de 50-100 IPs residenciales.
Manejo de errores en request_handler
Use try/except dentro del handler para errores de red y timeouts. Lanzar una excepción hace que Crawlee reintente; usar session.retire() antes de lanzar asegura que el reintento use una IP nueva:
@crawler.router.default_handler
async def handler(context: BeautifulSoupCrawlingContext) -> None:
try:
response = await context.http_client.send(
context.request, session=context.session
)
except Exception as e:
context.log.error(f"Error de red en {context.request.url}: {e}")
if context.session:
context.session.retire()
raise # Crawlee reintenta con sesión nueva
if response.status_code in (403, 429):
context.log.warning(f"HTTP {response.status_code} en {context.request.url}")
if context.session:
context.session.retire()
raise RuntimeError(f"Bloqueo HTTP {response.status_code}")
Errores comunes y casos límite
1. Rotar IPs sin retirar la sesión
Si cambia la URL del proxy manualmente sin llamar session.retire(), la sesión mantiene cookies y fingerprints de la IP anterior. El sitio ve cookies válidas desde una IP nueva — comportamiento sospechoso. Siempre retire la sesión antes de rotar.
2. Confiar en round-robin para sitios con estado
Sitios de e-commerce, redes sociales y dashboards requieren cookies de sesión. Rotar IP por petición sin sesiones pegajosas rompe el estado y genera bloqueos. Use sesiones pegajosas con session_id estable.
3. Ignorar el header de proxy
Crawlee expone context.proxy_info en el handler. Úselo para verificar qué IP se asignó y para logging:
proxy_info = await proxy_config.new_url(session_id=context.session.id)
context.log.info(f"Usando proxy: {proxy_info}")
4. No configurar timeouts de proxy
Los proxies residenciales tienen latencia variable. Configure request_handler_timeout generoso (45-90s) y maneje timeouts explícitamente. Un timeout sin manejar consume reintentos innecesariamente.
Configuración específica de ProxyHat
ProxyHat soporta geo-targeting y sesiones pegajosas vía el username. El formato es:
# HTTP residencial con país y sesión
http://user-country-US-session-abc123:pass@gate.proxyhat.com:8080
# HTTP residencial con país, ciudad y sesión
http://user-country-DE-city-berlin-session-xyz789:pass@gate.proxyhat.com:8080
# SOCKS5 residencial
socks5://user-country-US-session-abc123:pass@gate.proxyhat.com:1080
El session_id determina qué IP residencial se asigna. El mismo session_id devuelve la misma IP mientras la sesión esté activa (típicamente 10-30 minutos según configuración). Consulte la documentación de ProxyHat para detalles de TTL de sesiones.
Para ver los países y ciudades disponibles, visite /es/locations. Para comparar planes de proxies residenciales y datacenter, vea /es/pricing.
Cuándo NO usar navegadores headless
Playwright es potente pero costoso. Antes de usar PlaywrightCrawler, considere:
- ¿El contenido requiere JavaScript? Si los datos están en el HTML inicial, use
BeautifulSoupCrawler— es 10-20x más rápido y consume 90% menos memoria. - ¿El sitio usa Cloudflare? Cloudflare puede bloquear tanto HTTP como headless, pero los challenges interactivos solo se resuelven con navegador. Si no hay challenge, HTTP + residenciales suele bastar.
- ¿Necesita interacción? Clicks, scroll infinito y formularios requieren Playwright. Parsing estático no.
Una estrategia híbrida efectiva: use BeautifulSoupCrawler con proxies residenciales para la mayoría de peticiones, y reserve PlaywrightCrawler para URLs que devuelvan challenges o requieran renderizado.
Escalado: contenedores, flotas headless y concurrencia
Para escalar crawlers en producción:
- Contenerización: empaquete el crawler en Docker. Cada contenedor ejecuta una instancia con su propio
SessionPoolyProxyConfiguration. - Flota headless: para Playwright, use
browser_typeylaunch_optionspara optimizar memoria. Considere--disable-gpu --no-sandbox --disable-dev-shm-usageen contenedores. - Concurrencia distribuida: si usa múltiples contenedores, asegure que cada uno use un rango distinto de
session_idpara evitar colisiones de IP. ProxyHat asigna IPs porsession_id, por lo que IDs únicos garantizan IPs únicas. - Monitoring: registre la tasa de éxito por sesión. Si una sesión falla más de 2 veces consecutivas, retírela proactivamente.
Para casos de uso como web scraping a escala o SERP tracking, la combinación de Crawlee + proxies residenciales de ProxyHat ofrece una base sólida y idiomática.
Consideraciones éticas y legales
El scraping web opera en un área legal compleja. Puntos clave:
- Datos públicos únicamente: no acceda a áreas que requieran autenticación sin autorización explícita.
- Respete robots.txt: Crawlee puede respetar robots.txt configurando
respect_robots_txt=True. - Rate limits razonables: incluso con proxies, imponga límites. 1-2 peticiones por segundo por dominio es un mínimo ético.
- GDPR y CCPA: datos personales de usuarios de la UE o California tienen protección legal. No almacene PII sin base legal.
- Preferir APIs oficiales: si el sitio ofrece una API, úsela. Es más fiable, legal y rápido.
El CFAA (Computer Fraud and Abuse Act) en EE. UU. penaliza el acceso no autorizado a sistemas informáticos. Interpretaciones judiciales recientes (caso Van Buren, 2021) han matizado qué cuenta como "acceso no autorizado", pero la prudencia sigue siendo necesaria.
Key Takeaways
- La rotación idiomática en Crawlee usa
ProxyConfiguration+SessionPool: una sesión = una IP residencial estable.- Subclase
ProxyConfigurationy sobrescribanew_url(session_id=...)para inyectar elsession_iden el username de ProxyHat.- Llame
session.retire()al detectar bloqueos; Crawlee creará una sesión nueva con una IP nueva automáticamente.- Los proxies residenciales superan a los datacenter en objetivos con Cloudflare/DataDome (85-95% vs 40-60% de éxito).
- Use
BeautifulSoupCrawlerpor defecto; reservePlaywrightCrawlerpara challenges interactivos o contenido JS.- Configure
max_request_retries=3-4ymax_session_rotations=3-5para equilibrar fiabilidad y coste.- Respete robots.txt, términos de servicio y leyes de protección de datos. Prefiera APIs oficiales cuando existan.
Preguntas frecuentes
¿Qué es la rotación de proxies en Crawlee para Python?
Es la integración idiomática de proxies en Crawlee mediante la clase ProxyConfiguration y el SessionPool. En lugar de rotar IPs manualmente, Crawlee asigna una URL de proxy a cada sesión vía proxy_configuration.new_url(session_id=...), manteniendo la IP estable mientras la sesión esté activa y rotando automáticamente cuando la sesión se retira por bloqueos.
¿Por qué importa la rotación de proxies en Crawlee para Python para usuarios de proxies?
Porque vincula la rotación de IPs al ciclo de vida de las sesiones, preservando cookies y fingerprints. Rotar IPs sin gestionar sesiones rompe la consistencia que los sitios esperan, generando más bloqueos. El SessionPool de Crawlee automatiza la creación y retirada de sesiones, haciendo la rotación segura y eficiente.
¿Qué tipo de proxy funciona mejor para la rotación en Crawlee para Python?
Los proxies residenciales son superiores para objetivos con protección anti-bot como Cloudflare o DataDome, porque usan IPs de ISPs reales que aparecen como tráfico legítimo. Los proxies datacenter son más rápidos y baratos, pero sus rangos de IP están etiquetados como hosting y se filtran agresivamente. Para sesiones pegajosas, los residenciales con session_id ofrecen la mejor combinación de fiabilidad y consistencia.
¿Cómo evitar bloqueos al implementar rotación de proxies en Crawlee para Python?
Use sesiones pegajosas (no round-robin ciego), llame session.retire() al detectar HTTP 403/429 o páginas de challenge, configure max_session_rotations=3-5, mantenga concurrencia moderada (5-10 por pool), use proxies residenciales con geo-targeting apropiado, e implemente detección de bloqueos en el request_handler verificando contenido y códigos de estado.
¿Puedo usar SOCKS5 con Crawlee y ProxyHat?
Sí. ProxyHat soporta SOCKS5 en gate.proxyhat.com:1080. En ProxyConfiguration, use el formato socks5://user-country-US-session-abc123:pass@gate.proxyhat.com:1080. SOCKS5 es útil para conexiones que requieren tunneling completo, aunque HTTP suele ser suficiente para la mayoría de crawlers.






