Soluciona el error 429 Too Many Requests: Retroceso, Presupuestos y Distribución de IPs

Un 429 es el único bloqueo que te dice exactamente cómo solucionarlo, si lees los encabezados de respuesta en lugar de simplemente reintentar. Aquí está la aritmética detrás del rendimiento seguro de scraping.

HTTP 429 Too Many Requests es el bloqueo más honesto que un sitio web puede enviarte. A diferencia de un 403, nombra el problema — fuiste demasiado rápido — y frecuentemente incluye la solución en un encabezado de respuesta. Sin embargo, la reacción estándar es envolver la llamada en un bucle de reintento y esperar, lo que convierte un problema de ritmo solucionable en un desastre lento que genera bloqueos. Esta guía trata el 429 como lo que es: un problema aritmético con cuatro palancas. Lee la respuesta, ajusta el ritmo al límite publicado, retrocede correctamente cuando te excedes y distribuye la carga restante entre identidades.

Qué significa el 429, y cuándo está mintiendo

Un limitador de tasa cuenta las solicitudes por identidad — generalmente una dirección IP, a veces una clave API o cookie de sesión — dentro de una ventana de tiempo. Cruza el umbral y obtienes 429 en lugar de contenido. Las implementaciones difieren: los cubos de tokens otorgan una asignación fija que se recarga según un horario, las ventanas deslizantes cuentan sobre un período continuo en lugar de minutos de reloj, y los sistemas escalonados limitan suavemente antes de bloquear firmemente en un nivel superior. Cuál enfrentas determina si una pausa corta es suficiente o si debes esperar toda una ventana.

Ahora la advertencia que ahorra horas: un 429 en tu primera solicitud no es un límite de tasa. Es una respuesta de bot disfrazada de límite de tasa. Un hilo muy leído en Stack Overflow describe exactamente esto: la primera llamada de un scraper devolvió una página que decía "Misbehaving Content Scraper Please use robots.txt Your IP has been rate limited" junto con el 429. No se había excedido nada; el servidor simplemente decidió que el cliente era un bot y eligió ese código. Si ves 429 antes de haber enviado cualquier volumen, trátalo como un problema de detección y trabaja los encabezados y verificaciones de IP en nuestra guía para errores 403 prohibidos en su lugar.

Lee la respuesta antes de cambiar cualquier código

Los servidores bien comportados te dicen cuándo volver. Retry-After lleva ya sea un número de segundos o una fecha HTTP. Muchas APIs añaden X-RateLimit-Limit (el techo), X-RateLimit-Remaining (lo que queda en la ventana actual) y X-RateLimit-Reset (cuándo se recarga). Esos tres convierten el reintento reactivo en un ritmo proactivo: puedes desacelerar antes del bloqueo en lugar de después.

import requests

r = requests.get("https://target.example/api/items", timeout=20)
print(r.status_code)
for h in ("Retry-After", "X-RateLimit-Limit", "X-RateLimit-Remaining", "X-RateLimit-Reset"):
    if h in r.headers:
        print(f"{h}: {r.headers[h]}")

# No headers at all? The limit is undocumented - measure it:
# send a slow ramp (1 req/s, then 2, then 4) and note where 429 starts.

Si no vuelve nada útil, mide el límite tú mismo con una rampa: ejecuta a una solicitud por segundo durante un minuto, luego dos, luego cuatro, y registra la tasa a la que aparecen los 429. Diez minutos de medición superan una semana de conjeturas, y el número que encuentres se convierte en el presupuesto sobre el que se construye todo lo demás.

Diagrama de bandas que muestra los intervalos de solicitudes seguros, marginales y que disparan 429 contra un límite de tasa de 100 solicitudes por minuto
El cálculo completo: 60 segundos divididos por el límite publicado, luego añade un buffer para el jitter de red.

La aritmética del ritmo

Toma el límite documentado y divide. Un tope de 100 solicitudes por minuto significa 60 / 100 = 0.6 segundos entre solicitudes como un mínimo absoluto — y un mínimo no es un objetivo. La latencia de la red varía, tu reloj y el del servidor no coinciden, y un estallido en el límite de la ventana puede duplicar tu tasa aparente. Apunta al 70-80% del límite: aproximadamente 0.8 segundos por solicitud en ese ejemplo, lo que aún te da 75 páginas por minuto.

La concurrencia sigue del mismo número. Si deseas 1.25 solicitudes por segundo y cada solicitud tarda 2 segundos en ida y vuelta, necesitas 1.25 x 2 = 2.5 solicitudes en vuelo — así que un semáforo de 3, no los 50 que tu código asíncrono tiene por defecto. La expansión asíncrona sin limitación es la causa más común de 429s: cien corutinas lanzadas a la vez llegan como un estallido instantáneo, sin importar cuán educada parezca la media. Si haces scraping con asyncio, los patrones de semáforo en scraping asíncrono en Python con httpx y aiohttp son la solución.

Retroceso que funciona: exponencial, con límite, con jitter

Cuando te encuentres con un 429, respeta Retry-After si está presente. De lo contrario, comienza en un segundo y duplica — 1, 2, 4, 8, 16 — hasta un límite máximo para que un objetivo roto no detenga tu cola para siempre. Luego añade jitter. Sin aleatorización, cada trabajador que chocó contra la pared al mismo momento reintenta al mismo momento, reproduciendo el estallido que causó el problema.

import random, time, requests

def get_with_backoff(session, url, max_tries=6, cap=120.0):
    for attempt in range(max_tries):
        r = session.get(url, timeout=20)
        if r.status_code != 429:
            return r

        ra = r.headers.get("Retry-After", "")
        wait = float(ra) if ra.isdigit() else 2.0 ** attempt   # 1, 2, 4, 8, 16, 32
        wait = min(wait, cap)
        wait += random.uniform(0, wait * 0.3)                   # jitter: break the lockstep

        time.sleep(wait)
    raise RuntimeError(f"still 429 after {max_tries} attempts: {url}")

