API Tutorials

Explication du paramètre reCAPTCHA Data-S

Votre requête part avec le bon sitekey, la bonne pageurl, et Google refuse quand même le token : sur une page appartenant à Google, la cause est presque toujours un attribut data-s laissé de côté. En bref :

  • data-s ne concerne qu'une poignée d'implémentations reCAPTCHA v2, presque toutes chez Google.
  • Attribut présent : vous l'extrayez et vous le transmettez tel quel à l'API.
  • Attribut absent : vous n'ajoutez rien, au risque de voir la requête rejetée.

Sur quelles pages data-s apparaît réellement

Page ou service Fréquence À retenir
Google Search (page « trafic inhabituel », /sorry) Systématique data-s obligatoire pour toute résolution
YouTube Parfois Parcours de vérification de compte
Google Play Parfois Vérification de fiches d'applications
Google Forms Rarement Implémentations isolées
Sites tiers en reCAPTCHA v2 Quasiment jamais Intégrations standard sans data-s

Traduction pratique : si votre cible n'est pas un service Google, le paramètre ne vous concerne pas. Et si vos tokens sont systématiquement refusés sur une page /sorry, vérifiez data-s avant de soupçonner le proxy.


Ce que contient l'attribut data-s

data-s est une valeur générée côté serveur et injectée dans le HTML du widget reCAPTCHA. Elle rattache le défi CAPTCHA à une session précise : contrairement à la clé de site, identique pour tous les visiteurs, le token obtenu n'est pas rejouable ailleurs.

Où le repérer dans le HTML

<!-- reCAPTCHA widget with data-s parameter -->
<div class="g-recaptcha"
     data-sitekey="6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp"
     data-s="AB2grfE8_kyMp3XYRuJo5c..."
     data-callback="onCaptchaSolved">
</div>

L'attribut se place à côté du data-sitekey habituel, sur le <div> du widget.

Les propriétés à retenir

Propriété Valeur
Format Chaîne base64 de 200 à 500 caractères
Durée de vie Usage unique, liée au chargement de page en cours
Portée Session courante, jamais réutilisable d'une page à l'autre
Obligatoire Oui dès qu'il est présent : sans lui, la résolution échoue
Renouvellement Nouvelle valeur à chaque chargement ou rafraîchissement

Deux conséquences pour votre code :

  • La valeur ne se met pas en cache et ne se partage pas entre workers.
  • Elle se transmet brute, sans encodage supplémentaire.

Pourquoi Google lie le défi à une session

User triggers CAPTCHA (e.g., Google flags unusual search traffic)
    ↓
Google serves a CAPTCHA page with:

  - data-sitekey (site key, same for all Google search CAPTCHAs)
  - data-s (session token, unique per page load)
    ↓
reCAPTCHA widget initializes with both parameters
    ↓
Challenge completion generates a g-recaptcha-response token
    ↓
Token is submitted alongside the session reference
    ↓
Google validates token + session binding
    ↓
If data-s was not used during solving: "invalid-input-response" or silent failure

data-s joue le rôle d'un nonce : il associe le défi à un état serveur donné. La valeur doit être connue au moment où le token est produit, pas seulement à la soumission du formulaire. D'où les échecs silencieux :

  • Le token est bien formé, mais il pointe vers une session que Google n'attend pas.
  • Aucune erreur explicite ne remonte : la page redemande simplement une vérification.

Extraire data-s avant chaque résolution

L'ordre ne change jamais : charger la page, lire le widget, envoyer aussitôt.

Extraction en Python

import requests
from bs4 import BeautifulSoup
import re

