Es gibt eine verlockende Logik, die viele Teams zu Fall bringt: Ratenlimits werden pro IP durchgesetzt, ein Residential-Pool gibt einem viele IPs, also gelten Ratenlimits nicht mehr. Die ersten beiden Aussagen sind wahr, und die Schlussfolgerung ist falsch, und in der Lücke dazwischen leben überraschend viele blockierte Scraper.
Ein Pool verändert, wo das Limit greift, nicht ob es existiert. Man muss weiterhin das Tempo dosieren, nur entlang anderer Achsen als bei einem Single-IP-Setup, und die Achse, die dabei vergessen wird, ist genau die, die einen erwischt. Hier ist, wie Drosselung tatsächlich funktioniert, wenn Anfragen über einen rotierenden Pool verteilt sind.
Drei Limits, nicht eines
Wenn man über einen Pool scrapt, gelten mindestens drei separate Obergrenzen gleichzeitig, und der Durchsatz wird von der bestimmt, die zuerst erreicht wird.
Pro IP, pro Ziel. Das ist die klassische. Jede einzelne Adresse kann nur so viele Anfragen an eine bestimmte Seite senden, bevor diese Seite sie drosselt oder blockiert. Rotation ist das, was einen darunter hält, was der ganze Sinn der Lastverteilung über einen Pool ist, wie in load balancing beschrieben.
Pro Ziel, in Summe. Das ist die, die Leute überrascht. Eine Seite zählt nicht nur pro Adresse, sie sieht auch ihre eigene Gesamtlast an eingehendem Traffic und kann ein koordiniertes Muster über viele Adressen hinweg erkennen, besonders wenn die Anfragen Timing, Pfade oder Verhalten teilen. Fünfzig IPs, die jeweils zwei Anfragen pro Sekunde senden, ergeben hundert Anfragen pro Sekunde, die an einem Ursprung ankommen, und keine Menge an Adressrotation lässt das wie gewöhnlichen organischen Traffic aussehen. Anti-Bot-Systeme argumentieren zunehmend auf dieser Ebene.
Die eigene Kapazität. Nebenläufigkeit, die man tatsächlich aufrechterhalten kann: offene Verbindungen, Worker-Threads, Speicher, und die Tatsache, dass Residential-Latenz höher ist als eine Direktverbindung, sodass eine feste Thread-Anzahl weniger Anfragen pro Sekunde liefert, als man erwarten könnte.
Die praktische Konsequenz ist, dass die Drosselung pro Ziel ausgedrückt werden muss, nicht global. Ein einziges globales Ratenlimit lässt einen entweder auf den fünfzig Seiten hungern, die mehr vertragen könnten, oder hämmert auf die eine ein, die es nicht kann.
Vom Ziel ausgehen, nicht von den eigenen Ambitionen
Bevor man irgendeinen Drosselungscode schreibt, sollte man herausfinden, was das Ziel tatsächlich toleriert, denn Raten produziert entweder einen unnötig langsamen oder einen blockierten Job.
Lesen Sie die Signale, die die Seite gibt. Ein 429 ist eine explizite Aussage, dass man zu schnell ist. Ein Retry-After-Header sagt einem genau, wie lange man warten soll, und diesen zu befolgen ist sowohl korrekt als auch deutlich günstiger, als die Antwort durch Ausprobieren herauszufinden. Manche APIs veröffentlichen Limits in der Dokumentation oder liefern Header mit verbleibendem Kontingent zurück. Und wenn die Seite eine robots.txt mit einer Crawl-Delay-Direktive hat, ist das eine erklärte Präferenz, die es wert ist, respektiert zu werden.
Wo nichts veröffentlicht ist, kalibrieren Sie empirisch: beginnen Sie konservativ, erhöhen Sie schrittweise, und beobachten Sie die validierte Erfolgsrate statt der Statuscodes, da eine Seite unter Belastung oft degradiert, bevor sie verweigert, und eine Challenge-Seite mit Status 200 für einen naiven Zähler wie ein Erfolg aussieht, wie in detecting blocked or fake content behandelt. Der Punkt, an dem die Erfolgsrate zu sinken beginnt, ist die tatsächliche Obergrenze, und man möchte darunter operieren, nicht genau daran.
Die Drosselung implementieren
Ein Token-Bucket pro Ziel ist das Standardwerkzeug und einfach genug, um es von Grund auf zu schreiben. Tokens füllen sich mit der gewählten Rate wieder auf, jede Anfrage verbraucht eines, und eine Anfrage wartet, wenn der Bucket leer ist. Das ergibt einen stabilen Durchschnitt mit einer kontrollierten Burst-Erlaubnis, was besser zum Verhalten echten Traffics passt als eine starre Ein-Request-pro-N-Sekunden-Verzögerung.
import time, threading
class TargetLimiter:
"""Token bucket, one instance per target host."""
def __init__(self, rate_per_sec, burst=5):
self.rate, self.capacity = rate_per_sec, burst
self.tokens, self.updated = burst, time.monotonic()
self.lock = threading.Lock()
def acquire(self):
while True:
with self.lock:
now = time.monotonic()
self.tokens = min(self.capacity,
self.tokens + (now - self.updated) * self.rate)
self.updated = now
if self.tokens >= 1:
self.tokens -= 1
return
wait = (1 - self.tokens) / self.rate
time.sleep(wait) # sleep outside the lock
LIMITS = { # per target, tuned per target
"shop.example.com": TargetLimiter(2.0),
"search.example.com": TargetLimiter(0.5),
}
def fetch(url, host, proxies):
LIMITS[host].acquire()
return requests.get(url, proxies=proxies, timeout=20)
Zwei Verfeinerungen sind in der Produktion wichtig. Fügen Sie Jitter hinzu, damit Anfragen nicht auf exakten Intervallen landen, da perfekt regelmäßiger Abstand selbst eine Maschinensignatur ist und zudem Ihre Worker in Wellen synchronisiert. Und machen Sie den Limiter über Worker hinweg gemeinsam genutzt statt pro Prozess, sonst machen zehn Prozesse, die jeweils höflich zwei Anfragen pro Sekunde ausführen, zusammen zwanzig. In einem verteilten Setup bedeutet das einen gemeinsamen Zähler in Redis oder Ähnlichem, was dasselbe Problem ist, das scaling scraping across Kubernetes lösen muss.
Nebenläufigkeit ist die andere Hälfte
Rate und Nebenläufigkeit sind unterschiedliche Regler, und beide brauchen Limits. Die Rate steuert, wie oft Anfragen gestartet werden; Nebenläufigkeit steuert, wie viele gleichzeitig unterwegs sind. Ein Job mit einem moderaten Ratenlimit, aber unbegrenzter Nebenläufigkeit, wird trotzdem in dem Moment hunderte gleichzeitige Verbindungen zu einem Ursprung öffnen, in dem dieser sich verlangsamt, weil langsame Antworten dazu führen, dass sich Anfragen stauen.
Begrenzen Sie die Nebenläufigkeit pro Ziel mit einem Semaphor, und dimensionieren Sie sie anhand dessen, was das Ziel toleriert, nicht anhand dessen, was Ihre Maschine öffnen kann. Auf der Proxy-Seite sind concurrent connections im Allgemeinen nicht Ihre Einschränkung, was es leicht macht zu vergessen, dass die Toleranz des Ziels es ist. Die Verteilungsfrage, wie viele Adressen das impliziert, wird in how many proxy IPs you actually need durchgearbeitet.
Adaptive Drosselung schlägt eine feste Zahl
Eine statische Rate ist eine Vermutung, die falsch wird. Seiten ändern ihre Toleranz, werden unter Last langsamer oder verschärfen Limits zu Spitzenzeiten. Das robustere Muster ist, sich basierend auf Beobachtungen anzupassen, im Stil von additive increase und multiplicative decrease: die Rate langsam nach oben kriechen lassen, solange alles gesund ist, und beim ersten Zeichen von Belastung scharf zurückschneiden.
Lösen Sie die Verringerung durch ein zusammengesetztes Signal aus, nicht durch einen einzelnen Statuscode: ein 429, ein steigender Anteil an Challenge-Seiten, steigende Latenz oder ein Abfall der validierten Erfolgsrate. Erholen Sie sich dann schrittweise, statt direkt zur alten Rate zurückzuspringen, da eine unmittelbare Rückkehr auf volle Geschwindigkeit nach einer Blockierung selbst ein erkennbares Muster ist.
class AdaptiveRate:
def __init__(self, start=2.0, floor=0.2, ceiling=10.0):
self.rate, self.floor, self.ceiling = start, floor, ceiling
def ok(self): # healthy response
self.rate = min(self.ceiling, self.rate * 1.02) # creep up
def strained(self): # 429, challenge, timeout, latency spike
self.rate = max(self.floor, self.rate * 0.5) # back off hard
Wo Drosselung auf Rotation und Retries trifft
Drei Wechselwirkungen sind es wert, explizit gemacht zu werden, denn sie falsch anzugehen macht die Drosselung zunichte.
Beantworten Sie ein Ratensignal nicht durch Rotieren. Ein 429 bedeutet: langsamer werden. Auf eine frische Adresse zu wechseln, um das gleiche Tempo beizubehalten, ist genau das Verhalten, das aus einer gedrosselten Adresse einen verbrannten Pool macht, und es ist genau das, was Traffic ausweichend statt bloß enthusiastisch aussehen lässt. Warten Sie, wenn das Signal die Rate betrifft; rotieren Sie, wenn das Signal die Adresse betrifft.
Retries müssen den Limiter respektieren. Ein Retry ist eine weitere Anfrage und muss wie jede andere ein Token beziehen, sonst umgeht Ihr Fehlerpfad still die Dosierung, die Sie aufgebaut haben. Retry-Stürme sind die häufigste Art, wie aus einem gedrosselten Job ein blockierter wird, was das Thema von retry and backoff ist.
Sticky Sessions konzentrieren die Last. Eine Adresse für einen mehrstufigen Ablauf festzuhalten bedeutet, dass diese Adresse die gesamte Sequenz trägt, sodass Pro-IP-Dosierung innerhalb einer sticky session mehr zählt als bei rotierender Arbeit, bei der sich die Last natürlich verteilt.
Höflichkeit ist Eigeninteresse
Es ist leicht, all dies als Compliance-Pflicht zu lesen, aber die Anreize zeigen in die gleiche Richtung. Dosierung bewahrt die reputation der Adressen, auf die Sie sich verlassen, hält Ihre Erfolgsrate hoch, sodass Sie nicht für Bandbreite bei Challenge-Seiten bezahlen, und vermeidet den Eskalationszyklus, in dem aggressives Sammeln strengere Abwehrmaßnahmen provoziert, die das Ziel für alle schwerer machen, Sie selbst eingeschlossen, im nächsten Quartal. Die von einer Seite erklärten Limits, ihre Robots-Direktiven und ihre Nutzungsbedingungen zu respektieren, ist sowohl das Richtige als auch das Günstigere.
Das Fazit
Ein Pool entfernt keine Ratenlimits, er verschiebt sie. Drosseln Sie pro Ziel statt global, denn eine einzige Zahl ist für jede Seite, mit der Sie interagieren, falsch. Finden Sie die tatsächliche Toleranz jedes Ziels aus dessen eigenen Signalen heraus, befolgen Sie Retry-After und 429 als Anweisungen statt als Hindernisse, und kalibrieren Sie anhand der validierten Erfolgsrate statt der Statuscodes. Implementieren Sie mit einem gemeinsam genutzten Token-Bucket plus Jitter, begrenzen Sie Nebenläufigkeit getrennt von der Rate, und machen Sie das Limit adaptiv, sodass es schnell degradiert und langsam erholt. Halten Sie dann die Wechselwirkungen klar: beantworten Sie ein Dosierungssignal niemals durch Rotieren von Adressen, leiten Sie Retries immer durch den Limiter, und dosieren Sie innerhalb sticky Sessions strenger. Gut umgesetzt ist Drosselung das, was einen Pool mit seiner tatsächlichen Kapazität laufen lässt, statt sie zu verbrennen.
Der Pool selbst ist das, was Ihnen Spielraum gibt: residential proxies verteilen einen richtig dosierten Job über viele echte, hausähnliche Adressen mit Länder- und Städte-Targeting, und per-GB pricing bedeutet, dass ein Job, der gut dosiert und nur das abruft, was er braucht, weniger kostet als einer, der hämmert und wiederholt.