Tutorials

Extraction des paramètres reCAPTCHA de la source de la page

Un solveur reCAPTCHA ne devine rien : il rejoue exactement les paramètres présents dans la page cible. Avant le moindre appel à l'API CaptchaAI, vous devez donc lire la source de la page et en extraire au minimum deux valeurs, le sitekey et l'URL de la page. Selon la version rencontrée, il faut parfois aussi capter l'action (reCAPTCHA v3), le champ data-s (sites Google) ou l'indicateur Enterprise. Ce guide passe en revue chaque emplacement où ces valeurs se cachent, avec du code Python directement réutilisable.

L'enjeu est concret : un sitekey erroné ou une action manquante, et la résolution échoue silencieusement côté API.

Quelle méthode d'extraction choisir ?

Le bon point d'entrée dépend de la manière dont la page charge son widget :

  1. Widget visible dans le HTML : lisez l'attribut data-sitekey (méthode 1).
  2. reCAPTCHA v3 ou Enterprise : la clé est dans l'URL du script render= (méthode 2).
  3. Widget dans une iframe : la clé voyage dans le paramètre k de l'iframe (méthode 3).
  4. Widget injecté par grecaptcha.render() : lisez l'objet de configuration JavaScript (méthode 4).

Les paramètres à extraire selon la version

Ce tableau récapitule ce qui est obligatoire, optionnel ou inutile selon la variante :

Paramètre v2 standard v2 invisible v3 Enterprise
googlekey (sitekey) Obligatoire Obligatoire Obligatoire Obligatoire
pageurl Obligatoire Obligatoire Obligatoire Obligatoire
invisible - 1 - -
action - - Obligatoire Parfois
data-s Parfois Parfois - -
enterprise - - - 1

sitekey et pageurl sont toujours requis ; le reste dépend de ce que vous détectez.

Méthode 1 : lire les attributs HTML du widget

Dans la majorité des intégrations reCAPTCHA v2, le sitekey est posé en clair sur l'élément .g-recaptcha via l'attribut data-sitekey. Une requête HTTP et une regex suffisent, sans navigateur.

Depuis l'attribut data-sitekey

Ce script récupère le HTML brut, puis extrait le sitekey, le mode invisible, le callback éventuel et le champ data-s propre aux sites Google.

import re
import requests

url = "https://example.com/login"
html = requests.get(url).text