def extract_recaptcha_params(url):
    """Extract reCAPTCHA parameters including data-s from a page."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 (KHTML, like Gecko) "
                      "Chrome/120.0.0.0 Safari/537.36",
    }

    response = requests.get(url, headers=headers, timeout=15)
    soup = BeautifulSoup(response.text, "html.parser")

    # Find reCAPTCHA widget div
    widget = soup.find("div", class_="g-recaptcha")
    if not widget:
        # Try finding by data-sitekey attribute
        widget = soup.find(attrs={"data-sitekey": True})

    if not widget:
        return {"error": "No reCAPTCHA widget found"}

    params = {
        "sitekey": widget.get("data-sitekey"),
        "data_s": widget.get("data-s"),
        "callback": widget.get("data-callback"),
        "size": widget.get("data-size"),
        "has_data_s": widget.get("data-s") is not None,
    }

    return params

# Example: Google "unusual traffic" page
params = extract_recaptcha_params("https://www.google.com/sorry/index")
print(params)
# {
#   "sitekey": "6LfwuyUT...",
#   "data_s": "AB2grfE8_kyMp3...",
#   "has_data_s": True
# }

Extraction en Node.js

const axios = require("axios");
const cheerio = require("cheerio");

async function extractRecaptchaParams(url) {
    const { data: html } = await axios.get(url, {
        headers: {
            "User-Agent":
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
                "AppleWebKit/537.36 (KHTML, like Gecko) " +
                "Chrome/120.0.0.0 Safari/537.36",
        },
        timeout: 15000,
    });

    const $ = cheerio.load(html);
    const widget = $(".g-recaptcha, [data-sitekey]").first();

    if (widget.length === 0) {
        return { error: "No reCAPTCHA widget found" };
    }

    return {
        sitekey: widget.attr("data-sitekey"),
        dataS: widget.attr("data-s") || null,
        callback: widget.attr("data-callback") || null,
        hasDataS: !!widget.attr("data-s"),
    };
}

extractRecaptchaParams("https://www.google.com/sorry/index")
    .then(console.log);

Extraction avec Selenium

Sur les pages rendues côté client, le widget est absent du HTML initial : laissez le navigateur exécuter le script avant de lire l'attribut.

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

def extract_data_s_selenium(driver, url):
    """Extract data-s from a dynamically loaded reCAPTCHA page."""
    driver.get(url)

    # Wait for reCAPTCHA widget to load
    import time
    time.sleep(3)

    try:
        widget = driver.find_element(By.CSS_SELECTOR, ".g-recaptcha, [data-sitekey]")
        return {
            "sitekey": widget.get_attribute("data-sitekey"),
            "data_s": widget.get_attribute("data-s"),
        }
    except Exception:
        return {"error": "Widget not found"}

Quel que soit l'outil :

  • Lisez l'attribut sur l'élément qui porte data-sitekey, pas ailleurs dans le DOM.
  • Envoyez la tâche dans la foulée, sans file intermédiaire.

Transmettre data-s à l'API CaptchaAI

Le champ s'ajoute à in.php sous le nom data-s, à côté de googlekey et pageurl. Le reste du workflow ne bouge pas : envoi, polling sur res.php, récupération du g-recaptcha-response, injection dans le formulaire.

Python

import requests
import time

API_KEY = "YOUR_API_KEY"

# Step 1: Extract parameters from the CAPTCHA page
sitekey = "6LfwuyUTAAAAAOAmoS0fdqijC2PbbdH4kjq62Y1b"
data_s = "AB2grfE8_kyMp3XYRuJo5c..."  # Extracted from data-s attribute
page_url = "https://www.google.com/sorry/index?continue=..."

# Step 2: Submit to CaptchaAI WITH data-s
submit = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url,
    "data-s": data_s,  # Include data-s parameter
    "json": 1,
})

task_id = submit.json()["request"]

# Step 3: Poll for result
for _ in range(60):
    time.sleep(5)
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY,
        "action": "get",
        "id": task_id,
        "json": 1,
    }).json()

    if result.get("status") == 1:
        token = result["request"]
        print(f"Token: {token[:60]}...")
        # Submit this token to the Google CAPTCHA form
        break

Node.js

const axios = require("axios");

async function solveWithDataS(sitekey, dataS, pageUrl) {
    const API_KEY = "YOUR_API_KEY";

    // Submit with data-s
    const { data: submit } = await axios.post(
        "https://ocr.captchaai.com/in.php",
        new URLSearchParams({
            key: API_KEY,
            method: "userrecaptcha",
            googlekey: sitekey,
            pageurl: pageUrl,
            "data-s": dataS,
            json: 1,
        })
    );

    const taskId = submit.request;

    // Poll
    for (let i = 0; i < 60; i++) {
        await new Promise((r) => setTimeout(r, 5000));
        const { data: result } = await axios.get(
            "https://ocr.captchaai.com/res.php",
            {
                params: {
                    key: API_KEY,
                    action: "get",
                    id: taskId,
                    json: 1,
                },
            }
        );

        if (result.status === 1) {
            return result.request;
        }
    }

    throw new Error("Timeout");
}

Prévoyez un timeout franc : interroger res.php toutes les 5 secondes est une base saine, mais au-delà de quelques minutes, rechargez la page et repartez d'un data-s neuf.


Les erreurs qui font échouer la résolution

Problème Symptôme observé Correctif
data-s omis alors qu'il est présent Token accepté par l'API, refusé par la page Testez l'attribut avant chaque envoi
data-s envoyé alors qu'il est absent Paramètre ignoré ou requête rejetée N'ajoutez le champ que si le widget le porte
Même valeur réutilisée sur plusieurs chargements invalid-input-response Extrayez une valeur neuve à chaque chargement
Valeur ré-encodée en URL Paramètre malformé Transmettez la chaîne base64 brute
Valeur extraite plusieurs minutes avant l'envoi Décalage token/session Enchaînez extraction et envoi

Journalisez la présence de l'attribut à chaque tentative : c'est ce qui distingue un échec de résolution d'une erreur de payload.


Un extracteur réutilisable

Isolez la logique conditionnelle dans une classe qui construit le payload et n'ajoute data-s que si la page le fournit.

import requests
from bs4 import BeautifulSoup

class RecaptchaExtractor:
    """Extract reCAPTCHA parameters from any page."""

    def __init__(self, url, session=None):
        self.url = url
        self.session = session or requests.Session()
        self.params = None

    def extract(self):
        """Extract sitekey, data-s, and other parameters."""
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                          "AppleWebKit/537.36 (KHTML, like Gecko) "
                          "Chrome/120.0.0.0 Safari/537.36",
        }

        response = self.session.get(self.url, headers=headers, timeout=15)
        soup = BeautifulSoup(response.text, "html.parser")

        widget = soup.find(attrs={"data-sitekey": True})
        if not widget:
            raise ValueError("No reCAPTCHA widget found on page")

        self.params = {
            "sitekey": widget["data-sitekey"],
            "pageurl": self.url,
        }

        # Include data-s only if present
        data_s = widget.get("data-s")
        if data_s:
            self.params["data-s"] = data_s

        return self.params

    def build_solver_payload(self, api_key):
        """Build CaptchaAI submission payload with correct parameters."""
        if not self.params:
            self.extract()

        payload = {
            "key": api_key,
            "method": "userrecaptcha",
            "googlekey": self.params["sitekey"],
            "pageurl": self.params["pageurl"],
            "json": 1,
        }

        # Only include data-s when it exists
        if "data-s" in self.params:
            payload["data-s"] = self.params["data-s"]

        return payload


# Usage
extractor = RecaptchaExtractor("https://www.google.com/sorry/index?continue=...")
payload = extractor.build_solver_payload("YOUR_API_KEY")
# payload includes data-s only when present on the page

Cas concret : une veille SEO hébergée en Europe

Une équipe de veille éditoriale à Lyon ou à Bruxelles fait tourner son crawler sur OVHcloud ou sur AWS eu-west-3 (Paris). Les plages datacenter sont vite repérées : après quelques centaines de requêtes, Google Search renvoie la page « trafic inhabituel », widget porteur de data-s. Le script échoue alors sans message clair, faute d'envoyer data-s.

Deux réflexes pour ce type de déploiement :

  • Dimensionnement : une file de contrôle séquentielle tient sur le plan BASIC ($15/mois, 5 threads) ; une collecte parallélisée sur plusieurs marchés francophones s'appuie plutôt sur ADVANCE ($90/mois, 50 threads), la facturation restant en dollars US.
  • RGPD : ne conservez dans vos logs que l'URL, la clé de site et la présence ou non de data-s, jamais le HTML complet des pages capturées.

Ce dernier point évite de stocker des données personnelles issues des pages de résultats.


Questions fréquentes

Comment savoir rapidement si une page utilise data-s ?

Cherchez data-s= dans le HTML brut, ou lisez l'attribut sur l'élément qui porte data-sitekey. Sur une page rendue côté client, vérifiez après exécution du script.

Le paramètre data-s existe-t-il aussi en reCAPTCHA v3 ?

Non. reCAPTCHA v3 s'exécute sans widget interactif : la page fournit une clé de site et une action, et le service renvoie un score. data-s appartient au widget v2.

Faut-il extraire une nouvelle valeur avant chaque résolution ?

Oui. La valeur est liée à un chargement unique : la mettre en cache produit des tokens refusés. Rechargez, relisez l'attribut, envoyez.

CaptchaAI prend-il en charge hCaptcha sur ce type de page ?

Non, hCaptcha n'est pas pris en charge, pas plus que FunCaptcha (Arkose Labs) ; GeeTest v4 est à venir. Les types couverts : reCAPTCHA v2 et v3, Cloudflare Turnstile, Cloudflare Challenge, GeeTest v3, CAPTCHA image/OCR et grilles d'images, plus CaptchaFox (bêta), Friendly Captcha (bêta) et Lemin (bêta).


À retenir

data-s est un token de liaison de session, presque toujours servi par Google. La règle est binaire, et trois lignes conditionnelles dans votre extracteur couvrent les deux cas :

  • Attribut présent : extrayez-le et joignez-le à votre requête API CaptchaAI.
  • Attribut absent : envoyez googlekey et pageurl, rien de plus.

Articles connexes

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