如何在 Python 中用 data-s 参数识别 reCAPTCHA

recaptcha data-s parameter - How to Solve reCAPTCHA with the data-s Parameter in Python

Google 自家的页面会在 reCAPTCHA 小组件上多带一个值,它的名字叫 data-s。用普通方式识别这类验证码,你会拿到一个 token,但页面随后就会拒绝它。解决办法是多传一个参数:从页面 HTML 里读出 data-s ,把它传给识别工具,然后立刻提交 token。

recaptcha data-s 参数几乎是所有人都会漏掉的一环,主要是因为它在普通网站上根本不存在。下面说清楚它在哪、该叫什么名字,以及一个从头跑到尾的脚本。

data-s 到底是什么

它是 Google 在自家站点的 reCAPTCHA 小组件上附加的一个短时效、一次性的值。你最常撞上的是 Google 搜索那个 “异常流量” 拦截页。这个值把挑战绑定到那一次具体的页面加载,所以你拿回的 token 只在同一个请求上下文里有效。

由此引出三点,而这三点都会导致失败:

  • 它会过期。 把它当作 nonce 看待。抓页面、识别、提交。不要在两次运行之间缓存它。
  • 它只在 Google 上出现。 别人网站上的 reCAPTCHA 没有 data-s。硬要发过去属于参数错误,而不是无害的多余字段。
  • 它不是 Enterprise 标志。 这两者经常被搞混。 data-senterprise 是两个不同的选项,解决的问题也不一样, reCAPTCHA Enterprise 识别工具 页面讲的是后者。

你需要什么

  • CapSkip 在本地运行。它默认监听 127.0.0.1:8080 ,而 设置指南 介绍了安装过程。
  • Python 3.10 或更高版本。
  • SDK 和一个 HTTP 客户端。
# the CapSkip SDK plus requests for fetching the page
pip install capskip requests

在哪里找到 data-s 的值

你需要的两个值都在同一个元素上。小组件渲染为一个 div ,上面带有 data-sitekey ,另外还有一个只出现在 Google 自家页面上, data-s:

<div class="g-recaptcha"
     data-sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
     data-s="SGVsbG8gdGhlcmUsIHRoaXMgaXMgYSBvbmUtdGltZSB2YWx1ZQ">
</div>

sitekey 是稳定的,可以写死在代码里。而 data-s 的值每次加载都会变,所以必须在运行时从页面里抓出来:

# pip install capskip requests
import re
import requests

PAGE = "https://www.google.com/search?q=example"

# Keep one session. The value is tied to the request that
# produced it, cookies included.
session = requests.Session()
html = session.get(PAGE, timeout=30).text

sitekey = re.search(r'data-sitekey="([^"]+)"', html).group(1)
datas   = re.search(r'data-s="([^"]+)"', html).group(1)

print(sitekey, datas)   # second value is single use

如果第二个匹配结果是空的,说明你不在 Google 页面上,也就完全不需要这个参数。按普通的 v2 挑战识别即可。

传参时写 datas,不是 data-s

这是命名上的坑。原始 HTTP API 接受的参数名和它在 HTML 里出现的形式完全一致, data-s。SDK 用不了这个名字,因为连字符在 Python 关键字参数里不合法,所以每个 SDK 都把它暴露为 datas.

位置姓名
页面 HTMLdata-s
原始 API: in.phpdata-s
Python、Node.js、PHP、.NET SDKdatas

这一点理顺之后,识别就只是一次调用:

from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)

result = solver.recaptcha(
    sitekey=sitekey,
    url=PAGE,
    datas=datas,      # the data-s value, scraped seconds ago
)

print(result["code"])   # g-recaptcha-response token

完整可运行示例

抓取、提取、识别、提交,全部在同一个 session 上完成,让 cookie 和 IP 保持一致:

# pip install capskip requests
import re
import requests
from capskip import (
    CapSkip, ApiException, NetworkException, TimeoutException,
)

PAGE = "https://www.google.com/search?q=example"

session = requests.Session()
solver = CapSkip(host="127.0.0.1", port=8080, recaptchaTimeout=300)

html = session.get(PAGE, timeout=30).text
sitekey = re.search(r'data-sitekey="([^"]+)"', html).group(1)
datas   = re.search(r'data-s="([^"]+)"', html).group(1)

try:
    result = solver.recaptcha(sitekey=sitekey, url=PAGE, datas=datas)
except NetworkException:
    raise SystemExit("CapSkip is not running on 127.0.0.1:8080")
except TimeoutException:
    raise SystemExit("solve took longer than recaptchaTimeout")
