Scraping Rate Limits erklärt

Wie Geschwindigkeitslimits funktionieren, wie Standorte Abstreifer erkennen, und praktische Strategien, um unter Grenzen zu bleiben. Enthält adaptive Drosselung und verteilte Ratenbegrenzungsmuster.

Scraping Rate Limits erklärt

Was sind Scraping Rate Limits?

Grenzwerte sind die unsichtbaren Wände, die Webseiten bauen, um zu kontrollieren, wie schnell jeder einzelne Client Anfragen stellen kann. Wenn Sie eine Seite zu aggressiv abkratzen, treffen Sie diese Wände – und die Konsequenzen reichen von temporären Verlangsamungen bis zu dauerhaften IP-Banns. Das Verständnis, wie Geschwindigkeitslimits funktionieren, wie sie Sie erkennen und wie Sie unter ihnen bleiben, ist grundlegend für den Aufbau von Schabern, die Daten zuverlässig liefern.

Diese Anleitung erklärt die Mechanik hinter der Ratenbegrenzung, die Erkennungssignale Webseiten verwenden, und praktische Strategien zur adaptiven Drosselung, die Ihre Schaber reibungslos laufen lassen.

Für einen breiteren Überblick über das Schrotten mit Proxien, siehe unsere Kompletter Leitfaden für Web Scraping Proxies. Zur Vermeidung von Blöcken im Allgemeinen, lesen Wie man Websites löscht, ohne blockiert zu werden.

Wie Rate Limiting funktioniert

Websites implementieren Ratenlimits in mehreren Schichten, jeweils mit unterschiedlicher Erkennungsgranulat:

Ebene 1: IP-basierte Grenzwerte

Der häufigste Ansatz. Der Server verfolgt Anfragen pro IP-Adresse innerhalb eines Zeitfensters. Übersteigen Sie die Schwelle und Sie erhalten HTTP 429 (Too Many Requests) oder 503 Antworten.

# Typical rate limit behavior
Request 1-50:    HTTP 200 (normal)
Request 51:      HTTP 429 (rate limited)
Wait 60 seconds...
Request 52:      HTTP 200 (reset)

Ebene 2: Session/Cookie-basierte Limits

Verfolgen Sie die Anforderungsfrequenz pro Sitzung oder Browser-Cookie. Auch wenn Sie IPs drehen, wird der gleiche Session-Token, der den Server schnell trifft, Grenzen auslösen.

Ebene 3: Kontenbasierte Grenzen

Für Websites, die einen Login benötigen, sind Grenzen unabhängig von IP an das Benutzerkonto gebunden. Gemeinsam auf APIs und SaaS-Plattformen.

Ebene 4: Verhaltensanalyse

Erweiterte Systeme wie Cloudflare, PerimeterX und Akamai analysieren Verhaltensmuster: Anforderung Timing, Navigationsfluss, Mausbewegungen (in Browser-Kontexten). Diese Schicht ist am schwersten zu umgehen, weil sie sich nicht auf einfache Zähler verlässt.

Common Rate Limit Detection Signale

Webseiten verwenden mehrere Signale gleichzeitig, um automatisiertes Abkratzen zu erkennen:

Common Rate Limit Detection Signale
SignalWas es erkenntSchwierigkeiten bei Evade
Anfragen pro IP pro MinuteRohgeschwindigkeitEinfach (verwenden Sie Proxies)
Anfragen pro IP pro Stunde/TagBestandMedium (rotate IPs)
Fristsetzung anfordernMaschinenartige IntervalleMedium (add jitter)
Fehlende/wrong KopfzeilenKunden ohne BrowserEinfach (Set richtige Header)
Sequenzielle URL-MusterSystematische RaupenMedium (randomize order)
TLS FingerabdruckBibliothek vs BrowserSchwer (verwenden Sie echte Browser)
JavaScript AusführungKopfloser BrowserHard (erweitert)
Maus / Keyboard VeranstaltungenBot-VerhaltenSehr hart

Erfahren Sie mehr über Erkennungsmechanismen in unserer Anleitung Wie Anti-Bot-Systeme Proxies erkennen.

HTTP-Antwort-Codes, die die Signalrate begrenzen

Wenn Sie wissen, welche HTTP-Codes eine Geschwindigkeitsbegrenzung angeben, können Sie die richtige Retry-Logik erstellen:

