Les limites de débit du scraping expliquées

Comment les limites de taux fonctionnent, comment les sites détectent les racleurs, et des stratégies pratiques pour rester sous les limites. Inclut le code adaptatif et les modèles de limitation des taux distribués.

Les limites de débit du scraping expliquées

Quelles sont les limites de taux de scraping?

Les limites de taux sont les murs invisibles que les sites Web construisent pour contrôler la rapidité avec laquelle un seul client peut faire des requêtes. Lorsque vous grattez un site trop agressivement, vous frappez ces murs — et les conséquences vont des ralentissements temporaires aux interdictions permanentes de PI. Comprendre comment les limites de taux fonctionnent, comment ils vous détectent et comment rester sous eux est fondamental pour construire des racleurs qui fournissent des données fiables.

Ce guide explique les mécanismes derrière la limitation de vitesse, l'utilisation des sites Web de signaux de détection, et des stratégies pratiques pour le throttling adaptatif qui permettent à vos racleurs de fonctionner en douceur.

Pour un aperçu plus large du grattage avec des proxies, voir notre Guide complet des produits de scraping Web. Pour éviter les blocs en général, lire : Comment gratter les sites Web sans se faire bloquer.

Comment fonctionne la limitation des taux

Les sites web appliquent des limites de vitesse à plusieurs couches, chacune avec une granularité de détection différente:

Couche 1: Limites de taux fondées sur la PI

L'approche la plus courante. Le serveur suit les requêtes par adresse IP dans une fenêtre de temps. Excédé le seuil et vous recevez HTTP 429 (Trop de demandes) ou 503 réponses.

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

Couche 2 : Limites fondées sur la session/les témoins

Les pistes demandent la fréquence par session ou cookie de navigateur. Même si vous faites pivoter des IP, la même session en appuyant rapidement sur le serveur déclenchera des limites.

Calque 3 : Limites fondées sur les comptes

Pour les sites nécessitant une connexion, les limites sont liées au compte utilisateur indépendamment de l'IP. Commun sur les API et les plateformes SaaS.

Couche 4: Analyse comportementale

Les systèmes avancés comme Cloudflare, PerimeterX et Akamai analysent les modèles comportementaux : chronométrage des requêtes, flux de navigation, mouvements de souris (dans les contextes du navigateur). Cette couche est la plus difficile à contourner car elle ne repose pas sur des compteurs simples.

Signalisation de la limite de vitesse commune

Les sites Web utilisent simultanément plusieurs signaux pour détecter le grattage automatisé:

Signalisation de la limite de vitesse commune
SignalCe qu'il détecteDifficulté à éluder
Demandes par IP par minuteVitesse bruteFacile (utiliser des proxies)
Demandes par IP par heure/jourVolume soutenuMoyenne (IP rotatives)
Fréquence des demandesIntervalles de type machineMoyenne
En-têtes manquants/mauvaisesClients non-navigateursFacile ( réglez les en-têtes appropriés)
Modèles d'URL séquentielsRampage systématiqueMoyenne (ordre randomisé)
Empreinte TLSBibliothèque vs navigateurDur (utiliser des navigateurs réels)
Exécution JavaScriptNavigateur sans têteDur (configuration avancée)
Manifestations sur la souris et le clavierComportement du botTrès dur

En savoir plus sur les mécanismes de détection dans notre guide Comment les systèmes anti-bot détectent les proxies.

Codes de réponse HTTP qui limitent le taux de signal

Savoir quels codes HTTP indiquent des limites de vitesse vous aide à construire une logique de réessayer appropriée :

Codes de réponse HTTP qui limitent le taux de signal
CodeSignificationDécision
200 (avec CAPTCHA)Bloc souple — page de défi servieRotation IP, ralentissement
403 InterditIP ou session bloquéeRotation immédiate de l'IP
429 Trop de demandesTaux limite explicite atteintAttendez et réessayez avec le recul
503 Service non disponiblesurcharge ou bloc du serveurEn arrière, vérifier si elle est bloquée
302/307 vers l'URL CAPTCHARedirection des défisRotation IP, réduction de la vitesse

Stratégie 1: Throttling respectueux

L'approche la plus simple — maintenez votre taux de demande bien en dessous de ce que la cible permet. Cela signifie moins d'échecs, moins de bande passante gaspillée et un grattage plus durable.

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

