分数掉到 0.1 从来不是随机的。reCAPTCHA v3 的 0.0–1.0 分由 Google 的高级风险分析(ARA)引擎实时计算,每一档都对应一组可以逐项排查的信号。算法未公开,但主要因素早已被社区的对照测试摸清。
下面按五类信号拆解:看什么、权重多高、典型扣分多少。文中数字基于公开资料与内部观测样本,仅供参考,请在自有环境中测量。
分数是怎么算出来的:五类信号
ARA 引擎把特征分成五组,最终分数是加权结果:
Final Score = f(Browser Signals, Behavioral Signals, Network Signals,
History Signals, Environmental Signals)
权重并不均分:行为信号最重,其次是浏览器指纹与网络信誉,历史和环境信号只是微调。排查低分按这个顺序最省时间。
信号一:浏览器指纹(权重:高)
这一类只回答一个问题:发起请求的是真实浏览器,还是自动化运行时。
Canvas 指纹如何暴露运行环境
reCAPTCHA 会创建隐藏的 canvas 元素,画一段文字再把像素读回来。浏览器、系统与 GPU 组合不同,渲染结果就不同,因此构成一枚稳定特征。
// reCAPTCHA performs something similar to:
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.textBaseline = "top";
ctx.font = "14px 'Arial'";
ctx.fillText("Hello", 2, 2);
const fingerprint = canvas.toDataURL();
| canvas 读回的数据 | 分数影响 |
|---|---|
| 与浏览器、操作系统声明一致 | 中性 |
| 命中已知的无头运行环境模式 | -0.3 至 -0.5 |
| 读取被拦截或返回完全统一的数据 | -0.4 至 -0.6 |
WebGL 渲染器暴露的 GPU 信息
WebGL 的渲染器字符串会直接把显卡型号交出来:
const gl = document.createElement('canvas').getContext('webgl');
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
// Expected: "ANGLE (NVIDIA GeForce GTX 1060...)"
// Headless: "SwiftShader" or "Google SwiftShader"
没有 GPU 的服务器上,无头 Chrome 默认走 SwiftShader 软件渲染器,字符串一眼可辨,对应 0.3–0.5 的扣分。这也是本地正常、上云主机就掉分的常见原因。
reCAPTCHA 会探测哪些 JavaScript API
引擎还会逐个读取 navigator 属性,看是否自洽:
| 探测的 API | 真实浏览器中的表现 | 自动化环境中的异常 |
|---|---|---|
navigator.userAgent |
与真实 Chrome 版本一致 | 版本过时或自动化工具默认 UA |
navigator.plugins |
PluginArray 含 1 项以上 | 为空或缺失 |
navigator.languages |
含区域设置的数组 | 空数组或缺失 |
navigator.hardwareConcurrency |
与实际 CPU 匹配 | 有时为 0 或 undefined |
navigator.deviceMemory |
2–16 GB | 部分无头环境缺失 |
window.chrome |
带 runtime 等子对象 | 无头 Chrome 中缺失 |
Notification.permission |
default、granted 或 denied |
抛出异常或缺失 |
这些属性缺失或互相矛盾,合计可拉低 0.3–0.5 分;矛盾比缺失更敏感。
信号二:行为数据(权重:非常高)
行为信号权重最高,从页面加载到 token 生成全程采集。
键盘输入节奏
引擎关心的不是你输入了什么,而是怎么输入的:
Inter-key timing:
Human: Variable (50-300ms between keys, varies by key pair)
Bot: Constant (all keys at same interval, or instant paste)
Key events:
Human: keydown → keypress → keyup for each character
Bot: May skip keypress, or fire all events simultaneously
Corrections:
Human: Occasional backspace, re-typing
Bot: Perfect input without corrections
直接给 value 赋值或整段粘贴会跳过大部分键盘事件,扣分约 0.1–0.2,幅度不大但容易叠加。
鼠标轨迹
鼠标坐标与时间戳全程记录:
| 信号 | 真人的模式 | 脚本的模式 |
|---|---|---|
| 事件数量 | 每次访问 50–500 次以上 | 0–5 次 |
| 移动轨迹 | 速度不均匀的曲线 | 直线、匀速 |
| 加减速 | 自然的起步与减速 | 瞬间启动、瞬间停止 |
| 微小抖动 | 悬停时有轻微抖动 | 两次移动之间完全静止 |
| 越过目标 | 会略微越过再回调 | 每次精准命中中心点 |
完全没有鼠标事件是最重的单项扣分之一,可达 0.4–0.7。
滚动与阅读节奏
真人滚动分段、速度不均,中间会停下来读;脚本要么不滚动,要么一步跳到表单。表单交互前没有滚动事件,扣分约 0.2–0.3。
页面内的动线顺序
引擎也看元素的触碰顺序。真人会先点几下、把鼠标停在链接上、读完再填表;脚本直接定位输入框按 DOM 顺序填完提交。这种“零探索”动线扣分约 0.1–0.3。
信号三:网络与出口 IP(权重:中高)
HTTP 请求头是否自洽
服务端会检查请求头是否像浏览器发出的:
| 请求头 | 真人客户端 | 自动化客户端 |
|---|---|---|
User-Agent |
与现存浏览器版本一致 | 版本过时或工具默认 UA |
Accept-Language |
带完整区域偏好 | 缺失或通用 |
Accept-Encoding |
gzip, deflate, br |
缺失或异常 |
Sec-CH-UA |
客户端提示与浏览器一致 | 缺失 |
TLS 特征(JA3/JA4)
TLS 握手会暴露客户端支持的密码套件与扩展,顺序固定,因此每种客户端都有特征:
- Chrome: 特定的密码套件顺序、TLS 1.3、带 GREASE 扩展
- Python requests: 密码套件顺序不同,没有 GREASE,扩展列表更简单
- curl: 扩展最少,呈现 OpenSSL 的特有模式
用 requests 直接请求时,TLS 层几乎必然对不上,扣分约 0.2–0.4。
IP 信誉:数据中心段与家庭宽带段的差别
Google 的 IP 信誉库综合该 IP 的历史挑战结果、数据中心网段(AWS、GCP、Azure 等)、公开 VPN 网段与滥用举报。
| IP 类型 | 典型分数影响 |
|---|---|
| 家庭宽带,无滥用记录 | 中性至 +0.1 |
| 家庭宽带,有少量滥用记录 | -0.1 至 -0.2 |
| 数据中心网段 | -0.3 至 -0.5 |
| 已知 VPN 网段 | -0.2 至 -0.4 |
| Tor 出口节点 | -0.4 至 -0.6 |
| 近期被标记为滥用 | -0.3 至 -0.6 |
信号四:cookie 与历史信誉(权重:中等)
reCAPTCHA 自己的 cookie
挑战成功后 reCAPTCHA 会写入自己的 cookie:_GRECAPTCHA 表示此前解决过,rc::a、rc::b、rc::c 保存风险分析数据。每次都用全新上下文、不带这些 cookie,起始分低 0.1–0.2。
Google 账号会话
带着 SID、HSID、SSID 说明存在有历史的 Google 会话,起始分高 0.1–0.3;没有 Google cookie 的访客按未知用户处理。
跨站点信誉
信誉跟着浏览器配置走,不跟着站点走:同一份浏览器配置在 A 站的良好记录会带到 B 站。
信号五:环境一致性(权重:中低)
时区与 IP 归属地是否对得上
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
// Should match IP geolocation
// "America/New_York" with a New York IP → consistent
// "America/New_York" with a Tokyo IP → suspicious
容器镜像默认 UTC、出口 IP 却在新加坡或法兰克福,这种组合常见,也容易被识别。
屏幕与窗口尺寸
screen.width, screen.height // Physical screen
window.innerWidth, innerHeight // Browser window
window.outerWidth, outerHeight // Including browser chrome
0×0、超大分辨率,或整批任务全用同一个默认尺寸,扣分约 0.1–0.2。
电池状态(如果可读)
navigator.getBattery().then(battery => {
battery.level; // 0.0 to 1.0
battery.charging; // true/false
});
Battery API 的信号很弱:电量满格且始终在充电,与桌面或服务器环境一致,本身不算异常。
一个国内团队常遇到的场景
出海业务的数据采集最典型:本地用带界面的 Chrome 调试,分数稳在 0.7 以上;同一份代码放到海外云主机、切成无头模式后掉到 0.1。原因不是某一项写错,而是同时踩中四类信号——SwiftShader 渲染器、数据中心 IP 段、UTC 时区,以及没有鼠标与滚动事件的会话。
还有两点国内前提:
- 国内站点多用 GeeTest(极验)、网易易盾、腾讯防水墙,reCAPTCHA 一般只在出海项目里遇到。
- reCAPTCHA 脚本由 Google 域名托管,内地网络下不一定可达;调试失败可能只是脚本没加载,先确认
grecaptcha存在再看分数。
采集边界同样要守:只采集你有权采集的数据,遵守 robots 协议与《网络安全法》《数据安全法》《个人信息保护法》。
安全范围: 本指南仅适用于你自有或经授权的 QA、staging 与预发布环境。内容覆盖针对你自己 CAPTCHA 集成的诊断、测试与可观测性模式 — 不涉及第三方站点或未授权流程。
想把分数拉上来,各项做法的代价
| 做法 | 预期影响 | 实施难度 |
|---|---|---|
| 用带界面的 Chrome 代替无头模式 | +0.3 至 +0.5 | 低 |
| UA、平台、语言、时区互相自洽 | +0.2 至 +0.3 | 低 |
| QA 脚本保留真实鼠标与滚动交互 | +0.2 至 +0.4 | 中 |
| 使用自有服务器基础设施出口 IP | +0.2 至 +0.3 | 中 |
| 跨会话复用 Google cookie | +0.1 至 +0.2 | 中 |
| 用真实浏览器发请求,而非裸 HTTP 客户端 | +0.1 至 +0.3 | 高 |
| 提交表单前先模拟页面浏览 | +0.1 至 +0.2 | 中 |
这张表真正的成本在维护:Chrome 每几周一个版本,调好的参数很快过期。
另一种思路:直接用 API 拿 token
与其逐项调信号,不如把这一步交给接口:提交 sitekey 与页面地址,轮询拿回 token,再提交到你自己的表单。
import requests
import time
API_KEY = "YOUR_API_KEY"
# Request a high-score token
submit = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": "6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp",
"pageurl": "https://staging.example.com/qa-login",
"version": "v3",
"action": "login",
"json": 1,
})
task_id = submit.json()["request"]
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"High-score token received")
break
这段代码每 5 秒轮询一次 res.php,最多等 5 分钟;浏览器、行为与网络信号由服务端统一处理,具体分数请在自有环境中测量。CaptchaAI 按并发线程计费,不按识别次数计费:BASIC($15/月,5 线程)够单机脚本用,采集集群一般选 ADVANCE($90/月,50 线程)。
常见问题
reCAPTCHA v3 的分数多少算正常?
0.7 以上通常被视为接近真人,多数站点把阈值设在 0.5–0.7,0.9 几乎能通过所有实现。阈值由站点自行配置,同一个 0.6 分在 A 站放行、B 站被拦很正常。
提交任务时能指定想要的分数吗?
不能。分数由 Google 判定,CaptchaAI 接口没有 score-target 这类参数。你能做的是附带 cookies、userAgent 和 proxy 参数,让识别环境更接近真实会话上下文。
同一份代码本地能过、服务器上就掉分,先查什么?
按权重顺序查三项:
- WebGL 渲染器是不是 SwiftShader
- 出口 IP 是否落在数据中心网段
- 容器时区与 IP 归属地是否一致
这三项通常能解释大部分本地与线上的分数差。
无头浏览器就一定拿不到高分吗?
不一定,但起点明显更低:缺少 GPU 渲染、插件列表和真实窗口尺寸,再叠加数据中心 IP,很容易落到 0.1–0.3。自有站点的回归测试用带界面浏览器更省事;要规模化跑,用接口拿 token 更划算。
小结
reCAPTCHA v3 的分数不是黑箱:浏览器指纹、行为数据、网络特征、cookie 历史与环境一致性各占权重,逐项排查就能定位问题。不想长期维护这套信号,用接口直接获取 token 更省心。