API Tutorials

reCAPTCHA Data-S 参数解释

如果你的 reCAPTCHA 求解请求在大多数网站上都能正常拿到 token,唯独在 Google 自家产品——Google 搜索的“异常流量”页面、YouTube、Google Play——上频繁失败,问题往往不在 sitekey,而在于漏传了一个参数:data-s

data-s 只在少数场景下出现,但一旦出现就是必填项。先说结论:

  • 是什么:绑定单次页面加载的会话令牌,缺了它 token 校验会静默失败。
  • 谁会遇到:几乎只出现在 Google 自家产品上,第三方集成通常用不到。
  • 怎么办:确认组件上真实存在该属性时才提取并原样传给 CaptchaAI,没有就别硬传。

哪些场景会遇到 data-s

绝大多数 reCAPTCHA 集成用不到 data-s,它主要集中在 Google 自家资产上;对接的不是这类产品,大概率可以不用管——反过来,Google 搜索验证码总是求解失败,它就是第一嫌疑。

网站 / 场景 出现频率 说明
Google 搜索(“sorry”异常流量页) 总是 解析这类验证码必须携带
YouTube 有时 出现在部分身份验证流程中
Google Play 有时 应用列表相关的校验场景
Google Forms 较少 仅部分表单实现启用
使用 reCAPTCHA 的第三方网站 几乎不会 标准集成通常不涉及

出海团队多留意一点:reCAPTCHA 依赖 Google 托管脚本,从中国大陆网络访问不总是稳定——先排除网络链路问题,再排查 data-s,能少走弯路。

data-s 参数到底是什么

data-s 是服务器生成的会话令牌,直接写在 reCAPTCHA 组件的 HTML 里,把这次验证码质询和某个具体的服务器会话绑定在一起,防止同一个 token 被跨会话重复使用。

出现位置

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

data-s 属性和标准的 data-sitekey 一起,写在 reCAPTCHA 的 <div> 元素上。

关键属性

  • 格式:Base64 编码字符串,长度大约 200–500 个字符
  • 生命周期:一次性使用,只对应当前这一次页面加载
  • 作用域:会话级——无法跨页面加载复用
  • 是否必填:只要出现就必填;缺失会导致求解失败
  • 刷新规则:每次页面加载或刷新都会重新生成

校验流程

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 本质上是一个随机数(nonce),把验证码质询绑定到某个服务器端会话;求解器生成 token 时必须带上这个值,才能匹配 Google 期望的会话。

提取并提交 data-s

  1. 页面是静态渲染的,用 requests/BeautifulSoup(Python)或 axios/cheerio(Node.js)直接抓取。
  2. 组件是前端异步注入的,静态请求拿不到属性,改用 Selenium 等浏览器自动化工具,等渲染完再取值。
  3. 确认存在 data-s 后,原样带进 CaptchaAI 的求解请求,不要额外编码。

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
# }

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);

Selenium 提取(用于动态渲染的页面)

组件异步注入时静态抓取拿不到 data-s,交给浏览器渲染完再取值:

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"}

拿到值后原样提交:

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");
}

常见踩坑与排查

  • 有 data-s 却没传:token 校验静默失败——提交前先检查组件上是否有这个属性。
  • 没有却硬传一个:求解报错或请求被拒——只有真实存在时才带。
  • 跨页面复用同一个值:token 无效——每次加载都重新提取。
  • 额外做了 URL 编码:参数格式错乱——直接传原始 Base64 值。
  • 用了几分钟前的旧值:token 不匹配——提交前立即提取,不要缓存。

封装一个可复用的提取工具

  • 需要同时处理有/无 data-s 的页面,不想每次手写判断逻辑。
  • 想把提取和提交封装成一个类,方便在多个脚本里复用。
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

常见问题

什么情况下可以完全不管 data-s?

目标不是 Google 自家产品时,基本能忽略它。第三方 reCAPTCHA 集成只需要 sitekeypageurl,扫一眼组件 HTML,没有就不用管。

data-s 除了 Google 搜索,还会在哪些地方遇到?

主要是 Google 自家产品:搜索的异常流量验证页几乎必带,YouTube、Google Play 偶尔会有,Google Forms 较少见,第三方集成基本用不到。

提取出来的 data-s 是 None,问题出在哪?

先确认页面本来就没有这个属性,这是常态。如果确定有但没提取到,常见原因是组件异步注入,静态请求没等到渲染完成,改用 Selenium 再取值。

data-s 和 sitekey 能不能一起缓存,减少请求次数?

不能。sitekey 通常固定,data-s 却是会话级的,每次页面加载都会变,必须在每次提交前重新提取。

为什么加了 data-s,token 校验还是失败?

最常见的原因是值不新鲜或被二次编码了——必须是提交前刚提取的原始 Base64 字符串,对照上面“常见踩坑”逐一排查即可。

小结

data-s 是部分 reCAPTCHA 实现——主要是 Google 自家产品——才会出现的会话绑定令牌。存在就提取出来,随请求传给 CaptchaAI 的 API;没有就别硬传。

一句话记住:Google 搜索验证码基本必带 data-s,第三方网站基本用不到。

相关文章

该文章已禁用评论。