Los 429 Too Many Requests oplossen: Backoff, Budgetten en IP Verspreiding

Een 429 is de enige blokkade die je precies vertelt hoe je het moet oplossen — als je de response headers leest in plaats van alleen maar opnieuw te proberen. Hier is de rekenkunde achter veilige scraping doorvoer.

HTTP 429 Too Many Requests is de meest eerlijke blokkade die een website je kan sturen. In tegenstelling tot een 403, benoemt het het probleem — je ging te snel — en vaak biedt het de oplossing in een response header. Toch is de standaardreactie om de oproep in een retry loop te wikkelen en te hopen, wat een oplosbaar tempo probleem omzet in een trage, blokkade-genererende chaos. Deze gids behandelt 429 als wat het is: een wiskundig probleem met vier hefbomen. Lees de response, houd je aan de gepubliceerde limiet, back off correct wanneer je te snel gaat, en verdeel de resterende belasting over identiteiten.

Wat 429 betekent, en wanneer het liegt

Een rate limiter telt requests per identiteit — meestal een IP-adres, soms een API-sleutel of sessiecookie — binnen een tijdsvenster. Overschrijd de drempel en je krijgt 429 in plaats van content. Implementaties verschillen: token buckets geven een vaste toelage die op schema wordt bijgevuld, schuivende vensters tellen over een rollende periode in plaats van klokminuten, en gelaagde systemen vertragen bij een zachte limiet voordat ze hard blokkeren bij een hogere. Welke je tegenkomt, bepaalt of een korte pauze genoeg is of dat je een heel venster moet uitzitten.

Nu de kanttekening die uren bespaart: een 429 bij je eerste request is geen rate limit. Het is een botrespons in een rate-limit kostuum. Een veelgelezen Stack Overflow-thread beschrijft precies dit — de allereerste oproep van een scraper retourneerde een pagina met de tekst "Misbehaving Content Scraper Please use robots.txt Your IP has been rate limited" samen met de 429. Er was niets overschreden; de server besloot simpelweg dat de client een bot was en koos die code. Als je 429 ziet voordat je enig volume hebt verzonden, behandel het dan als een detectieprobleem en werk de header- en IP-controles in onze gids voor 403 forbidden errors in plaats daarvan.

Lees de response voordat je code wijzigt

Goed functionerende servers vertellen je wanneer je terug moet komen. Retry-After bevat ofwel een aantal seconden of een HTTP-datum. Veel API's voegen X-RateLimit-Limit (het plafond), X-RateLimit-Remaining (wat er over is in het huidige venster) en X-RateLimit-Reset (wanneer het bijgevuld wordt) toe. Die drie veranderen reactief herhalen in proactief tempo bepalen: je kunt vertragen voordat de blokkade optreedt in plaats van erna.

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.

Als er niets nuttigs terugkomt, meet dan zelf de limiet met een ramp: voer één request per seconde uit gedurende een minuut, dan twee, dan vier, en noteer de snelheid waarbij 429's verschijnen. Tien minuten meten verslaat een week gokken, en het getal dat je vindt wordt het budget waarop al het andere is gebaseerd.

Bands-diagram dat veilige, marginale en 429-triggerende request-gaten toont tegen een limiet van 100 requests per minuut
De hele berekening: 60 seconden gedeeld door de gepubliceerde limiet, voeg dan buffer toe voor netwerkjitter.

De tempo-rekenkunde

Neem de gedocumenteerde limiet en deel. Een limiet van 100 requests per minuut betekent 60 / 100 = 0,6 seconden tussen requests als absolute ondergrens — en een ondergrens is geen doel. Netwerkvertraging varieert, jouw klok en die van de server komen niet overeen, en een uitbarsting aan de venstergrens kan je schijnbare snelheid verdubbelen. Richt op 70-80% van de limiet: ongeveer 0,8 seconden per request in dat voorbeeld, wat je nog steeds 75 pagina's per minuut geeft.

Gelijktijdigheid volgt uit hetzelfde getal. Als je 1,25 requests per seconde wilt en elke request duurt 2 seconden rondreis, heb je 1,25 x 2 = 2,5 in-flight requests nodig — dus een semafoor van 3, niet de 50 waar je async-code standaard op staat. Onbeperkt asynchroon fan-out is de meest voorkomende oorzaak van 429's: honderd coroutines die tegelijk worden gestart, komen aan als één onmiddellijke uitbarsting, ongeacht hoe beleefd het gemiddelde eruitziet. Als je scrapt met asyncio, zijn de semafoorpatronen in async Python scraping met httpx en aiohttp de oplossing.

Backoff die werkt: exponentieel, begrensd, met jitter

Wanneer je een 429 tegenkomt, eer dan Retry-After als die aanwezig is. Begin anders bij één seconde en verdubbel — 1, 2, 4, 8, 16 — tot een harde grens zodat een kapot doel je wachtrij niet voor altijd kan stilleggen. Voeg dan jitter toe. Zonder randomisatie probeert elke werker die op hetzelfde moment de muur raakte op hetzelfde moment opnieuw, wat de uitbarsting reproduceert die het probleem veroorzaakte.

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}")

