API Engineering

Umgehung von API-Rate-Limits ohne Auslösung von Anti-Abuse-Heuristiken

4 min read Published Updated 808 words

Die meisten Implementierungen von API-Rate-Limits lassen sich durch naive Proxy-Rotation trivial umgehen – doch genau diese Rotation löst innerhalb weniger Minuten Anti-Missbrauchs-Heuristiken aus. Die eigentliche Herausforderung besteht nicht nur darin, unter dem Limit zu bleiben, sondern dies zu tun, ohne wie ein Bot zu wirken. Dazu müssen die beiden unterschiedlichen Rate-Limit-Bereiche (pro IP und pro Schlüssel) verstanden, ein verzögerter Backoff mit Jitter angewendet, die Pool-Rotation randomisiert und das Request-Timing so geformt werden, dass es organischen Traffic nachahmt. Das Token-Bucket-Muster, auf einen Proxy-Pool aufgesetzt, bietet eine robuste Grundlage.

Pro-IP vs. Pro-Schlüssel: Die zwei Achsen des Rate-Limiting

Rate-Limits wirken auf mindestens zwei unabhängigen Dimensionen: der Quell-IP und dem API-Schlüssel (oder Token). Ein einzelner Schlüssel erlaubt vielleicht 5.000 Requests pro Stunde (GitHubs authentifiziertes Limit), aber derselbe Schlüssel von einer einzelnen IP kann bei einer niedrigeren Burst-Obergrenze gedrosselt werden. Nicht authentifizierte Requests sind noch stärker eingeschränkt – typischerweise 60 Requests pro Stunde pro IP. Die IP-Dimension zu ignorieren ist der schnellste Weg, eine 429 Too Many Requests-Antwort (RFC 6585) auszulösen. Ein Proxy-Pool muss Requests auf mehrere IPs verteilen, um eine Sättigung einer einzelnen Quelle zu vermeiden, aber jede IP teilt sich denselben Schlüssel. Wenn das globale Limit des Schlüssels 5.000/Stunde beträgt und Sie 50 Proxys haben, kann jeder Proxy nur 100 Requests pro Stunde machen, bevor der Zähler auf Schlüsselebene auslöst. Ermitteln Sie beide Limits, bevor Sie auch nur eine Zeile Code schreiben.

Backoff mit Jitter: Der Unterschied zwischen höflich und vorhersagbar

Exponentieller Backoff ohne Jitter ist ein Fingerabdruck. Server sehen Requests, die in perfekt verdoppelten Intervallen eintreffen, und markieren sie als automatisiert. Die Lösung ist voller Jitter: sleep(random.uniform(0, min(cap, base * 2 ** attempt))). Dadurch werden Wiederholungsversuche über das Zeitfenster verteilt, sodass das Muster von einem Burst echter Nutzer nicht zu unterscheiden ist. AWS’ eigene Dokumentation zum exponentiellen Backoff empfiehlt aus genau diesem Grund Jitter. Das folgende Python-Snippet implementiert einen Token-Bucket, der mit einer festen Rate nachgefüllt wird, bei Leerstand mit Jitter schläft und bei jeder erfolgreichen Akquise den Proxy rotiert:

import time
import random
from collections import deque

class TokenBucketProxyPool:
    def __init__(self, proxies, rate, burst):
        self.proxies = deque(proxies)
        self.rate = rate          # tokens per second
        self.burst = burst
        self.tokens = burst
        self.last_refill = time.monotonic()

    def refill(self):
        now = time.monotonic()
        elapsed = now - self.last_refill
        self.tokens = min(self.burst, self.tokens + elapsed * self.rate)
        self.last_refill = now

    def acquire(self):
        while True:
            self.refill()
            if self.tokens >= 1:
                self.tokens -= 1
                self.proxies.rotate(1)   # simple rotation, but see section below
                return self.proxies[0]
            else:
                sleep_time = (1 - self.tokens) / self.rate
                jitter = random.uniform(0, sleep_time * 0.5)
                time.sleep(sleep_time + jitter)

