C'est la concurrence, pas les crédits, qui détermine la durée de votre job de scraping
Le même job de 200 000 requêtes prend 22 heures à 5 emplacements concurrents, 4,4 heures à 25. Pourquoi les plans plafonnent la concurrence.

Deux comptes peuvent avoir exactement le même solde de crédits et terminer le même job à des vitesses radicalement différentes. Le chiffre qui détermine la durée d’un job par lot est la concurrence, le nombre de requêtes que vous êtes autorisé à avoir en vol en même temps, et ce n’est généralement pas le chiffre que les gens vérifient en premier.
Le calcul
Disons qu’un job compte 200 000 requêtes, et que chacune prend environ 2 secondes en aller-retour, une moyenne raisonnable pour une page rendue ou une recherche avec des tentatives. Le travail total est de 400 000 secondes. La durée d’exécution dépend entièrement du nombre de ces secondes que vous pouvez dépenser en parallèle :
| Emplacements concurrents | Durée totale |
|---|---|
| 1 | 111,1 heures (4,6 jours) |
| 5 | 22,2 heures |
| 10 | 11,1 heures |
| 25 | 4,4 heures |
Même job, même coût en crédits, un facteur 25 de différence en temps réel entre 1 emplacement et 25. Si un job nocturne doit se terminer avant le début d’une journée de travail, la concurrence est le chiffre à vérifier avant de s’engager sur un planning, pas le solde de crédits.
Pourquoi les plans plafonnent la concurrence, pas seulement les crédits
Les crédits mesurent la quantité de travail que vous êtes autorisé à faire au total. La concurrence mesure la part de ce travail qui peut se produire au même instant, et elle existe pour une raison différente : chaque requête en vol occupe une session de navigateur, une connexion proxy ou les deux, sur une infrastructure qui coûte de l’argent à maintenir provisionnée, que vous soyez proche ou non de votre limite de crédits.
Sur FastAF, la concurrence évolue avec le pack de crédits que vous achetez : Free autorise 2 requêtes concurrentes, chaque pack payant 5 (Starter, Growth, Scale et Pro), Business 25 (octobre 2026). Un compte Free peut exécuter deux requêtes à la fois ; un compte Business peut en avoir 25 ouvertes simultanément. Envoyer plus de requêtes que la concurrence de votre pack ne les met pas poliment en file d’attente pour aller plus vite, ça commence à renvoyer des réponses 429, donc ce plafond vaut la peine d’être pris en compte dans la conception de votre job plutôt que découvert par essai-erreur.
Écrire un pool de workers qui respecte le plafond
L’erreur qui vous coûte réellement du temps est de régler max_workers de façon optimiste et de laisser la logique de relance masquer les 429 qui en résultent. Réglez-le sur la concurrence réelle de votre pack et les 429 cessent en grande partie de se produire dès le départ.
ThreadPoolExecutor, l’option la plus simple pour la plupart des scripts :
import time
import requests
from concurrent.futures import ThreadPoolExecutor
API_KEY = "faf_live_..." # votre clé FastAF
CONCURRENCY = 5 # correspond à votre pack : Free 2, de Starter à Pro 5, Business 25
def fetch(url, max_retries=5):
for attempt in range(max_retries):
resp = requests.get(
"https://api.fastaf.co/v1/scrape",
params={"url": url},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=90,
)
if resp.status_code == 200:
return resp.json()
if resp.status_code == 429:
wait = int(resp.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
continue
if resp.status_code in (502, 503, 504):
time.sleep(2 ** attempt) # échec upstream transitoire, non facturé
continue
resp.raise_for_status()
raise RuntimeError(f"gave up on {url} after {max_retries} attempts")
urls = [...] # vos 200 000 URLs
results = []
with ThreadPoolExecutor(max_workers=CONCURRENCY) as pool:
for result in pool.map(fetch, urls):
results.append(result)
asyncio, si le reste du pipeline est déjà asynchrone, avec un sémaphore qui fait le même travail que max_workers ci-dessus :
import asyncio
import httpx
CONCURRENCY = 5
sem = asyncio.Semaphore(CONCURRENCY)
async def fetch(client, url, max_retries=5):
async with sem:
for attempt in range(max_retries):
resp = await client.get(
"https://api.fastaf.co/v1/scrape",
params={"url": url},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=90,
)
if resp.status_code == 200:
return resp.json()
if resp.status_code == 429:
wait = int(resp.headers.get("Retry-After", 2 ** attempt))
await asyncio.sleep(wait)
continue
if resp.status_code in (502, 503, 504):
await asyncio.sleep(2 ** attempt)
continue
resp.raise_for_status()
raise RuntimeError(f"gave up on {url} after {max_retries} attempts")
async def run(urls):
async with httpx.AsyncClient() as client:
return await asyncio.gather(*(fetch(client, u) for u in urls))
results = asyncio.run(run(urls))
Le sémaphore maintient exactement le même plafond de concurrence que max_workers ci-dessus ; choisissez le style qui correspond au reste de votre code, pas l’un pour des raisons de correction.
Les deux choses qui causent réellement des 429 en pratique
Des workers au-dessus du plafond du pack. L’évidence : max_workers=50 sur un pack Starter (5 concurrentes) signifie que 45 requêtes sur 50 échouent immédiatement avec 429, et la logique de backoff ne fait que rendre ça lent au lieu de rapide.
Un second processus que vous avez oublié. Un cron job et un test manuel lancés contre la même clé API en même temps doublent votre concurrence effective sans qu’aucun des deux processus ne soit au courant de l’autre. Si vous voyez des 429 malgré un max_workers correctement réglé, vérifiez qu’un second script ne tourne pas contre la même clé avant de supposer que le plafond a changé.
En bref
Avant d’estimer la durée d’un job par lot, vérifiez la concurrence de votre pack, pas seulement son solde de crédits : 2 pour Free, 5 pour Starter, Growth, Scale et Pro, 25 pour Business à partir d’octobre 2026. Réglez la taille de votre pool de workers sur ce chiffre, respectez Retry-After sur un 429, et reculez avec un délai exponentiel sur un 5xx. La durée d’un job est (nombre total de requêtes x latence moyenne) / emplacements concurrents, et la concurrence est le seul de ces trois éléments que vous pouvez réellement changer en achetant un pack plus grand.