Web Scraping y Agentes

Cómo solucionar errores HTTP 429 y Cloudflare 520 en rastreadores y agentes de IA

Respuesta rápida: El error HTTP 429 indica una limitación de tasa (rate limit) de la API o WAF de destino, resuelta mediante backoff con jitter descorrelacionado (Decorrelated Jitter) y pools de proxies residenciales. El error Cloudflare 520 («Web Server Returned an Unknown Error») ocurre cuando el proxy perimetral recibe respuestas corruptas, reinicios TCP RST o cabeceras excesivas del servidor de origen bajo alta carga de rastreo.

1. Introducción: La fragilidad de los rastreadores web de IA autónomos

A medida que los agentes de IA autónomos evolucionan de interfaces de chat hacia motores de razonamiento de múltiples pasos (investigación web en tiempo real, auditorías financieras, monitorización de competidores), su principal cuello de botella ya no es la velocidad del LLM, sino la confiabilidad de la red y el acceso en el Edge.

Los agentes modernos en producción realizan miles de llamadas HTTP por minuto. A diferencia de los rastreadores clásicos (Scrapy, Googlebot), los agentes de IA requieren:

  1. Recuperación síncrona de baja latencia para no bloquear el bucle de razonamiento.
  2. Ejecución profunda de JavaScript para procesar aplicaciones SPA.
  3. Conversión limpia a Markdown para evitar el desperdicio de tokens de contexto.

Sin embargo, las defensas perimetrales (Cloudflare, Akamai, DataDome) aplican heurísticas antibot avanzadas, provocando dos errores críticos:

  • HTTP 429 Too Many Requests: Tasa de solicitudes excedida.
  • HTTP 520 Web Server Returned an Unknown Error (Cloudflare): Fallo de comunicación entre Cloudflare y el servidor de origen bajo intensa carga de scraping.

2. Anatomía de HTTP 429: Límites de tasa y huellas digitales

Definido por RFC 6585, el error 429 surge en tres capas arquitectónicas:

  1. Capa 1: WAF perimetral (Cloudflare / DataDome): Detecta discrepancias entre la huella TLS JA4 (por ejemplo, Python OpenSSL) y el User-Agent declarado (Chrome), aplicando un 429 inmediato.
  2. Capa 2: Pasarela de API (Kong, Envoy, Nginx): Algoritmos Token Bucket y Leaky Bucket.
  3. Capa 3: Servidor de origen: Protección contra la saturación de CPU o conexiones a bases de datos.

Cabeceras fundamentales para inspeccionar: Retry-After, RateLimit-Limit, RateLimit-Remaining y RateLimit-Reset.


3. Desmitificando el error 520 de Cloudflare

El código 520 es exclusivo de Cloudflare e indica un fallo en la infraestructura de origen:

  1. Caída del proceso de origen (OOM / SIGSEGV): Solicitudes concurrentes agotan la memoria del servidor, enviando un paquete TCP RST a Cloudflare.
  2. Desincronización de Keep-Alive: Cloudflare mantiene conexiones abiertas 15s. Si el servidor origen tiene un keepalive_timeout de 5s, el socket se corta en pleno envío.
  3. Desbordamiento de cabeceras (>16 KB): Bucles de cookies masivos exceden el búfer de Cloudflare.
  4. Respuestas vacías (Cero bytes): El servidor cierra la conexión TLS sin transferir datos.

4. Mitigación algorítmica: Backoff exponencial, Jitter y Circuit Breaker

Los reintentos simples (sleep(1)) provocan el problema de la manada atronadora (Thundering Herd). El estándar óptimo es el Jitter Descorrelacionado (Decorrelated Jitter):

# Fórmula de Jitter Descorrelacionado:
sleep = min(cap, random.uniform(base, previous_delay * 3.0))

Implementación en Python con Circuit Breaker

import asyncio, random, time, aiohttp
from urllib.parse import urlparse

class ResilientAgentCrawler:
    def __init__(self, base_delay=1.0, max_delay=60.0, max_retries=5, threshold=4, cooldown=30.0):
        self.base_delay = base_delay
        self.max_delay = max_delay
        self.max_retries = max_retries
        self.threshold = threshold
        self.cooldown = cooldown
        self.failures = {}
        self.opened_at = {}

    def _is_open(self, domain):
        t = self.opened_at.get(domain)
        if not t: return False
        if time.monotonic() - t > self.cooldown:
            del self.opened_at[domain]
            self.failures[domain] = 0
            return False
        return True

    async def fetch(self, session, url):
        domain = urlparse(url).netloc
        if self._is_open(domain):
            raise RuntimeError(f"Circuit Breaker abierto para {domain}")

        delay = self.base_delay
        for attempt in range(1, self.max_retries + 1):
            try:
                async with session.get(url) as resp:
                    if resp.status == 200:
                        self.failures[domain] = 0
                        return await resp.text()
                    elif resp.status == 429:
                        retry_after = resp.headers.get("Retry-After")
                        wait = float(retry_after) if retry_after else random.uniform(self.base_delay, delay * 3.0)
                        delay = min(self.max_delay, wait)
                        await asyncio.sleep(delay)
                    elif resp.status in (520, 502, 503, 504):
                        self.failures[domain] = self.failures.get(domain, 0) + 1
                        if self.failures[domain] >= self.threshold:
                            self.opened_at[domain] = time.monotonic()
                        delay = min(self.max_delay, random.uniform(self.base_delay, delay * 3.0))
                        await asyncio.sleep(delay)
                    else:
                        resp.raise_for_status()
            except Exception as e:
                if attempt == self.max_retries: raise e
                await asyncio.sleep(delay)

5. Evasión de huella digital TLS con curl_cffi

Las bibliotecas comunes de Python son detectadas por su huella JA4 OpenSSL. Utilice curl_cffi para emular navegadores reales:

from curl_cffi.requests import AsyncSession

async def fetch_stealth(url: str):
    async with AsyncSession(impersonate="chrome124") as s:
        res = await s.get(url, timeout=15)
        return res.text

6. Arquitectura de proxies y configuración de origen

  • Proxies residenciales rotativos: Esenciales para eludir límites 429 distribuyendo peticiones en miles de IPs.
  • Proxies móviles (4G/5G con CGNAT): Tolerancia máxima de los WAF, ya que comparten IP con miles de usuarios reales.
  • Ajustes de Nginx contra el error 520:
http {
    keepalive_timeout 75s;
    keepalive_requests 10000;
    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
}

7. Conclusión

Para construir rastreadores de IA imparables:

  1. Implemente Decorrelated Jitter para evitar picos de reintentos concurrentes.
  2. Utilice curl_cffi para imitar la huella JA4 y tramas HTTP/2 de Chrome.
  3. Configure Keep-Alive en 75 segundos en el servidor de origen para evitar el error 520.
  4. Active Circuit Breakers para no malgastar tokens en dominios con fallos de backend.
← Todos los Artículos
0 / 4