Tutorials

Gestion de plusieurs CAPTCHA sur une seule page

La règle tient en une phrase : traitez chaque CAPTCHA de la page comme une tâche indépendante. Chaque widget possède sa propre clé de site, son propre textarea g-recaptcha-response et son propre callback ; les confondre est la première cause de formulaires qui restent bloqués même après l'injection d'un token valide.

Le cas est courant. On le rencontre notamment sur :

  • une page de connexion sous reCAPTCHA qui affiche aussi une inscription newsletter protégée par un second reCAPTCHA ;
  • un tunnel de paiement en plusieurs étapes où chaque étape déclenche son propre défi.

La démarche ne change pas, quel que soit le nombre de widgets. Le reste de ce guide suit ces trois étapes :

  1. Détecter tous les widgets de la page et lire leurs clés de site.
  2. Résoudre les tokens en parallèle via l'API CaptchaAI.
  3. Réinjecter chaque token dans le bon widget, puis déclencher son callback.

Étape 1 : détecter tous les widgets de la page

Recensez tous les widgets avant de résoudre quoi que ce soit. Deux approches se complètent :

  • Parcourir les iframes reCAPTCHA (iframe[src*="recaptcha/api2/anchor"]) et lire la clé dans le paramètre k=.
  • Lister les conteneurs .g-recaptcha et lire leur attribut data-sitekey.

Attendez toujours le rendu complet de la page (WebDriverWait, page.waitForSelector) avant de scanner : un widget chargé en lazy loading passerait inaperçu.

Détection en Python avec Selenium

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://example.com/multi-captcha-page")

# Find all reCAPTCHA iframes
captcha_iframes = driver.find_elements(
    By.CSS_SELECTOR, 'iframe[src*="recaptcha/api2/anchor"]'
)

# Extract sitekeys from each iframe's src
import re
captchas = []
for i, iframe in enumerate(captcha_iframes):
    src = iframe.get_attribute("src")
    match = re.search(r"k=([A-Za-z0-9_-]+)", src)
    if match:
        captchas.append({
            "index": i,
            "sitekey": match.group(1),
            "iframe": iframe
        })

print(f"Found {len(captchas)} CAPTCHAs on page")
for c in captchas:
    print(f"  [{c['index']}] sitekey: {c['sitekey']}")

Résultat attendu :

Found 2 CAPTCHAs on page
  [0] sitekey: 6LcXyzABCDEF-login
  [1] sitekey: 6LcAbcDEFGHI-signup

Détection en JavaScript avec Puppeteer

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();
  await page.goto('https://example.com/multi-captcha-page', {
    waitUntil: 'networkidle2'
  });

  // Extract all reCAPTCHA widgets from the page
  const captchas = await page.evaluate(() => {
    const widgets = document.querySelectorAll('.g-recaptcha');
    return Array.from(widgets).map((el, i) => ({
      index: i,
      sitekey: el.getAttribute('data-sitekey'),
      elementId: el.id || `captcha-${i}`,
      callbackName: el.getAttribute('data-callback') || null
    }));
  });

  console.log(`Found ${captchas.length} CAPTCHAs`);
  captchas.forEach(c => console.log(`  [${c.index}] ${c.sitekey}`));
})();

Étape 2 : résoudre les CAPTCHA en parallèle

Envoyez toutes les clés de site à CaptchaAI en une seule salve, puis interrogez l'API jusqu'à ce que chaque token revienne. Ne bloquez jamais sur un CAPTCHA avant de lancer le suivant : c'est le parallélisme qui rend l'opération viable.

Les durées évoquées ici 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.

Prenons un tunnel d'inscription francophone : l'écran de création de compte impose un reCAPTCHA, puis l'écran de consentement RGPD qui suit en ajoute un second. La capacité se raisonne en threads :

  • Chaque CAPTCHA en cours de résolution occupe un thread ; il se libère dès que le token revient.
  • Cinq widgets en parallèle mobilisent donc cinq threads simultanés.
  • Le plan BASIC ($15/mois, 5 threads) couvre déjà ce cas ; STANDARD ($30/mois, 15 threads) laisse de la marge.
  • La facturation dépend du nombre de threads, jamais du nombre de résolutions.

