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-sne 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
googlekeyetpageurl, rien de plus.