← Volver al blog

Filtrado de proxies defectuosos en el pool del parser: métricas de errores y rotación en Python

Analizamos qué métricas de errores indican la degradación del proxy en el grupo de parsers y cómo construir un sistema automático de descarte y rotación de IP.

📅19 de octubre de 2026

Si el parser de Wildberries o Ozon de repente comenzó a devolver páginas vacías, tiempos de espera y errores 403, el problema no siempre está en la plataforma misma. Más a menudo, parte de las direcciones IP en el pool de proxies ya están "caducadas": han sido bloqueadas, ralentizadas o están bajo CAPTCHA. Analizamos qué métricas diferencian un proxy funcional de uno muerto y cómo automatizar el filtrado sin control manual.

Por qué el pool de proxies se degrada con el tiempo

Cualquier pool de proxies — incluso comprado a un proveedor confiable — pierde parte de las direcciones funcionales con el tiempo. Las razones son diversas: los sistemas antifraude de Wildberries, Ozon y Avito acumulan marcas de reputación en las IP, algunas direcciones caen en listas negras después de solicitudes masivas de otros clientes del proveedor (si el proxy es compartido), los rangos de centros de datos son bloqueados en lotes por subredes, y las IP residenciales a veces simplemente se retiran del pool porque el usuario real desconectó el dispositivo.

Si no se limpia el pool, la proporción de proxies "muertos" crece de manera no lineal: primero 5%, después de una semana de scraping activo — 20-30%. El parser comienza a gastar recursos en reintentos a través de IPs no funcionales, aumenta el tiempo promedio de recolección de una página, y en los logs se acumulan errores que ocultan problemas reales (por ejemplo, cambios en el diseño del sitio). Sin un sistema de filtrado, o bien mantienes el pool inflado "por si acaso", o recibes regularmente fallos en la recolección de datos.

La tarea no es solo registrar el hecho de un error, sino clasificarlo: sobrecarga temporal del servidor, bloqueo de una IP específica, bloqueo regional o problema del lado del parser (encabezados incorrectos, cookies desactualizadas). Dependiendo del tipo de error, se determina si se debe eliminar el proxy del pool para siempre, ponerlo en "cuarentena" por un tiempo o simplemente repetir la solicitud.

Métricas clave de errores para el filtrado

Para diferenciar una falla temporal de un proxy realmente muerto, es necesario recopilar varias métricas por cada IP, y no basarse en un único estado HTTP. Aquí está el conjunto básico que se debe registrar en cada solicitud:

Métrica Qué indica Acción
HTTP 403 / 429 Bloqueo o límite de tasa en la IP Cuarentena de 30-60 minutos, si se repite — eliminación
Timeout de conexión Proxy sobrecargado o muerto 3 timeouts consecutivos — eliminación del pool
CAPTCHA en la respuesta IP bajo sospecha de anti-bots Reducir la frecuencia de solicitudes o cambiar IP
Tiempo de respuesta > 5 seg Degradación de la velocidad del proxy Bajar prioridad en el pool
Tamaño de respuesta por debajo del promedio Se devolvió una página de bloqueo Verificación adicional del contenido antes de aceptar
Tasa de solicitudes exitosas (success rate) Evaluación integral de la calidad de la IP Por debajo del 70% en 50 solicitudes — eliminación

Es importante no confundir los códigos de error del sitio con errores de red. Por ejemplo, 404 no es un problema del proxy, sino un producto ausente en Wildberries. Pero si la misma IP recibe regularmente 403 donde otras IPs reciben 200, es una señal clara de un bloqueo específico de esa dirección.

Algoritmo de scoring de proxies

En lugar de un binario "funcional / no funcional", es más conveniente calcular para cada IP un score de 0 a 100, que disminuye gradualmente con los errores y se recupera con las solicitudes exitosas. Esto permite no eliminar el proxy después de un fallo aleatorio (el servidor del marketplace podría simplemente haberse congelado por un segundo), pero rápidamente eliminar aquellas IP que sistemáticamente devuelven basura.

La lógica es simple: una solicitud exitosa con código 200 y contenido válido suma +2 al score (pero no más de 100). Un timeout — menos 15. Código 403/429 — menos 25. CAPTCHA en la respuesta — menos 20. Si el score cae por debajo de un umbral (por ejemplo, 30), el proxy entra en cuarentena por un tiempo fijo, y si después de volver de la cuarentena el score vuelve a caer por debajo de 10 — la IP se elimina del pool de forma definitiva y se registra para análisis.