# Find data-sitekey
match = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]+)["\']', html)
if match:
    sitekey = match.group(1)
    print(f"Sitekey: {sitekey}")

# Check if invisible
invisible_match = re.search(r'data-size=["\']invisible["\']', html)
is_invisible = bool(invisible_match)
print(f"Invisible: {is_invisible}")

# Find callback
callback_match = re.search(r'data-callback=["\'](\w+)["\']', html)
callback = callback_match.group(1) if callback_match else None
print(f"Callback: {callback}")

# Check for data-s (Google-owned sites)
data_s_match = re.search(r'data-s=["\']([^"\']+)["\']', html)
data_s = data_s_match.group(1) if data_s_match else None
print(f"data-s: {data_s}")

Avec Puppeteer quand le widget est injecté par JavaScript

Si le .g-recaptcha n'apparaît pas dans le HTML statique, c'est qu'il est ajouté au DOM après le chargement. Il faut alors un navigateur headless comme Puppeteer, laisser le réseau se stabiliser (networkidle2), puis lire les attributs directement sur l'élément rendu.

const puppeteer = require('puppeteer');

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/login', { waitUntil: 'networkidle2' });

const params = await page.evaluate(() => {
  const widget = document.querySelector('.g-recaptcha');
  if (!widget) return null;

  return {
    sitekey: widget.getAttribute('data-sitekey'),
    size: widget.getAttribute('data-size'),
    callback: widget.getAttribute('data-callback'),
    dataS: widget.getAttribute('data-s'),
    invisible: widget.getAttribute('data-size') === 'invisible',
  };
});

console.log(params);

Méthode 2 : extraire depuis les balises de script

reCAPTCHA v3 et Enterprise ne posent pas de data-sitekey visible : la clé voyage dans l'URL du script api.js ou enterprise.js, via le paramètre render.

sitekeys reCAPTCHA v3 et Enterprise

La présence de enterprise.js est le signal le plus fiable pour distinguer une intégration Enterprise d'une v3 classique.

# Find sitekey from script src
v3_match = re.search(
    r'recaptcha/(?:api|enterprise)\.js\?.*?render=([A-Za-z0-9_-]+)',
    html
)
if v3_match:
    sitekey = v3_match.group(1)
    print(f"v3 Sitekey: {sitekey}")

# Check enterprise
is_enterprise = 'enterprise.js' in html
print(f"Enterprise: {is_enterprise}")

Retrouver le paramètre action

En v3, l'action est déclarée dans le JavaScript, au moment de l'appel grecaptcha.execute, et non dans un attribut HTML. Sans elle, le score obtenu sera rejeté par le site. Ciblez donc l'appel execute.

# Search for grecaptcha.execute calls
action_match = re.search(
    r'grecaptcha\.execute\s*\([^,]+,\s*\{[^}]*action\s*:\s*["\']([^"\']+)',
    html
)
if action_match:
    action = action_match.group(1)
    print(f"Action: {action}")

Méthode 3 : extraire depuis l'attribut src de l'iframe

Quand reCAPTCHA est rendu à l'intérieur d'une iframe, la clé se retrouve dans l'URL de l'iframe, transportée par le paramètre k. Repérez l'iframe recaptcha/api2/anchor, puis isolez la valeur de k.

# Find reCAPTCHA iframe
iframe_match = re.search(
    r'<iframe[^>]+src=["\']([^"\']*recaptcha/api2/anchor[^"\']*)["\']',
    html
)
if iframe_match:
    iframe_src = iframe_match.group(1)
    sitekey_match = re.search(r'k=([A-Za-z0-9_-]+)', iframe_src)
    if sitekey_match:
        sitekey = sitekey_match.group(1)
        print(f"Iframe sitekey: {sitekey}")

Méthode 4 : extraire du rendu JavaScript dynamique

Certaines pages appellent directement grecaptcha.render() avec un objet de configuration. Le sitekey, le callback et la size s'y trouvent sous forme de propriétés JavaScript.

# Find grecaptcha.render calls
render_match = re.search(
    r'grecaptcha\.render\s*\([^,]*,\s*\{([^}]+)\}',
    html
)
if render_match:
    config = render_match.group(1)
    sk = re.search(r'sitekey\s*:\s*["\']([A-Za-z0-9_-]+)', config)
    cb = re.search(r'callback\s*:\s*["\']?(\w+)', config)
    sz = re.search(r'size\s*:\s*["\'](\w+)', config)
    print(f"Sitekey: {sk.group(1) if sk else 'not found'}")
    print(f"Callback: {cb.group(1) if cb else 'not found'}")
    print(f"Size: {sz.group(1) if sz else 'not found'}")

Une fonction d'extraction complète

Plutôt que de choisir la méthode à la main, cette fonction les enchaîne dans l'ordre : data-sitekey, puis render (v3), l'iframe, et enfin grecaptcha.render. Elle s'arrête dès qu'une clé est trouvée et complète les indicateurs invisible, enterprise, action, data-s et le callback.

import re
import requests

def extract_recaptcha_params(url):
    html = requests.get(url, timeout=15).text
    params = {"pageurl": url}

    # Sitekey from data-sitekey
    sk = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]+)', html)
    if sk:
        params["sitekey"] = sk.group(1)

    # Sitekey from script render parameter (v3)
    if "sitekey" not in params:
        v3 = re.search(r'render=([A-Za-z0-9_-]{20,})', html)
        if v3:
            params["sitekey"] = v3.group(1)

    # Sitekey from iframe
    if "sitekey" not in params:
        iframe = re.search(r'recaptcha.*?k=([A-Za-z0-9_-]+)', html)
        if iframe:
            params["sitekey"] = iframe.group(1)

    # Sitekey from grecaptcha.render
    if "sitekey" not in params:
        render = re.search(r'sitekey\s*:\s*["\']([A-Za-z0-9_-]+)', html)
        if render:
            params["sitekey"] = render.group(1)

    # Version detection
    if re.search(r'data-size=["\']invisible', html):
        params["invisible"] = True
    if 'enterprise.js' in html:
        params["enterprise"] = True

    # Action (v3)
    action = re.search(
        r'action\s*:\s*["\']([^"\']+)',
        html[html.find('grecaptcha.execute'):] if 'grecaptcha.execute' in html else ''
    )
    if action:
        params["action"] = action.group(1)

    # data-s
    ds = re.search(r'data-s=["\']([^"\']+)', html)
    if ds:
        params["data_s"] = ds.group(1)

    # Callback
    cb = re.search(r'data-callback=["\'](\w+)', html)
    if cb:
        params["callback"] = cb.group(1)

    return params