Nog beter, sluit de lus. Additieve toename/multiplicatieve afname geeft je een scraper die de limiet zelf vindt en er net onder blijft: verhoog het tempo terwijl responses schoon zijn, halveer het zodra een 429 landt. Houd één pacer per domein — limieten zijn per host, en één agressief doel zou de andere veertig niet moeten vertragen.

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

Belasting spreiden: het budget is per identiteit, niet per project

Zodra je het tempo correct hebt en nog steeds meer doorvoer nodig hebt, is de enige hefboom die overblijft identiteiten. Omdat de teller aan je IP is gekoppeld, geven N exit IP's je N keer het budget — de rekenkunde is zo bot. Als een site 60 requests per minuut per adres tolereert en je hebt 1.200 pagina's per minuut nodig, zijn dat 20 gelijktijdige exits die comfortabel onder de limiet draaien, niet één exit die twintig keer eroverheen gaat.

Dit is waar roterende proxies eigenlijk voor zijn. Een roterende gateway geeft elke request een ander residentieel IP uit een pool van 90M+ adressen over 200+ landen, zodat per-IP tellers nooit vol raken. Twee regels maken het verschil tussen belasting spreiden en een pool verbranden: houd de per-IP snelheid onder de limiet zelfs na rotatie (rotatie vermenigvuldigt je budget, het verwijdert het niet), en gebruik sticky sessions voor elke flow die meerdere requests beslaat — een login, een winkelwagen, een gepagineerde resultaatset — zodat de sessie niet halverwege breekt. Wanneer je hetzelfde IP een paar minuten nodig hebt en een nieuw na dat, zijn de afwegingen uiteengezet in sticky versus roterende sessions.

Vermenigvuldig je rate budget met residentiële IP's

Statistiekenpaneel dat de sleutelgetallen toont voor het vermijden van HTTP 429 errors: 600 ms minimale kloof, exponentiële backoff, 50 procent snelheidsverlaging en per-IP budgetten
Vier getallen besturen het hele systeem. Leid ze af van de limieten van het doel, nooit uit optimisme.

Goedkoper dan meer IP's: verstuur minder requests

Bandbreedtediscipline betaalt zich dubbel uit — minder requests betekent minder 429's en een kleinere rekening, wat hetzelfde argument is dat we maken in het verlagen van proxybandbreedtekosten. En als je liever helemaal geen pacing-infrastructuur bouwt, absorbeert de QuantumProxies Scraper API retries, rotatie en per-domein throttling achter één endpoint dat markdown, JSON of HTML retourneert.

Veelgestelde vragen

Hoe vermijd ik HTTP error 429 too many requests in Python?

Stel een bewuste kloof in tussen requests op basis van de gepubliceerde limiet van het doel, beperk gelijktijdigheid met een semafoor die is afgestemd op snelheid keer latentie, eer Retry-After wanneer deze verschijnt, en probeer opnieuw met exponentiële backoff plus jitter. Als je daarna meer doorvoer nodig hebt, verspreid requests dan over roterende proxy IP's in plaats van de kloof te verkorten.

Hoe lang moet ik wachten na een 429?

Precies zo lang als Retry-After zegt, als de server het stuurt — het kan een aantal seconden zijn of een HTTP-datum. Zonder die header, begin bij één seconde en verdubbel bij elke volgende 429 tot een limiet van een minuut of twee, met toevoeging van willekeurige jitter zodat parallelle werkers niet unisono opnieuw proberen.

Verhelpen proxies 429 errors?

Ze vermenigvuldigen je budget, ze verwijderen de limiet niet. Omdat tellers aan het client-IP zijn gekoppeld, houdt het verspreiden van een run over veel residentiële exits elk adres onder de drempel. Maar een pool die tien keer de per-IP limiet wordt gehammert, zal nog steeds 429's verzamelen — en zijn reputatie verbranden. Tempo eerst, dan roteren.

Is een 429 hetzelfde als een ban?

Nee. Een 429 is tijdelijk van opzet en verdwijnt wanneer het venster opnieuw wordt ingesteld, wat het onderscheidt van een 403 identiteitsblokkade. Het herhaaldelijk negeren ervan is hoe het permanent wordt: aanhoudende overschrijding is precies het signaal dat een throttle bevordert tot een langer durende IP-ban.

Waarom krijg ik 429 bij de allereerste request?

Omdat er niets daadwerkelijk is geteld. Sommige servers retourneren 429 aan elke client die ze als een bot beschouwen, ongeacht het volume — de statuscode is gewoon hun gekozen respons. Controleer de response body: als het robots.txt, scrapers of een firewall vermeldt, corrigeer dan je headers, TLS-vingerafdruk en exit-IP in plaats van je tempo.

Behandel rate limits als een budget dat je bewust uitgeeft. Meet het plafond, draai op 70-80% ervan, back off met jitter wanneer je te snel gaat, en koop meer identiteiten pas als het tempo goed is. In die volgorde gedaan, stoppen 429's met een foutklasse te zijn en worden ze een getal in een configuratiebestand.

Verkrijg roterende proxies en stop met vechten tegen rate limits