# Usage: pool = TokenBucketProxyPool(proxies, rate=2.0, burst=10)
#         proxy = pool.acquire()   # blocks until token available

Proxy-Pool-Rotation: Randomisieren, nicht Round-Robin

Eine sequenzielle Round-Robin-Rotation durch eine Proxy-Liste ist erkennbar. Ein Server, der Requests von 1.2.3.4, dann 5.6.7.8, dann 9.10.11.12 im Gleichschritt sieht, wird das Muster über die IPs hinweg korrelieren. Wählen Sie stattdessen Proxys zufällig mit Zurücklegen aus und fügen Sie eine pro-Proxy-Abkühlzeit hinzu. Öffentliche Proxy-Verzeichnisse melden Ausfallraten von 60–80 % – ein Proxy, der einen 429 oder einen Verbindungsfehler zurückgibt, sollte für mindestens 60 Sekunden in eine Warteschlange für tote Proxys herabgestuft werden. Implementieren Sie eine gewichtete Zufallsauswahl, bei der kürzlich erfolgreiche Proxys mit höherer Wahrscheinlichkeit gewählt werden. Der Token-Bucket oben verwendet der Einfachheit halber ein einfaches rotate(1); ersetzen Sie dies in der Produktion durch random.choice() und verfolgen Sie die Fehlerzahlen pro Proxy.

Request-Shaping: Den Mittelwert nachahmen, ohne die Linie zu überschreiten

Selbst mit einem perfekten Token-Bucket und Proxy-Pool ist eine konstante Request-Rate von genau 2,0 pro Sekunde unnatürlich. Echte Nutzer machen Pausen, bündeln Requests und variieren die Intervalle zwischen den Requests. Fügen Sie vor jedem acquire()-Aufruf eine kleine zufällige Verzögerung (z. B. time.sleep(random.uniform(0.1, 0.5))) hinzu und variieren Sie die Rate des Token-Buckets alle paar Minuten um ±10 %. Fügen Sie jedoch keine Verzögerungen hinzu, die das dokumentierte Rate-Limit-Fenster der API überschreiten – das würde den Zweck verfehlen. Ziel ist es, innerhalb des Limits zu bleiben, dabei aber wie ein Cluster legitimer Clients zu wirken. Übermäßiges Shaping (z. B. das Einfügen von menschenähnlichen Tippverzögerungen) ist verschwendete Mühe; der API geht es um die Request-Frequenz, nicht um die Zeit zwischen Tastenanschlägen.

Ethische Absicherung: Wissen, wo die Grenze liegt

Jede hier beschriebene Technik ist eine legitime Optimierung – bis sie gegen die Nutzungsbedingungen der API verstößt. Die meisten ToS verbieten ausdrücklich das „Umgehen von Rate-Limits“ oder die „Nutzung automatisierter Mittel für den Zugriff auf den Dienst“. Das Lesen der ToS ist keine Option. Wenn die API 100 Requests pro Minute pro Schlüssel erlaubt, ist die Verwendung von 50 Proxys, die jeweils 2 Requests pro Minute machen, konform. Die Verwendung von 100 Proxys, die jeweils 100 Requests pro Minute machen, ist es nicht – es ist Missbrauch, egal wie ausgeklügelt Ihr Jitter ist. Der Unterschied liegt in Absicht und Volumen. Ein Token-Bucket auf einem Proxy-Pool ist ein Werkzeug; dasselbe Werkzeug, das ethisch ein öffentliches Verzeichnis scraped, kann auch zum DDoS einer kleinen API verwendet werden. Dokumentieren Sie Ihre Rate-Limit-Ziele, prüfen Sie Ihre Logs auf 429er und überschreiten Sie niemals die dokumentierte Obergrenze pro Schlüssel. Wenn Sie mehr Durchsatz benötigen, fragen Sie nach einem höheren Limit oder zahlen Sie für einen dedizierten Tarif.