Si Google rechaza tus tokens en la página de "tráfico inusual", casi siempre falta el mismo atributo: data-s, un token de sesión que Google inserta en el widget de reCAPTCHA y que solo vale para esa carga de página. Si envías la tarea con sitekey y pageurl pero sin data-s, el token no coincide con la sesión que espera el servidor y la validación falla sin error útil.
Afecta a pocas páginas y detectarlo cuesta una línea de código. Veamos dónde aparece, cómo extraerlo y cómo pasarlo a la API de CaptchaAI.
¿Dónde aparece data-s y cuándo puedes ignorarlo?
En la mayoría de los sitios con reCAPTCHA, data-s no existe. Aparece casi en exclusiva en propiedades de Google:
| Sitio | ¿Aparece data-s? |
Notas |
|---|---|---|
Búsqueda de Google (páginas /sorry, tráfico inusual) |
Siempre | Sin él la resolución no valida |
| YouTube | A veces | En ciertos flujos de verificación |
| Google Play | A veces | Verificación en fichas de aplicaciones |
| Formularios de Google | Rara vez | Implementaciones puntuales |
| Sitios de terceros con reCAPTCHA | Casi nunca | Las integraciones estándar no lo usan |
Un caso típico: una agencia en Ciudad de México monitorea posiciones de búsqueda y Google empieza a servirle la página /sorry. Como el sitekey no cambia, el equipo lo deja fijo y recibe tokens que Google descarta: nadie leía data-s.
- Si tu objetivo es una propiedad de Google, da por hecho que
data-spuede estar y compruébalo en cada carga. - Si es un formulario de terceros (una tienda WordPress, por ejemplo), casi seguro no lo lleva.
- Trabaja sobre sitios que te corresponden y respeta la normativa de protección de datos aplicable (GDPR y LOPDGDD en España, LFPDPPP en México).
Qué es data-s y dónde vive dentro del widget
data-s lo genera el servidor y viaja en el HTML del widget. Ata el desafío a una sesión concreta: un token resuelto en una no sirve en otra.
Cómo se ve en el HTML
<!-- reCAPTCHA widget with data-s parameter -->
<div class="g-recaptcha"
data-sitekey="6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp"
data-s="AB2grfE8_kyMp3XYRuJo5c..."
data-callback="onCaptchaSolved">
</div>
Aparece junto al data-sitekey de siempre, dentro del <div> del widget. Si tu código solo lee data-sitekey, se lo salta sin avisar.
Propiedades del token data-s
| Propiedad | Valor |
|---|---|
| Formato | Cadena en Base64 de 200 a 500 caracteres |
| Vida útil | De un solo uso, ligada a la carga actual |
| Alcance | Específico de la sesión; no se reutiliza entre cargas |
| Obligatorio | Sí, cuando está presente: sin él la resolución falla |
| Renovación | Valor nuevo en cada carga o refresco |
Qué ocurre por dentro cuando falta data-s
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 funciona como un nonce del lado del servidor. Cuando falta, la secuencia es siempre la misma:
- El servicio de resolución genera el token sin conocer esa sesión.
- Recibes un
g-recaptcha-responsede aspecto válido. - Google lo descarta con
invalid-input-responseo en silencio, sin pista sobre la causa.
Errores frecuentes que rompen la resolución
Casi todos los fallos con data-s caen en una de estas cinco casillas:
| Error | Síntoma | Solución |
|---|---|---|
Omitir data-s cuando el widget lo trae |
El token se rechaza en silencio | Comprueba el atributo antes de enviar |
| Añadirlo cuando no existe | Error del solver o rechazo | Inclúyelo solo si está en el HTML |
| Reutilizarlo en varias cargas | Token no válido | Extrae un valor nuevo en cada carga |
| Codificar el valor en URL | Parámetro mal formado | Envía la cadena Base64 tal cual |
Usar un data-s de hace minutos |
No coincide con la sesión | Extrae y envía en la misma pasada |
Cómo extraer data-s de la página
Se reduce a leer un atributo más del widget; la herramienta depende de cómo se construya la página:
- HTML estático:
requestsy BeautifulSoup. - Node.js:
axiosy cheerio. - Widget inyectado por JavaScript: Selenium o Playwright.
Extracción con requests y BeautifulSoup
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
# }
Extracción con Node.js y cheerio
Mismo recorrido del DOM: primer widget que aparezca y data-s en null cuando no está.
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);
Extracción con Selenium cuando la página es dinámica
Si el widget se inyecta por JavaScript, requests no verá nada: carga la página en un navegador y lee el atributo del DOM renderizado.
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"}
Cómo enviar data-s a la API de CaptchaAI
Cuando existe, viaja en el mismo POST a in.php que ya haces para un reCAPTCHA v2 normal:
- Extrae
sitekeyydata-sde la página. - Envía la tarea con
method=userrecaptchay añadedata-sjunto agooglekeyypageurl. - Consulta el resultado en
res.phphasta recibir el token.
Ejemplo en 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
Ejemplo en 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");
}
Un extractor reutilizable para cualquier página
En lugar de bifurcar por sitio, encapsula la lógica en una clase que añada data-s solo cuando exista.
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
- El mismo objeto sirve para un formulario corriente y para una página
/sorry. - El payload lleva
data-ssolo si el widget lo traía en esa carga.
Preguntas frecuentes
¿Cómo compruebo en un minuto si una página necesita data-s?
Busca el <div class="g-recaptcha"> en las herramientas de desarrollo y mira si lleva el atributo data-s; desde la terminal, descarga el HTML y busca data-s=. Si no aparece, no lo envíes.
¿Es data-s lo mismo que el sitekey?
No, y confundirlos causa muchos fallos. El sitekey es público, estable e igual para todos; data-s cambia en cada carga y dejarlo fijo no funciona nunca.
¿Necesito un navegador headless para obtenerlo?
Depende. Si el widget viene en el HTML inicial, requests o axios bastan y consumen mucho menos. Si se inyecta por JavaScript, hace falta Selenium o Playwright.
¿Qué plan de CaptchaAI necesito para este tipo de trabajo?
Depende de la concurrencia, no del volumen: CaptchaAI factura por thread simultáneo, con resoluciones ilimitadas por thread. Para un script de monitorización, BASIC ($15/mes, 5 threads) sobra; con decenas de workers a la vez encaja mejor ADVANCE ($90/mes, 50 threads).
En resumen
data-s separa un token válido de uno que Google descarta en sus propias páginas de verificación. La regla operativa cabe en tres líneas:
- Lee el atributo antes de cada envío.
- Inclúyelo si existe y omítelo si no.
- No lo reutilices entre cargas: con esa comprobación en tu extractor, la misma integración con la API de CaptchaAI sirve para un formulario cualquiera y para un reCAPTCHA de la Búsqueda de Google.