Cuando un analizador se traslada de un servidor a un clúster de varios trabajadores, surge un nuevo problema: varias máquinas comienzan a utilizar el mismo grupo de proxies al mismo tiempo. Sin coordinación, esto lleva a la duplicación de IP en las solicitudes, superación de los límites por dirección y prohibiciones masivas. En este artículo, analizaremos cómo shardear correctamente los proxies entre servidores, qué esquemas de distribución funcionan mejor y cómo implementar esto en la práctica con código en Python y Redis.
Por qué los proxies entran en conflicto en el analizador distribuido
Imaginemos una situación típica: tienes un grupo de 500 proxies y tres servidores, cada uno ejecutando 50 trabajadores de análisis en paralelo de Wildberries, Ozon o Avito. Si cada servidor toma proxies de la lista común sin coordinación, dos trabajadores en diferentes máquinas pueden enviar simultáneamente una solicitud desde la misma IP a un mismo dominio. Para el sistema anti-bots, esto parece una frecuencia anormalmente alta de solicitudes desde una dirección, y la IP es prohibida en cuestión de minutos.
El segundo problema es la distribución desigual de la carga. Si los proxies se dividen "como salga", un servidor puede quedarse con las IP más rápidas y frescas, mientras que otro se queda con el resto que ya tiene una reputación dañada. Como resultado, la velocidad de análisis en los servidores variará drásticamente, así como el porcentaje de errores.
El tercer problema es la pérdida de estado. Si un proxy es prohibido en un servidor, los demás servidores no lo sabrán y continuarán usándolo hasta que también sean prohibidos. Sin un registro centralizado del estado de los proxies, cada servidor "redescubre América" nuevamente, desperdiciando solicitudes.
La solución a los tres problemas es implementar un mecanismo de coordinación único: ya sea a través de un almacenamiento centralizado del estado (Redis, PostgreSQL, Consul), o mediante una división determinista del grupo por adelantado, sin intersecciones de rangos entre servidores.
Arquitectura del analizador distribuido
Antes de dividir los proxies, es necesario definir la arquitectura general del clúster. Hay dos enfoques básicos.
Esquema estático (shared-nothing). El grupo de proxies se divide entre los servidores de antemano, una vez al inicio. Cada servidor recibe su segmento no superpuesto y trabaja con él de forma autónoma, sin dirigirse a otros servidores. La ventaja es la máxima simplicidad y resistencia a fallos: la caída de un servidor no afecta a los demás. La desventaja es que si en un servidor se prohíben la mitad de los proxies, no puede "pedir prestadas" IP frescas a su vecino.
Esquema dinámico (shared-state). Todos los servidores acceden a un almacenamiento común (normalmente Redis), que almacena el estado de cada proxy y lo entrega bajo solicitud con bloqueo. La ventaja es la utilización uniforme del grupo y la reacción instantánea a las prohibiciones. La desventaja es que el almacenamiento se convierte en un único punto de fallo, se necesita una lógica separada en caso de que no esté disponible.
En la práctica, para el análisis de marketplaces y redes sociales, a menudo se elige un híbrido: un broker en Redis entrega proxies con bloqueo por un corto tiempo (TTL), y en caso de que Redis no esté disponible, los servidores cambian temporalmente a un grupo de respaldo local. Este enfoque combina la fiabilidad del esquema estático con la flexibilidad del dinámico.
Métodos para dividir el grupo de proxies entre servidores
Consideremos algoritmos específicos que se pueden aplicar para dividir el grupo.
División simple por módulo (round-robin sharding). Numeramos los proxies del 0 al N-1, el servidor con el número i toma todos los proxies donde index % número_de_servidores == i. Es simple, pero no maneja bien la adición o eliminación de servidores: al cambiar el número de máquinas, casi todo el grupo se redistribuye nuevamente.
Hashing consistente. A cada servidor y cada proxy se le asigna una posición en un anillo de hashes (generalmente por el hash de la dirección IP o ID del servidor). El proxy se asigna al servidor más cercano en el anillo. Al agregar o eliminar un servidor, solo se redistribuye una pequeña parte de los proxies, no todo el grupo; esta es la ventaja clave para clústeres que se escalan dinámicamente (por ejemplo, en Kubernetes con autoescalado).
Broker centralizado con bloqueos. En lugar de asignar estrictamente proxies a un servidor, todos los servidores solicitan un proxy "libre" a un servicio común (Redis, RabbitMQ), que lo entrega con un bloqueo temporal. Después de usar el proxy, se devuelve al grupo. Esta es la opción más flexible, especialmente si la carga en los servidores es desigual.
División por dominios o tareas. Si el analizador trabaja con varias fuentes (por ejemplo, Wildberries, Ozon y Avito en paralelo), tiene sentido dividir no solo los proxies entre servidores, sino también los proxies entre fuentes, para que la misma IP no aparezca simultáneamente en varias plataformas. Esto reduce el riesgo de que la IP caiga en la lista negra común de sistemas anti-fraude, si tales plataformas intercambian datos.
Tabla: comparación de métodos de sharding de proxies
| Método | Complejidad | Resistencia a la escalabilidad | Cuándo aplicar |
|---|---|---|---|
| División por módulo | Baja | Pobre | Número fijo de servidores |
| Hashing consistente | Media | Buena | Autoescalado del clúster |
| Broker centralizado | Media-alta | Excelente | Carga desigual, alto tráfico |
| División por dominios | Baja | Media | Análisis de varias plataformas simultáneamente |
Implementación de un broker de proxies en Redis
La opción más práctica para la mayoría de los analizadores distribuidos es un broker centralizado en Redis. Redis es adecuado porque es rápido, soporta operaciones atómicas y TTL "de serie". La idea es simple: todos los proxies disponibles se almacenan en un conjunto de Redis, y al entregar un proxy a un trabajador específico, se bloquea por un tiempo determinado a través de una clave con TTL.
A continuación, un ejemplo funcional de un broker en Python con la biblioteca redis-py. Implementa la entrega de un proxy libre con bloqueo, liberación después de su uso y marcado del proxy como prohibido.
import redis
import time
import random
r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)
PROXY_POOL_KEY = "proxy:pool" # conjunto de todos los proxies activos
LOCK_PREFIX = "proxy:lock:" # prefijo de bloqueo de un proxy específico
BANNED_PREFIX = "proxy:banned:" # prefijo de marca de prohibición
LOCK_TTL = 30 # segundos, por cuánto tiempo bloqueamos el proxy
BAN_TTL = 3600 # segundos, por cuánto tiempo consideramos que el proxy está prohibido
def load_pool(proxy_list):
"""Cargamos la lista de proxies en Redis una vez al iniciar el clúster."""
if proxy_list:
r.sadd(PROXY_POOL_KEY, *proxy_list)
def get_free_proxy(server_id, max_attempts=20):
"""Cada servidor llama a esta función para obtener un proxy libre."""
candidates = list(r.smembers(PROXY_POOL_KEY))
random.shuffle(candidates)
for proxy in candidates[:max_attempts]:
if r.exists(BANNED_PREFIX + proxy):
continue
lock_key = LOCK_PREFIX + proxy
# establecimiento atómico de bloqueo, si el proxy está libre
acquired = r.set(lock_key, server_id, nx=True, ex=LOCK_TTL)
if acquired:
return proxy
return None # no se encontraron proxies libres, se necesita un fallback
def release_proxy(proxy):
"""Liberamos el proxy después de una solicitud exitosa."""
r.delete(LOCK_PREFIX + proxy)
def mark_banned(proxy):
"""Marcamos el proxy como prohibido durante BAN_TTL segundos, eliminamos el bloqueo."""
r.set(BANNED_PREFIX + proxy, 1, ex=BAN_TTL)
r.delete(LOCK_PREFIX + proxy)
r.srem(PROXY_POOL_KEY, proxy) # lo eliminamos temporalmente del grupo
def return_to_pool_if_recovered():
"""Tarea periódica: devolvemos proxies cuyo ban ha expirado."""
for proxy in r.scan_iter(match=BANNED_PREFIX + "*"):
clean_proxy = proxy.replace(BANNED_PREFIX, "")
if not r.exists(BANNED_PREFIX + clean_proxy):
r.sadd(PROXY_POOL_KEY, clean_proxy)
El punto clave aquí es el uso de SET ... NX EX en Redis. Este comando es atómico: si dos servidores intentan bloquear el mismo proxy al mismo tiempo, solo uno de ellos tendrá éxito, el segundo recibirá un rechazo y buscará el siguiente candidato de la lista. Esto es lo que elimina los conflictos entre servidores al acceder paralelamente al grupo común.
Ejemplo de uso del broker en el trabajador del analizador:
import requests
def fetch_with_proxy(url, server_id):
proxy = get_free_proxy(server_id)
if not proxy:
raise RuntimeError("No hay proxies libres en el grupo")
proxies = {"http": f"http://{proxy}", "https": f"http://{proxy}"}
try:
response = requests.get(url, proxies=proxies, timeout=10)
if response.status_code in (403, 429):
mark_banned(proxy)
else:
release_proxy(proxy)
return response
except requests.exceptions.RequestException:
mark_banned(proxy)
raise
Este esquema se escala fácilmente: se pueden agregar 10 o 50 servidores, todos accederán a la misma instancia de Redis, y no habrá conflictos de IP porque el bloqueo es atómico a nivel de almacenamiento, no a nivel de servidor individual.
Sesiones pegajosas y vinculación de proxies a tareas
Para algunas tareas, simplemente entregar "cualquier proxy libre" no es suficiente. Si el analizador emula el comportamiento del usuario —por ejemplo, revisando secuencialmente las tarjetas de productos, agregando al carrito, navegando por las páginas del catálogo— es importante que todas las solicitudes de una sesión provengan de la misma IP. De lo contrario, el sitio verá un cambio brusco de geolocalización o dispositivo a mitad de sesión y la bloqueará como sospechosa.
Para esto, se introduce el concepto de sesión pegajosa: el proxy se vincula no a una solicitud, sino a una tarea específica o sesión de usuario durante su vida útil. En Redis, esto se implementa a través de una clave separada del tipo session:{task_id} -> proxy con un TTL igual a la duración esperada de la sesión.
SESSION_PREFIX = "proxy:session:"
SESSION_TTL = 600 # 10 minutos para la sesión
def get_sticky_proxy(task_id, server_id):
existing = r.get(SESSION_PREFIX + task_id)
if existing:
return existing
proxy = get_free_proxy(server_id)
if proxy:
r.set(SESSION_PREFIX + task_id, proxy, ex=SESSION_TTL)
return proxy
Para tareas con sesiones largas —por ejemplo, navegar por el catálogo de Wildberries emulando el comportamiento de un comprador real— es crítico usar proxies que se comporten lo más parecido posible a un usuario normal. Aquí son adecuados los proxies residenciales, ya que están vinculados a IP domésticas reales y generan menos sospechas en los sistemas anti-bots de los marketplaces en comparación con las direcciones de centros de datos.
Manejo de prohibiciones y rotación en el clúster
En un sistema distribuido, es importante que la información sobre la prohibición de un proxy se vuelva instantáneamente accesible a todos los servidores, no solo a aquel que recibió la prohibición. En el ejemplo anterior, esto es manejado por la función mark_banned: tan pronto como un servidor registra un 403 o 429 del sitio objetivo, el proxy se elimina del grupo común en Redis, y los demás servidores automáticamente dejan de recibirlo.
Para determinar la prohibición, es recomendable analizar no solo el código de respuesta HTTP, sino también el contenido de la página: algunos sitios devuelven 200 OK, pero con un captcha o un placeholder en lugar del contenido real. La lógica de detección de prohibiciones generalmente se ve así:
- Códigos HTTP 403, 429, 503 — un claro signo de bloqueo o límite de tasa.
- Redirección a una página de captcha o autorización — verificación de la URL después de la solicitud.
- Disminución brusca del tamaño de la respuesta o ausencia de selectores CSS esperados — signo de un placeholder.
- Timeout de conexión — a menudo significa que el proxy ha dejado de responder por completo.
Después de registrar una prohibición, tiene sentido no solo eliminar el proxy del grupo para siempre, sino ponerlo en "cuarentena" con TTL (como en el ejemplo — BAN_TTL), después del cual el proxy se devuelve automáticamente al grupo. Muchas prohibiciones en marketplaces y redes sociales son temporales y se levantan después de 30-60 minutos.
Si las prohibiciones ocurren con demasiada frecuencia incluso con una distribución uniforme de la carga, generalmente es una señal de que el propio grupo de proxies tiene baja calidad o un tamaño insuficiente para el volumen de solicitudes requerido. En tales casos, ampliar el grupo mediante proxies de centros de datos para tareas menos sensibles (por ejemplo, comprobación de disponibilidad de páginas) y usar IP residenciales más caras solo para sesiones críticas ayuda a reducir el porcentaje general de prohibiciones sin aumentar los costos de todo el tráfico.
Monitoreo de la salud del grupo de proxies
En un clúster de varios servidores, es fácil perder la visión general: cuántos proxies están activos en este momento, cuántos están prohibidos, cuál es la velocidad media de respuesta de cada uno. Sin monitoreo, es difícil entender por qué el análisis se ha ralentizado: si ha caído un servidor o si se han agotado los proxies vivos.
El conjunto mínimo de métricas que vale la pena recopilar en Redis o en una base de datos separada:
- Cantidad de proxies activos en el grupo en este momento.
- Cantidad de proxies prohibidos y tiempo medio hasta su recuperación.
- Tiempo medio de respuesta de cada proxy en las últimas N solicitudes.
- Porcentaje de solicitudes exitosas (tasa de éxito) por servidores y por el grupo en general.
- Distribución de carga: cuántas solicitudes ha procesado cada servidor durante un período.
Para la visualización, es conveniente almacenar estas métricas en Prometheus a través de un simple exportador, que cada minuto lee datos de Redis y los entrega en un formato comprensible para Grafana. Esto permite ver rápidamente una caída en la tasa de éxito por geolocalización específica o tipo de proxy y agregar IP frescas al grupo a tiempo, antes de que el analizador se detenga por completo.
Además, es importante monitorear los picos de prohibiciones al lanzar nuevos servidores en el clúster: si al escalar de 3 a 6 servidores, el porcentaje de prohibiciones aumenta drásticamente, probablemente el problema esté en el sharding: los nuevos servidores comenzaron a cruzarse en los rangos de proxies con los antiguos. Este es el primer signo de que es necesario pasar de la división por módulo a hashing consistente.
Lista de verificación para lanzar el analizador distribuido
Antes de desplegar el analizador en varios servidores, revisa los siguientes puntos:
- Mecanismo de coordinación del grupo elegido: división estática, hashing consistente o broker centralizado en Redis.
- Implementación de bloqueo atómico de proxies, excluyendo el uso de una IP por dos servidores simultáneamente.
- Configuración de TTL para el bloqueo y la prohibición, para que los proxies no "queden colgados" en estado bloqueado para siempre al caer un trabajador.
- Para tareas que requieren emular la sesión del usuario, configuración de vinculación de proxies pegajosos durante toda la duración de la tarea.
- Detección de prohibiciones configurada no solo por el código HTTP, sino también por el contenido de la respuesta.
- Monitoreo de la tasa de éxito, proxies activos y prohibidos en todo el clúster.
- Grupo de proxies dividido por tipos de tareas: IP de centros de datos para solicitudes menos sensibles, proxies residenciales o proxies móviles para sesiones donde la máxima similitud con un usuario real es importante.
- Carga en el almacenamiento de estado (Redis) verificada: con un gran número de servidores y alta frecuencia de solicitudes, puede ser necesario un clúster de Redis en lugar de una sola instancia.
Conclusión
La principal razón de los conflictos al trasladar el analizador a varios servidores es la falta de una única fuente de verdad sobre el estado del grupo de proxies. Una vez que se establece un broker centralizado con bloqueos atómicos (en Redis o un almacenamiento similar), el problema de la duplicación de IP y la desincronización de las prohibiciones desaparece por sí solo. Luego, solo queda elegir la estrategia de sharding adecuada para la escala de tu clúster: para un número fijo de servidores, es suficiente con una división simple, mientras que para una infraestructura en crecimiento dinámico, es mejor planear desde el principio el hashing consistente.
Si tu analizador distribuido trabaja con marketplaces o redes sociales donde la credibilidad del tráfico a nivel de sesión individual es importante, vale la pena dividir el grupo por tipo de tareas: para el escaneo masivo de catálogos, son adecuados proxies de centros de datos económicos, mientras que para sesiones que emulan el comportamiento del usuario, los proxies residenciales con IP domésticas reales reducen notablemente el porcentaje de prohibiciones en sistemas sensibles al fraude.