Mejor aún, cierra el ciclo. El aumento aditivo/disminución multiplicativa te da un scraper que encuentra el límite por sí mismo y se mantiene justo por debajo: aumenta la tasa mientras las respuestas son limpias, redúcela a la mitad en cuanto llegue un 429. Mantén un regulador por dominio — los límites son por host, y un objetivo agresivo no debería ralentizar a los otros cuarenta.

class Pacer:
    """One per domain. Additive increase, multiplicative decrease."""
    def __init__(self, rps=2.0, floor=0.2, ceiling=8.0):
        self.rps, self.floor, self.ceiling = rps, floor, ceiling

    def ok(self):          # clean response: creep faster
        self.rps = min(self.ceiling, self.rps + 0.05)

    def throttled(self):   # 429: halve immediately
        self.rps = max(self.floor, self.rps / 2)

    @property
    def gap(self):
        return 1.0 / self.rps

Distribución de carga: el presupuesto es por identidad, no por proyecto

Una vez que estás ajustando el ritmo correctamente y aún necesitas más rendimiento, la única palanca que queda son las identidades. Debido a que el contador está vinculado a tu IP, N IPs de salida te dan N veces el presupuesto — la aritmética es así de contundente. Si un sitio tolera 60 solicitudes por minuto por dirección y necesitas 1,200 páginas por minuto, eso son 20 salidas concurrentes funcionando cómodamente por debajo del límite, no una salida funcionando veinte veces por encima.

Esto es para lo que realmente son los proxies rotativos. Una puerta de enlace rotativa asigna a cada solicitud una IP residencial diferente de un pool de más de 90 millones de direcciones en más de 200 países, por lo que los contadores por IP nunca se llenan. Dos reglas marcan la diferencia entre distribuir la carga y quemar un pool: mantén la tasa por IP por debajo del límite incluso después de la rotación (la rotación multiplica tu presupuesto, no lo elimina), y usa sesiones pegajosas para cualquier flujo que abarque varias solicitudes — un inicio de sesión, un carrito, un conjunto de resultados paginados — para que la sesión no se rompa a mitad de camino. Cuando necesites la misma IP por unos minutos y una nueva después de eso, las compensaciones se detallan en sesiones pegajosas versus sesiones rotativas.

Multiplica tu presupuesto de tasa con IPs residenciales

Panel de estadísticas que muestra los números clave para evitar errores HTTP 429: 600 ms de intervalo mínimo, retroceso exponencial, reducción de tasa del 50 por ciento y presupuestos por IP
Cuatro números manejan todo el sistema. Derívalos de los límites del objetivo, nunca del optimismo.

Más barato que más IPs: envía menos solicitudes

La disciplina de ancho de banda paga dos veces — menos solicitudes significa menos 429s y una factura más pequeña, que es el mismo argumento que hacemos en reducir los costos de ancho de banda de proxy. Y si prefieres no construir infraestructura de ritmo en absoluto, el Scraper API de QuantumProxies absorbe reintentos, rotación y limitación por dominio detrás de un solo endpoint que devuelve markdown, JSON o HTML.

Preguntas frecuentes

¿Cómo evito el error HTTP 429 demasiadas solicitudes en Python?

Establece un intervalo deliberado entre solicitudes basado en el límite publicado del objetivo, limita la concurrencia con un semáforo dimensionado a la tasa por latencia, respeta Retry-After cuando aparece, y reintenta con retroceso exponencial más jitter. Si necesitas más rendimiento después de eso, distribuye las solicitudes entre IPs de proxy rotativas en lugar de acortar el intervalo.

¿Cuánto tiempo debo esperar después de un 429?

Exactamente el tiempo que Retry-After indique, si el servidor lo envía — puede ser un conteo de segundos o una fecha HTTP. Sin ese encabezado, comienza en un segundo y duplica en cada 429 subsiguiente hasta un límite de un minuto o dos, añadiendo jitter aleatorio para que los trabajadores paralelos no reintenten al unísono.

¿Los proxies solucionan los errores 429?

Multiplican tu presupuesto, no eliminan el límite. Debido a que los contadores están vinculados a la IP del cliente, distribuir una ejecución entre muchas salidas residenciales mantiene cada dirección por debajo del umbral. Pero un pool siendo golpeado a diez veces el límite por IP aún recogerá 429s — y quemará su reputación. Ajusta el ritmo primero, luego rota.

¿Es un 429 lo mismo que ser baneado?

No. Un 429 es temporal por diseño y se limpia cuando la ventana se reinicia, lo que lo distingue de un bloqueo de identidad 403. Ignorarlo repetidamente es cómo se vuelve permanente: un exceso sostenido es exactamente la señal que promueve un límite a una prohibición de IP más duradera.

¿Por qué recibo 429 en la primera solicitud?

Porque en realidad no se contó nada. Algunos servidores devuelven 429 a cualquier cliente que consideren un bot, independientemente del volumen — el código de estado es simplemente su respuesta elegida. Revisa el cuerpo de la respuesta: si menciona robots.txt, scrapers o un firewall, corrige tus encabezados, huella digital TLS y IP de salida en lugar de tu ritmo.

Trata los límites de tasa como un presupuesto que gastas deliberadamente. Mide el techo, opera al 70-80% de él, retrocede con jitter cuando te excedes, y compra más identidades solo una vez que el ritmo sea correcto. Hecho en ese orden, los 429 dejan de ser una clase de error y se convierten en un número en un archivo de configuración.

Obtén proxies rotativos y deja de luchar contra los límites de tasa