Risolvi 429 Too Many Requests: Backoff, Budget e Distribuzione IP

Un 429 è l'unico blocco che ti dice esattamente come risolverlo — se leggi gli header della risposta invece di riprovare semplicemente. Ecco l'aritmetica dietro un throughput sicuro per lo scraping.

HTTP 429 Too Many Requests è il blocco più onesto che un sito web può inviarti. A differenza di un 403, nomina il problema — sei andato troppo veloce — e spesso fornisce la soluzione in un header di risposta. Tuttavia, la reazione standard è avvolgere la chiamata in un ciclo di riprova e sperare, il che trasforma un problema di ritmo risolvibile in un pasticcio lento che genera blocchi. Questa guida tratta il 429 per quello che è: un problema aritmetico con quattro leve. Leggi la risposta, adegua il ritmo al limite pubblicato, fai correttamente backoff quando superi il limite e distribuisci il carico rimanente tra le identità.

Cosa significa 429 e quando mente

Un limitatore di velocità conta le richieste per identità — di solito un indirizzo IP, a volte una chiave API o un cookie di sessione — all'interno di una finestra temporale. Supera la soglia e ricevi 429 invece del contenuto. Le implementazioni differiscono: i token bucket distribuiscono un'assegnazione fissa che si ricarica secondo un programma, le finestre scorrevoli contano su un periodo mobile piuttosto che su minuti di orologio, e i sistemi a livelli limitano a un limite morbido prima di bloccare duramente a uno più alto. Quello che affronti determina se una breve pausa è sufficiente o se devi aspettare l'intera finestra.

Ora la precisazione che salva ore: un 429 alla tua prima richiesta non è un limite di velocità. È una risposta bot che indossa un costume da limitazione di velocità. Un thread molto letto su Stack Overflow descrive esattamente questo — la primissima chiamata di uno scraper ha restituito una pagina che diceva "Misbehaving Content Scraper Please use robots.txt Your IP has been rate limited" insieme al 429. Nulla era stato superato; il server ha semplicemente deciso che il client era un bot e ha scelto quel codice. Se vedi 429 prima di aver inviato qualsiasi volume, trattalo come un problema di rilevamento e lavora sugli header e sui controlli IP nella nostra guida agli errori 403 forbidden invece.

Leggi la risposta prima di cambiare qualsiasi codice

I server ben educati ti dicono quando tornare. Retry-After contiene un numero di secondi o una data HTTP. Molte API aggiungono X-RateLimit-Limit (il limite massimo), X-RateLimit-Remaining (ciò che rimane nella finestra corrente) e X-RateLimit-Reset (quando si ricarica). Questi tre trasformano il ritentativo reattivo in un ritmo proattivo: puoi rallentare prima del blocco invece che dopo.

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.

Se non arriva nulla di utile, misura tu stesso il limite con un ramp: esegui una richiesta al secondo per un minuto, poi due, poi quattro, e registra il tasso al quale appaiono i 429. Dieci minuti di misurazione battono una settimana di supposizioni, e il numero che trovi diventa il budget su cui si basa tutto il resto.

Diagramma a bande che mostra i gap di richiesta sicuri, marginali e che attivano il 429 contro un limite di 100 richieste al minuto
Il calcolo completo: 60 secondi divisi per il limite pubblicato, poi aggiungi un buffer per il jitter di rete.

L'aritmetica del ritmo

Prendi il limite documentato e dividi. Un limite di 100 richieste al minuto significa 60 / 100 = 0,6 secondi tra le richieste come minimo assoluto — e un minimo non è un obiettivo. La latenza di rete varia, il tuo orologio e quello del server non sono d'accordo, e un burst al confine della finestra può raddoppiare il tuo tasso apparente. Mira al 70-80% del limite: circa 0,8 secondi per richiesta in quell'esempio, che ti dà comunque 75 pagine al minuto.

La concorrenza segue lo stesso numero. Se vuoi 1,25 richieste al secondo e ogni richiesta impiega 2 secondi andata e ritorno, hai bisogno di 1,25 x 2 = 2,5 richieste in volo — quindi un semaforo di 3, non i 50 che il tuo codice asincrono predefinisce. L'espansione asincrona non limitata è la causa singola più comune dei 429: un centinaio di coroutine lanciate contemporaneamente arrivano come un burst istantaneo, non importa quanto sembri educato il tasso medio. Se fai scraping con asyncio, i pattern di semaforo in scraping asincrono Python con httpx e aiohttp sono la soluzione.

Backoff che funziona: esponenziale, limitato, con jitter

Quando colpisci un 429, rispetta Retry-After se presente. Altrimenti inizia da un secondo e raddoppia — 1, 2, 4, 8, 16 — fino a un limite massimo in modo che un obiettivo rotto non possa bloccare la tua coda per sempre. Poi aggiungi jitter. Senza randomizzazione, ogni lavoratore che ha colpito il muro nello stesso momento ritenta nello stesso momento, riproducendo il burst che ha causato il 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}")

