Comparisons

Résolution de CAPTCHA parallèle ou séquentielle : compromis en matière de performances

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

  1. Chronométrez un lot séquentiel de dix tâches.
  2. Extrayez la résolution dans une fonction autonome, bornée par un sémaphore réglé sur vos threads.
  3. 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 :

Les commentaires sont désactivés pour cet article.