Stratégie 2: Throttling adaptatif

Au lieu d'un taux fixe, ajustez dynamiquement votre vitesse en fonction des réponses que vous recevez. Accélérez quand tout fonctionne, ralentissez quand vous voyez des signes d'avertissement.

Mise en œuvre de 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

Mise en œuvre de Node.js

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;
}

Stratégie 3 : Limite des taux distribués

Lorsque vous utilisez plusieurs instances de racleur en parallèle, coordonnez la limite de taux pour tous les travailleurs. Sans coordination, chaque travailleur respecte sa propre limite, mais le trafic combiné dépasse encore la cible.

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

Stratégie 4 : Requête avec priorité

Pour les projets de démolition complexes, utilisez une file d'attente prioritaire qui gère les limites de taux par domaine cible :

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

Lecture Robots.txt pour Conseils de Taux

De nombreux sites publient leurs préférences de rampe dans robots.txt. Les Crawl-delay directive vous indique le minimum de secondes entre les requêtes:

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

Erreurs de limite de taux commune

  • En ignorant 429 réponses. De nombreux racleurs traitent toutes les réponses non-200 de la même façon. Un 429 vous indique exactement ce qui s'est passé — utilisez l'en-tête Retry-After et reculez.
  • Délais fixes sans blague. Une demande exactement toutes les 2.000 secondes semble robotique. Ajoutez une variation aléatoire (jitter) à vos retards.
  • Ne pas coordonner les travailleurs parallèles. Cinq travailleurs chacun faisant 10 RPM égale 50 RPM au total. Utilisez un limiteur de taux partagé.
  • Rotation des IP sans ralentissement. La rotation IP vous fait gagner du temps, mais si chaque nouvelle IP martele immédiatement le site, la détection avancée vous rattrapera encore. Combiner la rotation avec un étranglement approprié.
  • Scraping pendant les heures de pointe. Les sites sont plus agressifs avec une limitation de vitesse pendant les périodes de forte circulation. Planifiez de fortes rampes pendant les heures creuses pour le fuseau horaire de la cible.

Pour calculer le nombre de proxies dont vous avez besoin pour soutenir votre grattage à taux limité, voir Combien de proxies avez-vous besoin pour le scraping?. Pour les stratégies de rotation par procuration qui complètent les limites de taux, lire Stratégies de rotation par procuration pour le scrapage à grande échelle.

Commencez avec le grattage correctement limité à la vitesse ProxyHat Python SDK ou explorer plans de prix pour votre projet.

Foire aux questions

Que se passe-t-il lorsque je dépasse une limite de taux?

La réponse dépend du site. La plupart renvoient HTTP 429 avec un en-tête Retry-After. Certains servent les CAPTCHA. Les sites agressifs bloquent immédiatement l'IP avec une réponse 403. Dans le pire des cas, les violations répétées conduisent à des interdictions permanentes de la propriété intellectuelle.

Comment puis-je trouver la limite de tarifs d'un site?

Commencez lentement et augmentez graduellement tout en surveillant les codes de réponse. Vérifiez robots.txt pour les directives Crawl-delay. Observez les en-têtes de réponse pour les champs X-RateLimit-Limit et X-RateLimit-Remaining. Certaines API publient leurs limites dans la documentation.

Est-ce que l'utilisation de limites de taux de contournement de proxies?

Proxies distribue des requêtes sur plusieurs IP, de sorte que chaque IP reste sous la limite par IP. Cependant, des sites sophistiqués suivent également les séances, les empreintes digitales et les modèles comportementaux. Les proxies sont nécessaires, mais pas suffisantes — combinez-les avec des motifs de demande appropriés et réalistes.

Quel est le taux de demande le plus sûr pour le grattage?

Il n'y a pas de réponse universelle. Pour les cibles agressives comme Google ou Amazon, 1-5 requêtes par minute par IP est sûr. Pour les sites faiblement protégés, 20-60 RPM par IP peuvent fonctionner. Commencez toujours par la prudence et augmentez en fonction des taux de réussite observés.

Prêt à commencer ?

Accédez à plus de 50M d'IPs résidentielles dans plus de 148 pays avec filtrage IA.

Voir les tarifsProxies résidentiels
← Retour au Blog