Meglio ancora, chiudi il ciclo. L'aumento additivo/diminuzione moltiplicativa ti dà uno scraper che trova il limite da solo e rimane appena sotto: aumenta il tasso mentre le risposte sono pulite, dimezzalo all'istante quando arriva un 429. Mantieni un pacer per dominio — i limiti sono per host, e un obiettivo aggressivo non dovrebbe rallentare gli altri quaranta.

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

Distribuzione del carico: il budget è per identità, non per progetto

Una volta che stai ritmando correttamente e hai ancora bisogno di più throughput, l'unica leva rimasta sono le identità. Poiché il contatore è associato al tuo IP, N IP di uscita ti danno N volte il budget — l'aritmetica è così diretta. Se un sito tollera 60 richieste al minuto per indirizzo e hai bisogno di 1.200 pagine al minuto, sono 20 uscite concorrenti che funzionano comodamente sotto il limite, non una uscita che funziona venti volte oltre.

Questo è ciò per cui i proxy rotanti sono effettivamente progettati. Un gateway rotante assegna a ogni richiesta un diverso IP residenziale da un pool di oltre 90 milioni di indirizzi in più di 200 paesi, quindi i contatori per IP non si riempiono mai. Due regole fanno la differenza tra distribuire il carico e bruciare un pool: mantieni il tasso per IP sotto il limite anche dopo la rotazione (la rotazione moltiplica il tuo budget, non lo rimuove), e usa sessioni persistenti per qualsiasi flusso che si estende su più richieste — un login, un carrello, un set di risultati paginato — in modo che la sessione non si interrompa a metà. Quando hai bisogno dello stesso IP per pochi minuti e di uno nuovo subito dopo, i compromessi sono delineati in sessioni persistenti contro proxy rotanti.

Moltiplica il tuo budget di velocità con IP residenziali

Pannello delle statistiche che mostra i numeri chiave per evitare errori HTTP 429: gap minimo di 600 ms, backoff esponenziale, riduzione del tasso del 50% e budget per IP
Quattro numeri gestiscono l'intero sistema. Derivali dai limiti del target, mai dall'ottimismo.

Più economico di più IP: invia meno richieste

La disciplina della larghezza di banda paga due volte — meno richieste significa meno 429 e una bolletta più piccola, che è lo stesso argomento che facciamo in ridurre i costi di larghezza di banda dei proxy. E se preferisci non costruire affatto un'infrastruttura di ritmo, l'API Scraper di QuantumProxies assorbe ritentativi, rotazione e limitazione per dominio dietro un endpoint che restituisce markdown, JSON o HTML.

Domande frequenti

Come evito l'errore HTTP 429 too many requests in Python?

Imposta un intervallo deliberato tra le richieste basato sul limite pubblicato del target, limita la concorrenza con un semaforo dimensionato in base al tasso per la latenza, rispetta Retry-After quando appare, e ritenta con backoff esponenziale più jitter. Se hai bisogno di più throughput dopo, distribuisci le richieste tra IP proxy rotanti piuttosto che accorciare l'intervallo.

Quanto tempo dovrei aspettare dopo un 429?

Esattamente quanto dice Retry-After, se il server lo invia — potrebbe essere un conteggio di secondi o una data HTTP. Senza quell'header, inizia da un secondo e raddoppia a ogni successivo 429 fino a un limite di un minuto o due, aggiungendo jitter casuale in modo che i lavoratori paralleli non ritentino all'unisono.

I proxy risolvono gli errori 429?

Moltiplicano il tuo budget, non rimuovono il limite. Poiché i contatori sono associati all'IP del client, distribuire un'esecuzione su molte uscite residenziali mantiene ogni indirizzo sotto la soglia. Ma un pool martellato a dieci volte il limite per IP raccoglierà comunque 429 — e brucerà la sua reputazione. Ritma prima, poi ruota.

Un 429 è lo stesso di un ban?

No. Un 429 è temporaneo per design e si cancella quando la finestra si resetta, il che lo distingue da un blocco di identità 403. Ignorarlo ripetutamente è come diventa permanente: il superamento sostenuto è esattamente il segnale che promuove un throttling in un ban IP di lunga durata.

Perché ricevo un 429 alla primissima richiesta?

Perché nulla è stato effettivamente contato. Alcuni server restituiscono 429 a qualsiasi client che considerano un bot, indipendentemente dal volume — il codice di stato è solo la loro risposta scelta. Controlla il corpo della risposta: se menziona robots.txt, scraper o un firewall, correggi i tuoi header, l'impronta digitale TLS e l'IP di uscita piuttosto che il tuo ritmo.

Tratta i limiti di velocità come un budget che spendi deliberatamente. Misura il tetto, esegui al 70-80% di esso, fai backoff con jitter quando superi, e acquista più identità solo una volta che il ritmo è corretto. Fatto in quell'ordine, i 429 smettono di essere una classe di errore e diventano un numero in un file di configurazione.

Ottieni proxy rotanti e smetti di combattere i limiti di velocità