HTTP-Antwort-Codes, die die Signalrate begrenzen
CodeBedeutungAktion
200 (mit CAPTCHA)Weichblock — Herausforderung Seite dientIP drehen, verlangsamen
403 VerbotenIP oder Sitzung blockiertIP sofort drehen
429 Too Viele AnfragenExplicit rate limit hitWarten und retry mit Backoff
503 Service Nicht verfügbarServerüberlastung oder BlockBackoff, überprüfen, ob blockiert
302/307 zu CAPTCHA URLRedirect von ChallengeIP drehen, Geschwindigkeit reduzieren

Strategie 1: Respektive Drosselung

Der einfachste Ansatz — halten Sie Ihre Anfrage Rate gut unter, was das Ziel erlaubt. Dies bedeutet weniger Fehler, weniger verschwendete Bandbreite und nachhaltiges Abkratzen.

import requests
import time
import random
PROXY = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080"
def respectful_scrape(urls: list[str], rpm_limit: int = 10) -> list[str]:
    """Scrape URLs while respecting a requests-per-minute limit."""
    delay = 60.0 / rpm_limit
    results = []
    for url in urls:
        try:
            resp = requests.get(
                url,
                proxies={"http": PROXY, "https": PROXY},
                timeout=30
            )
            results.append(resp.text if resp.status_code == 200 else None)
        except requests.RequestException:
            results.append(None)
        # Add delay with random jitter (±30%) to look less robotic
        jitter = delay * random.uniform(0.7, 1.3)
        time.sleep(jitter)
    return results

Strategie 2: Adaptive Drosselung

Anstelle einer festen Rate, dynamisch einstellen Sie Ihre Geschwindigkeit basierend auf den Antworten, die Sie erhalten. Beschleunigen Sie sich, wenn alles funktioniert, verlangsamen Sie sich, wenn Sie Warnzeichen sehen.

Implementierung von Python

import requests
import time
import random
from dataclasses import dataclass, field
PROXY = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080"
@dataclass
class AdaptiveThrottle:
    """Automatically adjusts request rate based on server responses."""
    base_delay: float = 2.0      # seconds between requests
    min_delay: float = 0.5
    max_delay: float = 30.0
    current_delay: float = 2.0
    success_streak: int = 0
    warning_codes: set = field(default_factory=lambda: {429, 403, 503})
    def on_success(self):
        self.success_streak += 1
        # Speed up after 10 consecutive successes
        if self.success_streak >= 10:
            self.current_delay = max(self.current_delay * 0.85, self.min_delay)
            self.success_streak = 0
    def on_rate_limit(self):
        self.success_streak = 0
        # Double the delay on rate limit
        self.current_delay = min(self.current_delay * 2.0, self.max_delay)
    def on_block(self):
        self.success_streak = 0
        # Aggressive backoff on block
        self.current_delay = min(self.current_delay * 3.0, self.max_delay)
    def wait(self):
        jitter = self.current_delay * random.uniform(0.7, 1.3)
        time.sleep(jitter)
def scrape_adaptive(urls: list[str]) -> list[dict]:
    throttle = AdaptiveThrottle()
    results = []
    for url in urls:
        try:
            resp = requests.get(
                url,
                proxies={"http": PROXY, "https": PROXY},
                timeout=30
            )
            if resp.status_code == 200:
                throttle.on_success()
                results.append({"url": url, "status": 200, "body": resp.text})
            elif resp.status_code == 429:
                throttle.on_rate_limit()
                # Check Retry-After header
                retry_after = int(resp.headers.get("Retry-After", 0))
                if retry_after:
                    time.sleep(retry_after)
                results.append({"url": url, "status": 429, "body": None})
            elif resp.status_code == 403:
                throttle.on_block()
                results.append({"url": url, "status": 403, "body": None})
            else:
                results.append({"url": url, "status": resp.status_code, "body": resp.text})
        except requests.RequestException as e:
            throttle.on_block()
            results.append({"url": url, "status": 0, "error": str(e)})
        throttle.wait()
        print(f"Current delay: {throttle.current_delay:.1f}s")
    return results

Node.js Implementierung