Python

import requests
import time
from concurrent.futures import ThreadPoolExecutor, as_completed

API_KEY = "YOUR_API_KEY"
PAGE_URL = "https://example.com/multi-captcha-page"

def submit_captcha(sitekey):
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": PAGE_URL,
        "json": "1"
    })
    result = resp.json()
    if result["status"] != 1:
        raise Exception(f"Submit error: {result['request']}")
    return result["request"]

def poll_result(task_id, timeout=120):
    deadline = time.time() + timeout
    while time.time() < deadline:
        time.sleep(5)
        resp = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY,
            "action": "get",
            "id": task_id,
            "json": "1"
        })
        result = resp.json()
        if result["status"] == 1:
            return result["request"]
        if result["request"] != "CAPCHA_NOT_READY":
            raise Exception(f"Poll error: {result['request']}")
    raise TimeoutError(f"Task {task_id} timed out")

def solve_all(captchas):
    # Submit all in parallel
    task_ids = {}
    with ThreadPoolExecutor(max_workers=len(captchas)) as pool:
        futures = {
            pool.submit(submit_captcha, c["sitekey"]): c["index"]
            for c in captchas
        }
        for future in as_completed(futures):
            idx = futures[future]
            task_ids[idx] = future.result()
            print(f"[captcha-{idx}] Submitted → task {task_ids[idx]}")

    # Poll all in parallel
    tokens = {}
    with ThreadPoolExecutor(max_workers=len(task_ids)) as pool:
        futures = {
            pool.submit(poll_result, tid): idx
            for idx, tid in task_ids.items()
        }
        for future in as_completed(futures):
            idx = futures[future]
            tokens[idx] = future.result()
            print(f"[captcha-{idx}] Solved")

    return tokens

# Example usage
captchas = [
    {"index": 0, "sitekey": "6LcXyzABCDEF-login"},
    {"index": 1, "sitekey": "6LcAbcDEFGHI-signup"}
]

tokens = solve_all(captchas)

Résultat attendu :

[captcha-0] Submitted → task 71823456
[captcha-1] Submitted → task 71823457
[captcha-1] Solved
[captcha-0] Solved

Étape 3 : injecter chaque token dans le bon widget

Chaque widget reCAPTCHA gère son propre textarea g-recaptcha-response, imbriqué dans son conteneur. Trois règles évitent les erreurs les plus fréquentes :

  • Visez le textarea du bon conteneur, jamais le premier venu de la page.
  • Associez chaque token à une clé de site, pas à une position d'index.
  • Déclenchez le callback (data-callback) après avoir écrit le token, sinon le formulaire reste bloqué.

Injection en Python (Selenium)

def inject_tokens(driver, captchas, tokens):
    for c in captchas:
        idx = c["index"]
        token = tokens[idx]

        # Find the textarea within the widget's container
        container = driver.find_elements(By.CSS_SELECTOR, ".g-recaptcha")[idx]
        textarea = container.find_element(
            By.CSS_SELECTOR, 'textarea[name="g-recaptcha-response"]'
        )
        driver.execute_script(
            "arguments[0].value = arguments[1];", textarea, token
        )

        # Trigger the callback if defined
        callback = c.get("callback")
        if callback:
            driver.execute_script(f"{callback}('{token}');")

        print(f"[captcha-{idx}] Token injected")

inject_tokens(driver, captchas, tokens)

Injection en JavaScript (Puppeteer)

async function injectTokens(page, captchas, tokens) {
  for (const captcha of captchas) {
    const token = tokens[captcha.index];

    await page.evaluate((idx, tkn, callbackName) => {
      const widgets = document.querySelectorAll('.g-recaptcha');
      const textarea = widgets[idx].querySelector(
        'textarea[name="g-recaptcha-response"]'
      );
      textarea.value = tkn;

      if (callbackName && typeof window[callbackName] === 'function') {
        window[callbackName](tkn);
      }
    }, captcha.index, token, captcha.callbackName);

    console.log(`[captcha-${captcha.index}] Token injected`);
  }
}

