Explainers

reCAPTCHA 评分因素:完整的技术分析

分数掉到 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 defaultgranteddenied 抛出异常或缺失

这些属性缺失或互相矛盾,合计可拉低 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

挑战成功后 reCAPTCHA 会写入自己的 cookie:_GRECAPTCHA 表示此前解决过,rc::arc::brc::c 保存风险分析数据。每次都用全新上下文、不带这些 cookie,起始分低 0.1–0.2。

Google 账号会话

带着 SIDHSIDSSID 说明存在有历史的 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 这类参数。你能做的是附带 cookiesuserAgentproxy 参数,让识别环境更接近真实会话上下文。

同一份代码本地能过、服务器上就掉分,先查什么?

按权重顺序查三项:

  • WebGL 渲染器是不是 SwiftShader
  • 出口 IP 是否落在数据中心网段
  • 容器时区与 IP 归属地是否一致

这三项通常能解释大部分本地与线上的分数差。

无头浏览器就一定拿不到高分吗?

不一定,但起点明显更低:缺少 GPU 渲染、插件列表和真实窗口尺寸,再叠加数据中心 IP,很容易落到 0.1–0.3。自有站点的回归测试用带界面浏览器更省事;要规模化跑,用接口拿 token 更划算。


小结

reCAPTCHA v3 的分数不是黑箱:浏览器指纹、行为数据、网络特征、cookie 历史与环境一致性各占权重,逐项排查就能定位问题。不想长期维护这套信号,用接口直接获取 token 更省心。

相关文章

该文章已禁用评论。