const HttpsProxyAgent = require('https-proxy-agent');
const fetch = require('node-fetch');
class AdaptiveThrottle {
  constructor() {
    this.currentDelay = 2000; // ms
    this.minDelay = 500;
    this.maxDelay = 30000;
    this.successStreak = 0;
  }
  onSuccess() {
    this.successStreak++;
    if (this.successStreak >= 10) {
      this.currentDelay = Math.max(this.currentDelay * 0.85, this.minDelay);
      this.successStreak = 0;
    }
  }
  onRateLimit() {
    this.successStreak = 0;
    this.currentDelay = Math.min(this.currentDelay * 2, this.maxDelay);
  }
  onBlock() {
    this.successStreak = 0;
    this.currentDelay = Math.min(this.currentDelay * 3, this.maxDelay);
  }
  async wait() {
    const jitter = this.currentDelay * (0.7 + Math.random() * 0.6);
    return new Promise(resolve => setTimeout(resolve, jitter));
  }
}
async function scrapeAdaptive(urls) {
  const throttle = new AdaptiveThrottle();
  const agent = new HttpsProxyAgent('http://USERNAME:PASSWORD@gate.proxyhat.com:8080');
  const results = [];
  for (const url of urls) {
    try {
      const res = await fetch(url, { agent, timeout: 30000 });
      if (res.ok) {
        throttle.onSuccess();
        results.push({ url, status: res.status, body: await res.text() });
      } else if (res.status === 429) {
        throttle.onRateLimit();
        const retryAfter = parseInt(res.headers.get('retry-after') || '0');
        if (retryAfter) await new Promise(r => setTimeout(r, retryAfter * 1000));
        results.push({ url, status: 429, body: null });
      } else if (res.status === 403) {
        throttle.onBlock();
        results.push({ url, status: 403, body: null });
      }
    } catch (err) {
      throttle.onBlock();
      results.push({ url, status: 0, error: err.message });
    }
    await throttle.wait();
    console.log(`Current delay: ${throttle.currentDelay.toFixed(0)}ms`);
  }
  return results;
}

Strategie 3: Distributed Rate Limiting

Wenn mehrere Abstreifer-Instanzen parallel laufen, koordinieren Sie die Tarifgrenze für alle Arbeitnehmer. Ohne Koordination respektiert jeder Arbeiter seine eigene Grenze, aber der kombinierte Verkehr überfordert das Ziel noch.

import requests
import time
import threading
class DistributedRateLimiter:
    """Thread-safe rate limiter for multiple scraper workers."""
    def __init__(self, max_rpm: int):
        self.min_interval = 60.0 / max_rpm
        self.lock = threading.Lock()
        self.last_request_time = 0.0
    def acquire(self):
        """Block until it is safe to make the next request."""
        with self.lock:
            now = time.time()
            elapsed = now - self.last_request_time
            if elapsed < self.min_interval:
                time.sleep(self.min_interval - elapsed)
            self.last_request_time = time.time()
# Shared limiter across all threads
limiter = DistributedRateLimiter(max_rpm=30)
PROXY = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080"
def worker(urls: list[str], results: list):
    for url in urls:
        limiter.acquire()
        try:
            resp = requests.get(
                url,
                proxies={"http": PROXY, "https": PROXY},
                timeout=30
            )
            results.append({"url": url, "status": resp.status_code})
        except Exception as e:
            results.append({"url": url, "error": str(e)})

Strategie 4: Queue mit Priorität anfordern

Für komplexe Abstreifprojekte verwenden Sie eine Prioritätswarte, die Geschwindigkeitslimits pro Zieldomäne verwaltet:

import requests
import time
import heapq
import threading
from collections import defaultdict
PROXY = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080"
class DomainRateLimiter:
    """Per-domain rate limiting with priority queue."""
    def __init__(self, default_rpm: int = 10):
        self.default_rpm = default_rpm
        self.domain_limits = {}          # domain -> max RPM
        self.domain_last = defaultdict(float)  # domain -> last request time
        self.lock = threading.Lock()
    def set_limit(self, domain: str, rpm: int):
        self.domain_limits[domain] = rpm
    def wait_for_domain(self, domain: str):
        rpm = self.domain_limits.get(domain, self.default_rpm)
        min_interval = 60.0 / rpm
        with self.lock:
            now = time.time()
            elapsed = now - self.domain_last[domain]
            if elapsed < min_interval:
                time.sleep(min_interval - elapsed)
            self.domain_last[domain] = time.time()
# Configure per-domain limits
limiter = DomainRateLimiter(default_rpm=10)
limiter.set_limit("amazon.com", 3)        # Very conservative for Amazon
limiter.set_limit("example.com", 30)      # Lenient for simple sites
limiter.set_limit("google.com", 5)        # Moderate for Google

Reading Robots.txt für Rate Hints

Viele Seiten veröffentlichen ihre Crawl Präferenzen in robots.txt. Die Crawl-delay Die Richtlinie sagt Ihnen die Mindestsekunden zwischen Anträgen:

