API Engineering

Atténuer les limites de débit des API sans déclencher les heuristiques anti-abus

4 min read Published Updated 808 words

La plupart des implémentations de limites de débit d'API sont trivialement contournées par une rotation naïve de proxy — mais cette même rotation déclenche des heuristiques anti-abus en quelques minutes. Le véritable défi n'est pas seulement de rester en dessous de la limite, mais de le faire sans ressembler à un bot. Cela nécessite de comprendre les deux périmètres distincts de limitation (par IP et par clé), d'appliquer un backoff avec gigue, de randomiser la rotation du pool et de façonner le timing des requêtes pour imiter le trafic organique. Le modèle du seau à jetons, superposé à un pool de proxies, fournit une base robuste.

Par IP vs Par clé : les deux axes de la limitation de débit

Les limites de débit opèrent sur au moins deux dimensions indépendantes : l'IP source et la clé API (ou jeton). Une seule clé peut autoriser 5 000 requêtes par heure (la limite authentifiée de GitHub), mais la même clé depuis une seule IP peut être limitée à un plafond de rafale plus bas. Les requêtes non authentifiées sont encore plus contraintes — généralement 60 requêtes par heure par IP. Ignorer la dimension IP est le moyen le plus rapide de déclencher une réponse 429 Too Many Requests (RFC 6585). Un pool de proxies doit répartir les requêtes sur plusieurs IP pour éviter de saturer une seule origine, mais chaque IP partage toujours la même clé. Si la limite globale de la clé est de 5 000/h et que vous avez 50 proxies, chaque proxy ne peut effectuer que 100 requêtes par heure avant que le compteur au niveau de la clé ne se déclenche. Cartographiez les deux limites avant d'écrire une seule ligne de code.

Backoff avec gigue : la différence entre un comportement poli et prévisible

Un backoff exponentiel sans gigue est une empreinte digitale. Les serveurs voient des requêtes arriver à des intervalles parfaitement doublés et les signalent comme automatisées. La solution est la gigue complète : sleep(random.uniform(0, min(cap, base * 2 ** attempt))). Cela étale les tentatives sur la fenêtre temporelle, rendant le motif indiscernable d'une rafale d'utilisateurs réels. La propre documentation d'AWS sur le backoff exponentiel recommande la gigue exactement pour cette raison. L'extrait Python suivant implémente un seau à jetons qui se remplit à un rythme fixe, dort avec gigue lorsqu'il est vide et fait tourner le proxy à chaque acquisition réussie :

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

Rotation du pool de proxies : randomisez, ne faites pas de round-robin

Un round-robin séquentiel dans une liste de proxies est détectable. Un serveur qui voit des requêtes provenant de 1.2.3.4, puis 5.6.7.8, puis 9.10.11.12 en parfaite synchronisation corrélera le motif entre les IP. Au lieu de cela, choisissez les proxies aléatoirement avec remise et ajoutez un temps de repos par proxy. Les répertoires de proxies publics signalent des taux d'échec de 60 à 80 % — un proxy qui renvoie une 429 ou une erreur de connexion doit être rétrogradé dans une file d'attente morte pendant au moins 60 secondes. Implémentez une sélection aléatoire pondérée où les proxies récemment réussis ont plus de chances d'être choisis. Le seau à jetons ci-dessus utilise un simple rotate(1) pour plus de clarté ; en production, remplacez-le par random.choice() et suivez les comptes d'échec par proxy.

Façonnage des requêtes : imitez la moyenne sans franchir la ligne

Même avec un seau à jetons et un pool de proxies parfaits, un taux de requêtes constant d'exactement 2,0 par seconde n'est pas naturel. Les vrais utilisateurs marquent des pauses, regroupent et varient les intervalles entre les requêtes. Ajoutez un petit délai aléatoire (par exemple time.sleep(random.uniform(0.1, 0.5))) avant chaque appel acquire(), et faites varier le taux du seau à jetons de ±10 % toutes les quelques minutes. N'ajoutez toutefois pas de délais qui dépassent la fenêtre de limite de débit documentée de l'API — cela irait à l'encontre du but recherché. L'objectif est de rester dans la limite tout en donnant l'impression d'être un groupe de clients légitimes. Un sur-façonnage (par exemple, insérer des délais de frappe humains) est un effort inutile ; l'API se soucie de la fréquence des requêtes, pas du timing entre les frappes.

Atténuation éthique : sachez où se trouve la limite

Chaque technique décrite ici est une optimisation légitime — jusqu'à ce qu'elle viole les conditions d'utilisation (ToS) de l'API. La plupart des ToS interdisent explicitement le « contournement des limites de débit » ou « l'utilisation de moyens automatisés pour accéder au service ». Lire les ToS n'est pas optionnel. Si l'API autorise 100 requêtes par minute par clé, utiliser 50 proxies effectuant chacun 2 requêtes par minute est conforme. Utiliser 100 proxies effectuant chacun 100 requêtes par minute ne l'est pas — c'est un abus, quelle que soit l'intelligence de votre gigue. La distinction réside dans l'intention et le volume. Un seau à jetons superposé à un pool de proxies est un outil ; le même outil qui scrape éthiquement un répertoire public peut aussi être utilisé pour DDoS une petite API. Documentez vos objectifs de limite de débit, auditez vos logs pour les 429 et ne dépassez jamais le plafond documenté par clé. Si vous avez besoin de plus de débit, demandez une limite plus élevée ou payez pour un plan dédié.