await injectTokens(page, captchas, tokens);

Pages mixtes : reCAPTCHA et Turnstile ensemble

Quand une page combine plusieurs technologies — par exemple reCAPTCHA et Cloudflare Turnstile côte à côte — détectez chaque famille séparément avant de soumettre :

def detect_all_captchas(driver):
    detected = []

    # reCAPTCHA
    recaptchas = driver.find_elements(By.CSS_SELECTOR, ".g-recaptcha")
    for i, el in enumerate(recaptchas):
        detected.append({
            "type": "userrecaptcha",
            "sitekey": el.get_attribute("data-sitekey"),
            "label": f"recaptcha-{i}"
        })

    # Turnstile
    turnstiles = driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile")
    for i, el in enumerate(turnstiles):
        detected.append({
            "type": "turnstile",
            "sitekey": el.get_attribute("data-sitekey"),
            "label": f"turnstile-{i}"
        })

    return detected

Envoyez ensuite chaque tâche avec le paramètre method qui lui correspond :

  • reCAPTCHA → method=userrecaptcha, token réinjecté dans g-recaptcha-response.
  • Cloudflare Turnstile → method=turnstile, token réinjecté dans cf-turnstile-response.

Ne mélangez jamais les deux champs : chaque type de CAPTCHA possède son propre nom de token.

Bonnes pratiques pour les pages multi-CAPTCHA

  • Journalisez chaque widget (index, clé de site, callback) : c'est ce qui permet de tracer un échec précis.
  • Re-scannez la page après chaque soumission de formulaire ; un second défi peut n'apparaître qu'ensuite.
  • Minimisez les données personnelles manipulées dans ces formulaires, conformément à vos obligations RGPD.
  • Prévoyez un retry avec backoff exponentiel si un token expire avant l'envoi du formulaire.

Dépannage

Problème Cause Correctif
Token injecté mais formulaire toujours bloqué Callback jamais déclenché Lisez data-callback et appelez la fonction avec le token
Seul le premier CAPTCHA est détecté Le second widget se charge en lazy loading Attendez l'apparition de toutes les iframes avant de scanner la page
Token placé dans le mauvais widget Décalage d'index Associez chaque token à une clé de site, jamais à une position
ERROR_WRONG_GOOGLEKEY Clé de site mal extraite Relisez la clé depuis l'attribut src de l'iframe ou data-sitekey

FAQ

Combien de threads faut-il pour résoudre plusieurs CAPTCHA en parallèle ?

Un par CAPTCHA en vol. Trois widgets résolus simultanément occupent trois threads le temps de la résolution ; dès qu'un token revient, le thread se libère. Le plan BASIC ($15/mois, 5 threads) suffit pour la plupart des pages multi-CAPTCHA.

CaptchaAI peut-il gérer une page mêlant reCAPTCHA et Cloudflare Turnstile ?

Oui. Détectez chaque famille avec son sélecteur, puis soumettez chaque tâche avec le bon paramètre method :

  • .g-recaptchauserrecaptcha
  • .cf-turnstileturnstile

Les deux types se résolvent dans la même salve parallèle.

Pourquoi mon formulaire reste-t-il bloqué après l'injection du token ?

Le plus souvent, le callback n'a pas été déclenché. Écrire le token dans le textarea g-recaptcha-response ne suffit pas si la page attend un data-callback : récupérez le nom de la fonction et appelez-la explicitement avec le token.

Faut-il résoudre les CAPTCHA un par un ou simultanément ?

Simultanément. Chaque résolution prend de l'ordre de 15 à 30 s ; en série, les délais s'additionnent, alors qu'en parallèle le temps total reste proche de celui d'un seul défi.

Résolvez autant de CAPTCHA que nécessaire par page

Créez votre compte et récupérez votre clé API sur captchaai.com, puis lancez votre première salve parallèle.

Guides associés

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