Este enfoque es similar al sistema de reputación en navegadores anti-detect como Dolphin Anty o AdsPower, donde las cuentas tienen "salud" dependiendo de la actividad. La misma mecánica funciona muy bien para direcciones IP en scraping.

Código: ProxyPool con filtrado automático en Python

A continuación, un marco de trabajo para gestionar un pool de proxies teniendo en cuenta las métricas de errores. Se puede conectar a cualquier parser en requests, httpx o Scrapy, reemplazando la lógica de obtención de proxies por la llamada pool.get_proxy().

import time
import random
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class ProxyStats:
    address: str
    score: int = 100
    quarantine_until: float = 0.0
    total_requests: int = 0
    total_success: int = 0
    consecutive_timeouts: int = 0
    is_dead: bool = False

    @property
    def success_rate(self) -> float:
        if self.total_requests == 0:
            return 1.0
        return self.total_success / self.total_requests


class ProxyPool:
    def __init__(self, proxies: list[str],
                 min_score: int = 30,
                 dead_score: int = 10,
                 quarantine_seconds: int = 1800):
        self.stats = {p: ProxyStats(address=p) for p in proxies}
        self.min_score = min_score
        self.dead_score = dead_score
        self.quarantine_seconds = quarantine_seconds

    def _is_available(self, stats: ProxyStats) -> bool:
        if stats.is_dead:
            return False
        if stats.quarantine_until > time.time():
            return False
        return True

    def get_proxy(self) -> Optional[str]:
        available = [s for s in self.stats.values() if self._is_available(s)]
        if not available:
            return None
        # ordenamos por score, tomamos uno aleatorio del top-30% para no golpear una IP
        available.sort(key=lambda s: s.score, reverse=True)
        top_pool = available[:max(1, len(available) // 3)]
        return random.choice(top_pool).address

    def report_success(self, proxy: str):
        s = self.stats[proxy]
        s.total_requests += 1
        s.total_success += 1
        s.consecutive_timeouts = 0
        s.score = min(100, s.score + 2)

    def report_timeout(self, proxy: str):
        s = self.stats[proxy]
        s.total_requests += 1
        s.consecutive_timeouts += 1
        s.score -= 15
        if s.consecutive_timeouts >= 3:
            self._quarantine(s)
        self._check_dead(s)

    def report_blocked(self, proxy: str, captcha: bool = False):
        s = self.stats[proxy]
        s.total_requests += 1
        s.score -= 20 if captcha else 25
        self._quarantine(s)
        self._check_dead(s)

    def _quarantine(self, s: ProxyStats):
        if s.score < self.min_score:
            s.quarantine_until = time.time() + self.quarantine_seconds

    def _check_dead(self, s: ProxyStats):
        if s.score <= self.dead_score:
            s.is_dead = True

    def stats_report(self) -> list[dict]:
        return [
            {
                "proxy": s.address,
                "score": s.score,
                "success_rate": round(s.success_rate, 2),
                "dead": s.is_dead,
            }
            for s in self.stats.values()
        ]

En un parser real, las llamadas report_success, report_timeout y report_blocked se insertan directamente en el manejador de respuesta después de cada solicitud HTTP. Ejemplo de integración con requests:

import requests

def fetch(url: str, pool: ProxyPool):
    proxy = pool.get_proxy()
    if not proxy:
        raise RuntimeError("No hay proxies disponibles en el pool")

    proxies = {"http": proxy, "https": proxy}
    try:
        resp = requests.get(url, proxies=proxies, timeout=8)
    except requests.exceptions.Timeout:
        pool.report_timeout(proxy)
        return None

    if resp.status_code in (403, 429):
        pool.report_blocked(proxy)
        return None

    if "captcha" in resp.text.lower():
        pool.report_blocked(proxy, captcha=True)
        return None

    pool.report_success(proxy)
    return resp

Este enfoque alivia la carga del monitoreo manual: configuras los umbrales una vez, y luego el pool "se recupera" automáticamente, expulsando direcciones muertas y devolviendo de la cuarentena aquellas que se congelaron temporalmente.

Valores umbral para el bloqueo de IP

No existen cifras universales — los umbrales dependen de la plataforma y de la agresividad de su anti-bots. Pero hay referencias que funcionan bien en la práctica al hacer scraping de Wildberries, Ozon y Avito:

  • Tasa de éxito por debajo del 70% en una muestra de 30-50 solicitudes — motivo para eliminar la IP, incluso si el score actual no es crítico.
  • 3 timeouts consecutivos — casi siempre significa que el proxy está físicamente inaccesible o sobrecargado, no tiene sentido mantenerlo en el pool activo.
  • CAPTCHA más de 1 vez cada 20 solicitudes — la IP ya está bajo sospecha del anti-bots, es mejor reducir la frecuencia de solicitudes o cambiar la dirección.
  • Tiempo promedio de respuesta superior a 2x la mediana del pool — señal de degradación, no crítica, pero motivo para bajar la prioridad al elegir.

La cuarentena de 30-60 minutos suele ser suficiente para proxies de centros de datos y residenciales, para que el anti-bots "olvide" la actividad sospechosa temporal. Para proxies móviles, la situación es diferente: allí la IP cambia al cambiar la celda del operador, por lo que la cuarentena puede ser más corta — 10-15 minutos, ya que después de la rotación es físicamente otra dirección.

Healthcheck y rotación en segundo plano

Además del filtrado reactivo (basado en errores en el flujo principal de scraping), es útil ejecutar un healthcheck en segundo plano — un proceso separado que cada pocos minutos verifica los proxies con solicitudes ligeras a una URL neutral (por ejemplo, httpbin.org/ip) y actualiza las estadísticas del pool antes de que el proxy llegue a una solicitud de producción a Wildberries o Ozon.

import threading

def healthcheck_loop(pool: ProxyPool, interval: int = 300):
    while True:
        for proxy in list(pool.stats.keys()):
            try:
                r = requests.get(
                    "https://httpbin.org/ip",
                    proxies={"http": proxy, "https": proxy},
                    timeout=5
                )
                if r.status_code == 200:
                    pool.report_success(proxy)
                else:
                    pool.report_blocked(proxy)
            except requests.exceptions.RequestException:
                pool.report_timeout(proxy)
        time.sleep(interval)

threading.Thread(target=healthcheck_loop, args=(pool,), daemon=True).start()

Este proceso en segundo plano es especialmente importante si el pool es grande (cientos de IPs) y el scraping no se realiza de manera continua, sino en lotes — sin healthcheck, parte de los proxies simplemente logra "caducar" entre ejecuciones, y los primeros minutos de scraping se gastan en el filtrado en vivo, en lugar de trabajar directamente con direcciones verificadas.

Qué tipo de proxy elegir para el scraping de marketplaces

La elección del tipo de proxy influye directamente en la frecuencia con la que se deberá ejecutar el filtrado y cuán agresivos serán los umbrales. Para un scraping estable de Wildberries y Ozon, donde el anti-bots analiza patrones de comportamiento y la reputación de la subred, los proxies residenciales son los que mejor funcionan — sus IP pertenecen a proveedores reales de internet doméstico, y el anti-bots bloquea mucho menos frecuentemente en lotes por subred.

Para fuentes más simples y menos protegidas (por ejemplo, la recolección de datos públicos de Avito o el monitoreo de precios en vitrinas simples) a menudo son suficientes proxies de centros de datos — son más rápidos y baratos, lo cual es crítico en grandes volúmenes de solicitudes, pero también son bloqueados un poco más frecuentemente, por lo que los umbrales de filtrado para ellos deben ser más estrictos.

Tipo de proxy Velocidad de filtrado Cuándo usar
Centro de datos Alta, subredes son bloqueadas en lotes Vitrinas simples, grandes volúmenes, baja protección
Residencial Media, IP son bloqueadas de forma puntual Wildberries, Ozon, sitios con anti-bots
Móvil Baja, IP cambia automáticamente por el operador Fuentes altamente protegidas, scraping de alta frecuencia

En la práctica, muchos equipos utilizan un pool híbrido: la mayoría de las solicitudes se realizan a través de IPs residenciales, y en caso de bloqueos masivos se conectan proxies móviles como reserva "de emergencia", ya que su cambio de IP ocurre de manera natural a través de la rotación de la red celular y no requiere intervención manual.

Conclusión

Un pool de proxies sin un sistema de filtrado inevitablemente se degrada, y cuanto más tiempo funcione sin limpieza, más recursos desperdicia el parser en IPs muertas. Un modelo de scoring que tenga en cuenta los códigos HTTP, timeouts, CAPTCHA y la tasa de éxito permite identificar automáticamente direcciones problemáticas y mantener el pool en estado operativo sin control manual. El código de este artículo se puede adaptar a cualquier stack — requests, httpx, Scrapy o su propio parser asíncrono.

Si solo estás formando un pool para el scraping de marketplaces y deseas reducir la frecuencia de bloqueos desde el inicio, recomendamos considerar proxies residenciales — ofrecen una tasa de éxito notablemente más alta en plataformas protegidas como Wildberries y Ozon en comparación con las direcciones de centros de datos comunes.