En dessous de 500 résolutions par jour, restez en séquentiel : vous vous épargnez une catégorie entière de bugs, pour un débit suffisant. Au-dessus, seul le parallèle tient le rythme. Le reste de l'arbitrage tient aux threads de votre offre et au temps de résolution du type visé — ce comparatif les tranche avec l'API CaptchaAI, code Python et Node.js à l'appui.
La règle de décision en trente secondes
| Votre situation | Approche | Pourquoi |
|---|---|---|
| Moins de 500 résolutions/jour | Séquentiel | Code lisible, débit suffisant |
| Plus de 500 résolutions/jour | Parallèle | Seule la concurrence tient le rythme |
| Fenêtre de traitement contrainte | Parallèle, concurrence bornée | Le lot doit finir à l'heure |
| Étapes dépendantes (session, panier) | Séquentiel | L'ordre conditionne l'étape suivante |
| Vous hésitez | Séquentiel d'abord | Migrez quand le lot déborde de la fenêtre |
Votre débit dépend de vos threads, pas de votre boucle
La plupart des équipes le découvrent en production : un asyncio.gather sur 200 tâches ne produit pas 200 résolutions simultanées si votre offre en autorise 50. Le plafond réel, c'est le thread.
- BASIC ($15/mois, 5 threads) : quelques centaines de résolutions/heure sur reCAPTCHA v2 ; le parallèle n'y apporte presque rien.
- ADVANCE ($90/mois, 50 threads) : le premier palier où l'asynchrone se justifie.
- PREMIUM ($170/mois, 100 threads) : au-delà, les gains deviennent marginaux pour la plupart des pipelines.
Réglez le sémaphore sur les threads de votre offre, jamais au-dessus.
Ce qui change vraiment entre les deux approches
Les chiffres ci-dessous reposent sur des mesures observées et des retours d'utilisateurs. Les résultats varient selon l'environnement, le volume et le moment de la journée.
| Facteur | Séquentiel | Parallèle |
|---|---|---|
| Débit (reCAPTCHA v2, médiane observée de 15 s) | ~240/heure | ~10 000+/heure |
| Complexité du code | Simple | Modérée à élevée |
| Gestion des erreurs | Une exception à la fois | Isolation par tâche indispensable |
| Empreinte mémoire | Minimale (~30 Mo) | Croît avec la concurrence (~100–500 Mo) |
| Coût | Identique : facturation au thread | Identique |
| Débogage | Trace linéaire | Plus difficile : timing, conditions de course |
| Ordre des résultats | Préservé nativement | Suivi explicite par index |
| Cas d'usage type | < 500 résolutions/jour | > 500 résolutions/jour |
La ligne à retenir est celle du coût : passer en parallèle ne change pas la facture. Les offres CaptchaAI se facturent au thread, avec des résolutions illimitées par thread ; le parallélisme arrive plus tôt au même total.
Résolution séquentielle : le chemin lisible
Un CAPTCHA à la fois : soumission, attente, interrogation, token, tâche suivante.
# sequential_solver.py
import os
import time
import requests
API_KEY = os.environ.get("CAPTCHAAI_KEY", "YOUR_API_KEY")
def solve_sequential(tasks):
"""Solve CAPTCHAs one by one."""
results = []
session = requests.Session()
for task in tasks:
# Submit
resp = session.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": task["sitekey"],
"pageurl": task["pageurl"],
"json": "1",
})
result = resp.json()
if result.get("status") != 1:
results.append({"error": result.get("request")})
continue
task_id = result["request"]
time.sleep(15)
# Poll
token = None
for _ in range(25):
poll = session.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get",
"id": task_id, "json": "1",
})
poll_result = poll.json()
if poll_result.get("status") == 1:
token = poll_result["request"]
break
if poll_result.get("request") != "CAPCHA_NOT_READY":
break
time.sleep(5)
results.append({"token": token} if token else {"error": "timeout"})
return results
# 10 tasks sequentially → ~150 seconds total
tasks = [{"sitekey": "SITEKEY", "pageurl": "https://example.com"}] * 10
start = time.time()
results = solve_sequential(tasks)
print(f"Completed in {time.time() - start:.0f}s")
Les cas où le séquentiel reste le bon choix
- Parcours mono-page : un formulaire, un CAPTCHA.
- Reproduction de bugs : un flux linéaire raccourcit le diagnostic.
- Faible volume : sous 500 résolutions/jour, l'asynchrone ne se rentabilise pas.
- Enchaînements contraints : le token d'une page conditionne l'URL suivante.
Résolution parallèle : le motif sémaphore et asyncio
Les tâches sont soumises puis interrogées en parallèle, sous une concurrence bornée.
# parallel_solver.py
import os
import asyncio
import aiohttp
API_KEY = os.environ.get("CAPTCHAAI_KEY", "YOUR_API_KEY")
async def solve_one(session, sitekey, pageurl, semaphore):
"""Solve a single CAPTCHA within concurrency limits."""
async with semaphore:
# Submit
async with session.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY, "method": "userrecaptcha",
"googlekey": sitekey, "pageurl": pageurl, "json": "1",
}) as resp:
result = await resp.json(content_type=None)
if result.get("status") != 1:
return {"error": result.get("request")}
task_id = result["request"]
await asyncio.sleep(15)
# Poll
for _ in range(25):
async with session.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get",
"id": task_id, "json": "1",
}) as resp:
poll_result = await resp.json(content_type=None)
if poll_result.get("status") == 1:
return {"token": poll_result["request"]}
if poll_result.get("request") != "CAPCHA_NOT_READY":
return {"error": poll_result.get("request")}
await asyncio.sleep(5)
return {"error": "timeout"}
async def solve_parallel(tasks, max_concurrent=50):
"""Solve CAPTCHAs in parallel with concurrency control."""
semaphore = asyncio.Semaphore(max_concurrent)
connector = aiohttp.TCPConnector(limit=max_concurrent)
async with aiohttp.ClientSession(connector=connector) as session:
coros = [
solve_one(session, t["sitekey"], t["pageurl"], semaphore)
for t in tasks
]
return await asyncio.gather(*coros)
# 10 tasks in parallel → ~20 seconds total
import time
tasks = [{"sitekey": "SITEKEY", "pageurl": "https://example.com"}] * 10
start = time.time()
results = asyncio.run(solve_parallel(tasks))
print(f"Completed in {time.time() - start:.0f}s")
La même logique en Node.js
Un agent HTTPS keepAlive, avec un maxSockets aligné sur la concurrence, évite de rouvrir une connexion TLS à chaque requête.
// parallel_solver.js
const axios = require('axios');
const https = require('https');
const API_KEY = process.env.CAPTCHAAI_KEY || 'YOUR_API_KEY';
const agent = new https.Agent({ keepAlive: true, maxSockets: 50 });
const api = axios.create({ baseURL: 'https://ocr.captchaai.com', httpsAgent: agent });
async function solveOne(sitekey, pageurl) {
const submit = await api.get('/in.php', {
params: { key: API_KEY, method: 'userrecaptcha', googlekey: sitekey, pageurl, json: '1' },
});
if (submit.data.status !== 1) return { error: submit.data.request };
await new Promise(r => setTimeout(r, 15000));
for (let i = 0; i < 25; i++) {
const poll = await api.get('/res.php', {
params: { key: API_KEY, action: 'get', id: submit.data.request, json: '1' },
});
if (poll.data.status === 1) return { token: poll.data.request };
if (poll.data.request !== 'CAPCHA_NOT_READY') return { error: poll.data.request };
await new Promise(r => setTimeout(r, 5000));
}
return { error: 'timeout' };
}
(async () => {
const tasks = Array.from({ length: 10 }, () => ({
sitekey: 'SITEKEY', pageurl: 'https://example.com',
}));
const start = Date.now();
const results = await Promise.all(tasks.map(t => solveOne(t.sitekey, t.pageurl)));
console.log(`Completed in ${((Date.now() - start) / 1000).toFixed(0)}s`);
console.log(`Solved: ${results.filter(r => r.token).length}/${tasks.length}`);
agent.destroy();
})();
Débit estimé selon le niveau de concurrence
| Résolutions simultanées | Débit estimé/heure | Mémoire (Python) | Complexité |
|---|---|---|---|
| 1 (séquentiel) | 240 | 30 Mo | Faible |
| 10 | 2 400 | 50 Mo | Faible |
| 25 | 6 000 | 80 Mo | Moyenne |
| 50 | 10 000+ | 120 Mo | Moyenne |
| 100 | 18 000+ | 200 Mo | Élevée |
Mêmes réserves que plus haut. Base de calcul : reCAPTCHA v2, médiane de 15 s. Sur Cloudflare Turnstile, résolu en moins de 10 s, ces niveaux donnent un débit supérieur.
Approche hybride : séquentiel pour le parcours, parallèle pour la résolution
Le compromis qui convient à la plupart des pipelines de scraping : le parcours reste séquentiel, seul le lot de CAPTCHA passe en parallèle.
# Process 10 URLs sequentially, but solve their CAPTCHAs in a parallel batch
urls = get_next_batch() # 10 URLs
captcha_params = [extract_sitekey(url) for url in urls] # Sequential extraction
tokens = asyncio.run(solve_parallel(captcha_params, max_concurrent=10)) # Parallel solving
for url, result in zip(urls, tokens):
submit_form(url, result.get("token")) # Sequential submission
Scénario : la recette nocturne d'un site e-commerce
Une équipe QA belge rejoue chaque nuit 1 200 parcours de checkout en préproduction, depuis un worker OVHcloud. En séquentiel, à 15 s de résolution médiane, le lot demande cinq heures : la recette n'est pas finie à l'ouverture des bureaux. Avec un sémaphore réglé sur 50 threads, il passe sous les quinze minutes.
Deux réflexes accompagnent ce passage à l'échelle. La latence : un worker en région Paris (eu-west-3) économise des allers-retours à chaque interrogation. La journalisation : conservez les identifiants de tâche et les codes d'erreur, pas les données saisies — vos obligations RGPD valent aussi en préproduction.
Migrer du séquentiel au parallèle en trois étapes
- Chronométrez un lot séquentiel de dix tâches.
- Extrayez la résolution dans une fonction autonome, bornée par un sémaphore réglé sur vos threads.
- Remesurez, puis remontez la concurrence par paliers tant que le taux d'erreur reste stable.
Dépannage
| Problème | Cause probable | Correctif |
|---|---|---|
| Le parallèle n'accélère presque rien | Sémaphore ou threads trop bas | Alignez max_concurrent sur vos threads |
| Échecs aléatoires sous forte concurrence | État partagé entre coroutines | Isolez l'état par tâche ; ne partagez que le client HTTP |
| Résultats mélangés | Agrégation par ordre d'arrivée | asyncio.gather préserve l'ordre : gardez son résultat tel quel |
ERROR_NO_SLOT_AVAILABLE |
Plus de soumissions que de threads | Réduisez la concurrence, espacez les soumissions de quelques centaines de ms |
| Timeouts au-delà de 100 tâches | Pool de connexions saturé | Augmentez limit du TCPConnector / maxSockets, ou découpez en lots |
FAQ
Combien de threads faut-il pour 5 000 résolutions par heure ?
À 15 s par reCAPTCHA v2, un thread produit environ 240 résolutions/heure : il en faut une vingtaine, soit l'offre ADVANCE ($90/mois, 50 threads), marge de nouvelles tentatives comprise.
Que se passe-t-il si ma concurrence dépasse les threads de mon offre ?
Elles sont refusées, typiquement avec ERROR_NO_SLOT_AVAILABLE. Rien n'est perdu, mais vous accumulez des tentatives inutiles : bornez la concurrence côté client.
La résolution parallèle coûte-t-elle plus cher ?
Non. La facturation se fait au thread, résolutions illimitées par thread : le parallélisme change la durée du lot, pas le montant de l'abonnement.
Le taux de réussite baisse-t-il quand on augmente la concurrence ?
Le taux de réussite dépend du type de CAPTCHA et des paramètres envoyés, pas du nombre de tâches en vol. Une concurrence mal bornée produit en revanche des timeouts côté client, qui ressemblent à des échecs : mesurez les deux séparément.
Prochaines étapes
Mesurez d'abord, parallélisez ensuite. Récupérez votre clé API CaptchaAI pour ce premier lot.
Guides associés :