except ApiException as err:
    raise SystemExit(f"API refused the task: {err}")

# Submit immediately. The token is good for about two minutes.
response = session.post(
    PAGE,
    data={"g-recaptcha-response": result["code"]},
    timeout=30,
)
print(response.status_code)

这里有两个细节,比看上去更重要。 session 被复用了,所以识别和提交共用同一批 cookie。另外,提交紧接在识别之后进行,因为 reCAPTCHA token 在签发大约两分钟后就不再被接受。

在代理后面运行

如果你是通过代理抓的页面,就用同一个代理去识别。挑战绑定在创建它的那次请求上,从另一条网络路径识别,是 token 被拒的第二大常见原因。

result = solver.recaptcha(
    sitekey=sitekey,
    url=PAGE,
    datas=datas,
    proxy={"type": "HTTPS", "uri": "user:[email protected]:3128"},
)

代理对 reCAPTCHA、Turnstile 和极验(GeeTest)有效。对图片验证码则毫无作用,因为图片验证码从不接触目标站点。

不用 SDK 的同一次调用

这个 API 兼容 2captcha,而且跑在你自己的机器上,所以任何 HTTP 客户端都能用。这里参数保留连字符:

# submit the task, get an ID back
curl -s -X POST "http://127.0.0.1:8080/in.php" \
  -d "key=capskip" \
  -d "method=userrecaptcha" \
  -d "googlekey=YOUR_SITEKEY" \
  -d "pageurl=https://www.google.com/search?q=example" \
  -d "data-s=THE_DATA_S_VALUE" \
  -d "json=1"

# {"status":1,"request":"2122988149"}

然后轮询 res.php 取回 token。包含轮询间隔在内的完整两步流程,见 用 cURL 识别验证码.

其他 SDK 用的是同样的 datas 写法。Node.js 在 options 对象里传,.NET 放进 options 字典里:

// npm install capskip
const { CapSkip } = require('capskip');

const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });

const result = await solver.recaptcha(sitekey, pageUrl, {
  datas: dataS,        // same value, same rules
});

console.log(result.code);

常见错误

你所看到的原因修复
ERROR_GOOGLEKEYsitekey 根本没进到请求里,或者正则匹配到了别的属性识别前先把抓到的值打印出来。详见 修复 ERROR_GOOGLEKEY
ERROR_BAD_PARAMETERSdata-s 发成了空字符串,或者发给了非 Google 页面只在这个属性确实存在时才传它
ERROR_CAPTCHA_UNSOLVABLE通常是过期的 data-s:页面是在识别前几分钟抓取的抓取和识别要一气呵成。参见 ERROR_CAPTCHA_UNSOLVABLE
token 被识别工具接受,却被 Google 拒绝抓取和提交之间 cookie 不同,或者 IP 不同复用同一个 session,两边走同一个代理
NetworkExceptionCapSkip 没有在运行启动应用,在设置里确认端口

每个参数和响应格式都列在 API 文档.

常见问题

普通网站上需要 data-s 吗?

不需要。它只出现在 Google 自家的页面上。如果 HTML 里没有 data-s ,就干脆不要传这个参数,按标准 v2 挑战来识别,就像 reCAPTCHA v2 识别 页面里讲的那样。

data-s 和 Enterprise 标志是一回事吗?

不是,两者互不相干。 enterprise=1 告诉识别工具面对的是哪一种 reCAPTCHA 产品。 datas 携带的则是页面给出的一次性值。Google 搜索的挑战需要后者,如果某个页面确实两个都要,也可以一起传。

这个值能有效多久?

按秒算,不是按分钟算。它随页面一起签发,也随页面一起失效。如果你的爬虫先把页面排队、稍后再识别,那就在识别时重新抓一遍页面,而不是把这个值存起来。

为什么代码里是 datas,HTML 里却是 data-s?

因为连字符不能出现在 Python 关键字参数或 C# 标识符里。SDK 去掉了它,并在发送时替你还原成带连字符的名字。原始的 in.php 调用仍然期望收到 data-s.

小结

抓取 data-sitekeydata-s ,这两个值在同一个元素上;把第二个作为 datas,在同一个 session 上、几分钟之内把 token 提交出去。这三件事漏掉任何一件,你都会得到一个看起来没问题、实际却失败的 token。

Python 这边其余的接口都在 Python 验证码识别 页面上。CapSkip 把上面这一切都作为本地 验证码绕过 服务,跑在你自己的机器上,所以为了拿到一个新值而把页面多抓一次,除了这次请求本身,不花你任何代价。