# Usage
params = extract_recaptcha_params("https://example.com/login")
for k, v in params.items():
    print(f"  {k}: {v}")

Sur une page v2 invisible avec callback, la sortie ressemble à ceci :

  pageurl: https://example.com/login
  sitekey: 6Le-SITEKEY-abc123
  invisible: True
  callback: onCaptchaComplete

Envoyer les paramètres extraits à CaptchaAI

Une fois params rempli, l'envoi se résume à un POST sur in.php avec method=userrecaptcha. Les champs optionnels ne sont ajoutés que s'ils ont été détectés, ce qui évite un invisible ou un enterprise parasite.

data = {
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": params["sitekey"],
    "pageurl": params["pageurl"],
    "json": "1",
}

if params.get("invisible"):
    data["invisible"] = "1"
if params.get("enterprise"):
    data["enterprise"] = "1"
if params.get("action"):
    data["action"] = params["action"]
if params.get("data_s"):
    data["data-s"] = params["data_s"]

resp = requests.post("https://ocr.captchaai.com/in.php", data=data).json()

Le compte BASIC ($15/mois, 5 threads) suffit pour valider une extraction : la facturation se fait au thread concurrent, avec des résolutions illimitées, jamais à la pièce.

Exemple : auditer un formulaire de connexion en pré-production

Cas courant pour une équipe QA francophone : vous préparez des tests sur un formulaire de connexion protégé par reCAPTCHA, sur votre pré-production hébergée chez OVHcloud ou Scaleway. Lancez extract_recaptcha_params sur l'URL de staging et lisez le résultat :

  • un sitekey sans invisible : c'est une v2 standard ;
  • un enterprise: True avec une action : votre suite devra transmettre ces deux champs à chaque appel.

Profitez-en pour vérifier qu'aucune donnée personnelle inutile ne transite dans le formulaire : le sitekey est public, mais c'est le bon moment pour appliquer le principe de minimisation des données du RGPD sur vos propres pages.

Dépannage

Quand l'extraction ne rend pas la valeur attendue, la cause est presque toujours ici.

Problème Cause probable Correctif
Aucun sitekey trouvé La page génère le widget par JavaScript Passez sur Puppeteer ou Selenium plutôt que sur le HTML statique
Mauvais sitekey capté Plusieurs instances de reCAPTCHA sur la page Vérifiez quel widget correspond au formulaire que vous soumettez
Action introuvable Elle est définie dans un fichier JS externe Récupérez et analysez les fichiers JavaScript liés à la page
data-s change à chaque requête Google le régénère côté serveur Extrayez un data-s frais pour chaque résolution

FAQ

Comment identifier la version de reCAPTCHA présente sur une page ?

Un data-sitekey sur .g-recaptcha indique une v2, data-size="invisible" une v2 invisible, un paramètre render= dans l'URL du script une v3, et enterprise.js une intégration Enterprise. La fonction complète détecte ces cas automatiquement.

Pourquoi mon extraction ne trouve-t-elle aucun sitekey ?

Le plus souvent, le widget est injecté après le chargement par du JavaScript et n'existe pas dans le HTML renvoyé par requests.get. Rejouez l'extraction dans un navigateur headless (Puppeteer ou Selenium) qui exécute le script avant de lire le DOM.

Le sitekey doit-il rester secret comme la clé API ?

Non. Le sitekey est une clé publique attribuée au site, visible dans la source de la page : sa présence dans votre code ne pose aucun risque. Votre clé API CaptchaAI, elle, reste confidentielle et ne doit jamais figurer côté client.

À quelle fréquence le paramètre data-s change-t-il ?

Sur les sites Google, data-s est régénéré côté serveur et peut changer à chaque chargement. Ne le mettez jamais en cache : extrayez-le juste avant chaque envoi à l'API.

Passez des paramètres extraits à une résolution fiable

Récupérez votre clé API sur le site CaptchaAI et testez votre extraction de bout en bout.

Guides associés

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