Si estás construyendo crawlers en Python con Crawlee, ya sabes que el framework maneja colas de peticiones, reintentos y autocarga de forma nativa. Pero cuando tu objetivo está detrás de Cloudflare o DataDome, la rotación de proxies en Crawlee para Python se convierte en el factor que determina si tu scraper llega a producción o muere en staging. Esta guía cubre cómo integrar proxies residenciales de forma idiomática usando ProxyConfiguration, SessionPool y patrones de producción probados.
Aviso legal: Esta guía cubre técnicas para extraer datos públicos. No uses estos métodos para acceder a datos protegidos, violar términos de servicio o eludir controles de acceso. El CFAA en EE.UU. y el GDPR en la UE pueden aplicar. Consulta siempre a un abogado antes de scraping a escala.
Arquitectura de Crawlee para Python: la base del crawlee python proxy
Crawlee para Python organiza el scraping en tres capas que trabajan juntas: un crawler (BeautifulSoupCrawler o PlaywrightCrawler), una cola de peticiones unificada (RequestQueue) y un SessionPool que gestiona cookies, fingerprints y proxies por sesión. Entender cómo interactúan es clave para configurar la rotación de proxies correctamente.
BeautifulSoupCrawler vs PlaywrightCrawler
El BeautifulSoupCrawler hace peticiones HTTP simples y parsea HTML estático. Es rápido y ligero: procesa fácilmente 200–500 páginas por minuto con concurrencia moderada. El PlaywrightCrawler lanza un navegador headless real, ejecuta JavaScript y mantiene estado entre navegaciones — pero consume 10x más memoria por página y es más lento.
La regla general: usa BeautifulSoup cuando el HTML del servidor contiene los datos que necesitas. Reserva Playwright para SPAs que renderizan todo con JavaScript en el cliente.
SessionPool: el pegamento entre cookies, fingerprints e IPs
El SessionPool es donde Crawlee une proxies con identidad. Cada sesión mantiene:
- Cookies acumuladas durante la navegación.
- Un ID de sesión que se vincula a una IP de proxy específica.
- Estado de "salud" — si la sesión recibe demasiados errores, se retira.
- Contadores de uso que el autocargador monitoriza para escalar.
Cuando configuras ProxyConfiguration, Crawlee asigna automáticamente un proxy a cada sesión nueva. Si la sesión se retira (por bloqueo o errores), la próxima petición recibe una sesión nueva con un proxy nuevo. Este es el mecanismo nativo de crawlee proxy rotation.
Autoscaling y concurrencia
El AutoscaledPool de Crawlee ajusta la concurrencia dinámicamente según el rendimiento. Si las peticiones fallan o tardan demasiado, reduce el número de peticiones simultáneas. Esto significa que si tus proxies son lentos o se bloquean, Crawlee baja la concurrencia automáticamente — una red de seguridad que evita saturar el objetivo.
La clase ProxyConfiguration: rotación idiomática de proxies
La clase ProxyConfiguration es el punto de entrada idiomático para gestionar proxies en Crawlee. En lugar de inyectar proxies manualmente en cada petición, configuras la rotación una vez y el framework la aplica de forma transparente.
Rotación round-robin vs sesión fija
Hay dos estrategias fundamentales:
- Round-robin: cada petición recibe una IP nueva. Útil para scraping masivo de páginas independientes sin estado.
- Sesión fija (sticky): la misma IP se mantiene durante toda la sesión. Necesario para flujos de login, carritos de compra o paginación donde el servidor valida la IP contra las cookies.
En Crawlee, la sesión fija se implementa con proxy_configuration.new_url(session_id=...). El session_id se pasa al proveedor de proxies, que mantiene la misma IP para ese ID durante una ventana configurable.
Conexión con ProxyHat
ProxyHat permite codificar el país, ciudad y ID de sesión directamente en el nombre de usuario. Esto se integra perfectamente con el patrón new_url(session_id=...) de Crawlee:
from crawlee import ProxyConfiguration
import urllib.parse
class ProxyHatConfig:
GATEWAY = 'gate.proxyhat.com'
PORT = 8080
BASE_USER = 'user'
PASSWORD = 'pass'
def build_url(self, session_id: str, country: str = 'US') -> str:
username = f'{self.BASE_USER}-country-{country}-session-{session_id}'
encoded = urllib.parse.quote(username, safe='')
return f'http://{encoded}:{self.PASSWORD}@{self.GATEWAY}:{self.PORT}'
def to_crawlee_config(self) -> ProxyConfiguration:
async def new_url_fn(session_id: str | None = None):
sid = session_id or 'default'
return self.build_url(sid)
return ProxyConfiguration(new_url_function=new_url_fn)
Aquí, new_url_function es el hook que Crawlee llama cada vez que necesita un proxy para una sesión. El session_id que Crawlee pasa se convierte en parte del nombre de usuario de ProxyHat, garantizando que la misma sesión siempre reciba la misma IP residencial.
Por qué los proxies residenciales superan a los datacenter en Cloudflare y DataDome
Los sistemas anti-bot modernos como Cloudflare Bot Management y DataDome no solo miran el User-Agent. Analizan la reputación de la IP, el ASN, el patrón de tráfico y la consistencia entre cookies, headers y comportamiento.
| Característica | Proxy residencial | Proxy datacenter |
|---|---|---|
| Origen de IP | ISP real (hogares) | Centros de datos (AWS, DigitalOcean) |
| Puntuación de fraude | Baja (< 10 típicamente) | Alta (50–90 en IP2Proxy) |
| Detección por Cloudflare | Difícil | Trivial |
| Latencia media | 800–2000 ms | 50–200 ms |
| Coste por GB | $3–8/GB | $0.5–2/GB |
| Ideal para | Sitios anti-bot, login, e-commerce | Scraping masivo de APIs públicas |
La diferencia clave: las IPs residenciales provienen de ASN de ISPs reales (Comcast, AT&T, Movistar). Cloudflare las trata como tráfico humano legítimo porque millones de usuarios reales comparten esos mismos rangos. Las IPs datacenter, en cambio, pertenecen a rangos que Cloudflare marca como "no residencial" en su base de datos, y un solo request desde ahí puede activar un CAPTCHA.
Estrategia de proxies escalonada (tiered fallback)
En producción, no confíes en un solo tipo de proxy. Una estrategia escalonada combina:
- Nivel 1 (residencial): intenta primero con proxies residenciales para maximizar la tasa de éxito.
- Nivel 2 (móvil): si el residencial falla, cae a proxies móviles (4G/5G), que tienen aún mejor reputación.
- Nivel 3 (datacenter): último recurso para objetivos sin protección anti-bot, donde la velocidad importa más que la evasión.
En Crawlee, esto se implementa con un ProxyConfiguration personalizado que cambia de nivel según el historial de bloqueos de cada sesión.
Ejemplo ejecutable: BeautifulSoupCrawler con rotación residencial
Este ejemplo muestra un crawler completo que usa ProxyConfiguration con endpoints de ProxyHat parametrizados por país y sesión:
import asyncio
import urllib.parse
from crawlee import ProxyConfiguration
from crawlee.beautifulsoup_crawler import BeautifulSoupCrawler
from crawlee.beautifulsoup_crawler import BeautifulSoupCrawlingContext
class ProxyHatProxyConfig:
"""Generador de URLs de proxy por sesión para Crawlee."""
GATEWAY = 'gate.proxyhat.com'
PORT = 8080
BASE_USER = 'user'
PASSWORD = 'pass'
def build_proxy_url(self, session_id: str, country: str = 'US') -> str:
username = f'{self.BASE_USER}-country-{country}-session-{session_id}'
encoded = urllib.parse.quote(username, safe='')
return f'http://{encoded}:{self.PASSWORD}@{self.GATEWAY}:{self.PORT}'
def get_new_url_function(self):
async def new_url(session_id: str | None = None):
sid = session_id or 'rotating'
return self.build_proxy_url(sid, country='US')
return new_url
async def main():
proxy_config = ProxyConfiguration(
new_url_function=ProxyHatProxyConfig().get_new_url_function()
)
crawler = BeautifulSoupCrawler(
proxy_configuration=proxy_config,
max_request_retries=3,
max_requests_per_crawl=500,
request_handler_timeout=30,
)
@crawler.router.default_handler
async def handle_page(context: BeautifulSoupCrawlingContext) -> None:
title = context.soup.find('title')
context.log.info(
f'URL: {context.request.url} | '
f'Sesión: {context.session.id if context.session else "N/A"} | '
f'Título: {title.text.strip() if title else "sin título"}'
)
# Extraer enlaces de paginación
next_link = context.soup.select_one('a.next-page')
if next_link and next_link.get('href'):
await context.enqueue_links(
selector='a.next-page',
label='PAGINATION'
)
@crawler.router.handler('PAGINATION')
async def handle_pagination(context: BeautifulSoupCrawlingContext) -> None:
# Detectar bloqueo por Cloudflare
if context.http_response.status_code == 403:
context.log.warning(f'Bloqueo detectado en {context.request.url}')
if context.session:
context.session.retire()
raise context.request_handler_error
items = context.soup.select('.product-card')
for item in items:
name = item.select_one('.product-name')
price = item.select_one('.price')
if name and price:
await context.push_data({
'product': name.text.strip(),
'price': price.text.strip(),
'url': context.request.url,
})
await crawler.run(['https://example.com/products'])
if __name__ == '__main__':
asyncio.run(main())
Lo que hace este código: cada sesión nueva recibe un session_id único generado por Crawlee. Ese ID se incrusta en el nombre de usuario de ProxyHat como -session-abc123, lo que hace que el gateway de ProxyHat asigne una IP residencial de EE.UU. y la mantenga estable para esa sesión. Si la sesión se retira por un bloqueo (403), la próxima petición crea una sesión nueva con un ID nuevo y, por tanto, una IP nueva.
Generación de IDs de sesión con el SDK de ProxyHat
Para un control más fino, puedes generar IDs de sesión únicos por tarea o por batch:
import uuid
from datetime import datetime
class SessionManager:
"""Genera IDs de sesión únicos para vincular a IPs residenciales."""
def __init__(self, prefix: str = 'crawl'):
self.prefix = prefix
self._counter = 0
def new_session_id(self) -> str:
self._counter += 1
timestamp = datetime.utcnow().strftime('%Y%m%d%H%M')
unique = uuid.uuid4().hex[:8]
return f'{self.prefix}-{timestamp}-{self._counter}-{unique}'
def new_url_function(self, country: str = 'US'):
async def new_url(session_id: str | None = None):
sid = session_id or self.new_session_id()
username = f'user-country-{country}-session-{sid}'
encoded = urllib.parse.quote(username, safe='')
return f'http://{encoded}:pass@gate.proxyhat.com:8080'
return new_url
# Uso con Crawlee
session_mgr = SessionManager(prefix='prod')
proxy_config = ProxyConfiguration(
new_url_function=session_mgr.new_url_function(country='US')
)
Este patrón permite trazar cada IP hasta un batch específico de scraping, lo que facilita el debugging cuando un objetivo bloquea un rango de IPs.
Patrones de producción: session.retire(), reintentos y concurrencia
Retirar sesiones bloqueadas
El método session.retire() es el mecanismo idiomático de Crawlee para descartar una sesión sin destruir el crawler. Cuando lo llamas:
- La sesión se marca como "retirada" y no se reutiliza.
- El proxy asociado se libera.
- La próxima petición recibe una sesión nueva con un proxy nuevo.
- Las cookies y el estado de la sesión vieja se descartan.
Es crítico llamar retire() en cuanto detectes un bloqueo, no después de varios reintentos. Un proxy bloqueado que sigue recibiendo peticiones empeora la reputación de esa IP y puede provocar bloqueos en cascada:
@crawler.router.default_handler
async def handle_page(context: BeautifulSoupCrawlingContext) -> None:
response = context.http_response
# Detección de bloqueos comunes
if response.status_code in (403, 429):
context.log.warning(
f'Bloqueo HTTP {response.status_code} en {context.request.url}'
)
if context.session:
context.session.retire()
raise context.request_handler_error
# Detección de página de CAPTCHA
if 'cf-challenge' in (response.headers.get('server', '') or ''):
context.log.warning(f'CAPTCHA de Cloudflare en {context.request.url}')
if context.session:
context.session.retire()
raise context.request_handler_error
# Procesamiento normal
await context.push_data({'url': context.request.url})
Configuración de max_request_retries y backoff
El parámetro max_request_retries controla cuántas veces Crawlee reintenta una petición antes de descartarla. Con proxies residenciales, un valor de 3–5 es razonable: suficiente para superar bloqueos temporales, pero no tanto como para desperdiciar ancho de banda caro.
Crawlee aplica automáticamente un backoff exponencial entre reintentos. La primera vez espera ~1 segundo, la segunda ~2 segundos, y la tercera ~4 segundos. Esto da tiempo a que el servidor anti-bot relaje sus reglas o a que el proxy rotado asigne una IP nueva.
Concurrencia con el autoscaled pool
El AutoscaledPool de Crawlee mide el rendimiento continuamente. Sus parámetros clave son:
min_concurrency: mínimo de peticiones simultáneas (default: 1).max_concurrency: máximo de peticiones simultáneas (default: 50).max_cpu_usage: porcentaje de CPU que dispara reducción de concurrencia (default: 0.9).max_memory_usage: fracción de memoria que dispara reducción (default: 0.9).
Con proxies residenciales, te recomiendo max_concurrency=20–30 para empezar. Los proxies residenciales tienen latencia más alta (800–2000 ms por petición), y forzar 100 sesiones concurrentes puede agotar tu pool de IPs o saturar el objetivo.
Manejo de errores en request_handler
Crawlee distingue entre errores recuperables (red, timeout) y errores fatales (parsing, lógica). Los primeros disparan reintentos automáticamente. Los segundos se propagan y pueden detener el crawler. Envuelve tu lógica de parsing en try/except para evitar que un fallo de parsing mate todo el crawl:
@crawler.router.default_handler
async def handle_page(context: BeautifulSoupCrawlingContext) -> None:
try:
data = extract_product_data(context.soup)
await context.push_data(data)
except Exception as exc:
context.log.error(
f'Error de parsing en {context.request.url}: {exc}'
)
# No relanzar — el crawler continúa con la siguiente URL
# Si fuera un error de red, sí relanzar para reintento
Cuándo NO usar crawling con navegador
Hay una tentación constante de usar PlaywrightCrawler "por si acaso" el sitio necesita JavaScript. Pero el coste es real:
- Memoria: cada pestaña de Chromium consume 100–300 MB. Con 20 pestañas concurrentes, necesitas 2–6 GB de RAM.
- Tiempo: una página que con BeautifulSoup tarda 500 ms, con Playwright puede tardar 3–8 segundos incluyendo renderizado.
- Detección: los navegadores headless exponen señales que BeautifulSoup no envía (navigator.webdriver, canvas fingerprint, WebGL). Cloudflare detecta headless Chrome con ~95% de precisión según estudios de evasión anti-bot.
Usa PlaywrightCrawler solo cuando:
- Los datos se cargan vía XHR/fetch después del renderizado inicial.
- El sitio requiere interacción (click, scroll, login) para revelar contenido.
- El HTML del servidor es intencionalmente incompleto (SPA pura).
En todos los demás casos, BeautifulSoupCrawler con proxies residenciales bien rotados es más rápido, más barato y paradójicamente más difícil de detectar.
Ética y límites: datos públicos, robots.txt y APIs oficiales
El scraping ético no es opcional — es una cuestión legal y reputacional. Antes de lanzar cualquier crawler:
- Revisa robots.txt: respeta las directivas
Disallow. Crawlee puede cargar robots.txt automáticamente, pero la decisión de obedecerlo es tuya. - Consulta la API oficial primero: muchos sitios ofrecen APIs públicas o planes para desarrolladores. Si existe una API, úsala. Es más fiable, más rápida y legalmente más segura.
- Limita tu tasa: incluso con rotación de proxies, no satures el objetivo. 1–2 peticiones por segundo por IP es un límite razonable para la mayoría de sitios.
- No extraigas datos personales: el GDPR europeo prohíbe procesar datos personales sin base legal. Si el sitio expone emails, teléfonos o datos de usuarios, detente.
- No evites controles de acceso: si el sitio requiere login para ver ciertos datos, acceder sin autorización puede violar el CFAA en EE.UU. o leyes equivalentes en otros países.
Para más detalles sobre nuestras opciones de proxies residenciales, visita nuestra página de precios o explora las ubicaciones disponibles. Si necesias casos de uso específicos, revisa nuestras guías de web scraping y SERP tracking. La documentación técnica completa está en docs.proxyhat.com.
Key Takeaways
Resumen de patrones clave:
- ProxyConfiguration es el punto de entrada idiomático: usa
new_url_functionpara integrar cualquier proveedor, incluido ProxyHat con sesión fija.- Sesión fija > rotación por petición: vincular una IP a una sesión mantiene cookies consistentes y reduce bloqueos en sitios con estado.
- Residencial para anti-bot, datacenter para escala: usa proxies residenciales en Cloudflare/DataDome y datacenter en APIs públicas.
- session.retire() es tu primera defensa: retira la sesión en cuanto detectes un 403 o CAPTCHA, no esperes a que falle completamente.
- BeautifulSoup primero, Playwright como último recurso: el crawler HTTP es 10x más rápido y más difícil de detectar.
- Ética primero: datos públicos, robots.txt, APIs oficiales y respeto al CFAA/GDPR no son negociables.
Preguntas frecuentes
¿Qué es la rotación de proxies en Crawlee para Python?
Es el proceso de asignar direcciones IP diferentes a cada sesión o petición dentro de un crawler de Crawlee usando la clase ProxyConfiguration. Crawlee vincula automáticamente cada proxy a una sesión del SessionPool, manteniendo cookies y fingerprints consistentes con la misma IP para evitar detección por sistemas anti-bot.
¿Por qué importa la rotación de proxies en Crawlee para usuarios de proxies?
Porque los sitios modernos detectan y bloquean IPs que hacen demasiadas peticiones. Rotar proxies residenciales permite distribuir el tráfico entre cientos de IPs reales con baja puntuación de fraude, reduciendo bloqueos y manteniendo la consistencia de sesión necesaria para flujos de login, carritos o paginación.
¿Qué tipo de proxy funciona mejor para rotación en Crawlee para Python?
Los proxies residenciales funcionan mejor para sitios protegidos por Cloudflare o DataDome, ya que usan IPs de ISP reales con baja puntuación de fraude (< 10). Los proxies datacenter son más rápidos (50–200 ms) pero se bloquean con facilidad. Una estrategia escalonada que cae a datacenter tras bloqueos residenciales ofrece el mejor equilibrio entre evasión y coste.
¿Cómo evitar bloqueos al implementar rotación de proxies en Crawlee para Python?
Usa session.retire() cuando detectes un bloqueo (403, 429 o CAPTCHA), configura max_request_retries=3–5 con backoff exponencial, rota IPs por sesión y no por petición para mantener consistencia, respeta robots.txt y limita la concurrencia con el AutoscaledPool a 20–30 sesiones simultáneas con proxies residenciales.