import requests
from urllib.parse import urlparse
from urllib.robotparser import RobotFileParser
def get_crawl_delay(base_url: str, user_agent: str = "*") -> float | None:
    """Extract Crawl-delay from robots.txt."""
    parsed = urlparse(base_url)
    robots_url = f"{parsed.scheme}://{parsed.netloc}/robots.txt"
    try:
        resp = requests.get(robots_url, timeout=10)
        if resp.status_code != 200:
            return None
        rp = RobotFileParser()
        rp.parse(resp.text.splitlines())
        delay = rp.crawl_delay(user_agent)
        return delay
    except Exception:
        return None
# Check before scraping
delay = get_crawl_delay("https://example.com")
if delay:
    print(f"Site requests {delay}s between requests")
else:
    print("No crawl-delay specified")

Gemeinsame Rate Limit Fehler

  • 429 Antworten ignorieren. Viele Schaber behandeln alle nicht-200 Antworten gleich. A 429 sagt Ihnen genau, was passiert ist – verwenden Sie den Retry-After Header und zurück.
  • Feste Verzögerungen ohne Jitter. Eine Anfrage genau alle 2.000 Sekunden sieht robotisch aus. Fügen Sie zufällige Variation (Jitter) zu Ihren Verzögerungen hinzu.
  • Nicht koordinieren Parallelarbeiter. Fünf Arbeiter, die jeweils 10 RPM machen, sind insgesamt 50 RPM. Verwenden Sie einen gemeinsamen Tarifbegrenzer.
  • Drehen von IPs ohne Verlangsamung. IP-Drehung kauft Sie Zeit, aber wenn jede neue IP sofort hämmert die Website, erweiterte Erkennung wird Sie noch fangen. Kombinieren Sie die Rotation mit der richtigen Drosselung.
  • Schrott während der Spitzenzeiten. Seiten sind aggressiver mit Ratenbegrenzung während der Hochverkehrszeiten. Schwere Raupen während der Sprechstunden für die Zeitzone des Ziels planen.

Um zu berechnen, wie viele Proxies Sie brauchen, um Ihr satzbegrenztes Abkratzen zu unterstützen, siehe Wie viele Proxies benötigen Sie für Schrott?. Für Proxy-Drehstrategien, die die Geschwindigkeitsbegrenzung ergänzen, lesen Proxy-Drehstrategien für großschalige Schrapierung.

Beginnen Sie mit richtig satzbegrenztem Abkratzen mit der ProxyHat Python SDK oder erkunden Preispläne für Ihr Projekt.

Häufig gestellte Fragen

Was passiert, wenn ich eine Ratengrenze überschreitet?

Die Antwort hängt von der Website ab. Die meisten Return HTTP 429 mit einem Retry-After Header. Einige dienen CAPTCHAs. Aggressive Websites blockieren die IP sofort mit einer 403 Antwort. Im schlimmsten Fall führen wiederholte Verstöße zu dauerhaften IP-Versagen.

Wie finde ich die Rate der Seite?

Beginnen Sie langsam und erhöhen Sie allmählich während der Überwachung Antwortcodes. Überprüfen Sie robots.txt für Crawl-Delay-Richtlinien. Beobachten Sie Antwort-Header für X-RateLimit-Limit und X-RateLimit-Remaining Felder. Einige APIs veröffentlichen ihre Grenzen in der Dokumentation.

Benutzt man Proxies Bypass Rate Limits?

Proxies vertreibt Anfragen über mehrere IPs, so dass jede IP unter der per-IP-Begrenzung bleibt. Allerdings verfolgen anspruchsvolle Seiten auch Sitzungen, Fingerabdrücke und Verhaltensmuster. Proxies sind notwendig, aber nicht ausreichend – kombinieren Sie sie mit richtigen Drosselung und realistischen Anforderungsmustern.

Was ist die sicherste Anforderungsrate für Schrott?

Es gibt keine universelle Antwort. Für aggressive Ziele wie Google oder Amazon, 1-5 Anfragen pro Minute pro IP ist sicher. Für leicht geschützte Standorte können 20-60 RPM pro IP funktionieren. Starten Sie immer konservativ und erhöhen Sie basierend auf den beobachteten Erfolgsquoten.

Bereit loszulegen?

Zugang zu über 50 Mio. Residential-IPs in über 148 Ländern mit KI-gesteuerter Filterung.

Preise ansehenResidential Proxies
← Zurück zum Blog