如果你的 GeeTest v3 集成一直报错,先别急着怀疑账号或参数——十有八九是同一个原因:challenge 值过期了。
CaptchaAI 的GeeTest v3 API 文档写得很明确:每一次解决请求都要拿一个全新的 challenge 值。验证码只要在页面上加载完成,旧的 challenge 就立刻失效。这也是为什么很多集成"看起来参数都对",却还是频繁报错。
GeeTest v3 的失败基本可以归到三类:提交阶段(把任务交给 API)、轮询阶段(等结果)、目标页面验证阶段(API 明明返回了值,页面却不认)。下面逐一拆解,附诊断思路与修复方法。
头号问题:challenge 过期
只能查一件事的话,先查 challenge 的新鲜度。
GeeTest v3 需要两个关键参数:
gt—— 网站公钥(静态,不会变)challenge—— 动态挑战码(每次页面加载都会刷新)
为什么会失效
challenge 是 GeeTest 组件在页面上初始化时生成的。如果你只抓取一次,然后在多次解决请求里反复使用它,从第二次开始就会出现下面两种情况之一:
- 提交时就被 API 直接拒绝;
- API 返回了结果,但目标页面因为 challenge 已过期而拒绝这个结果。
怎么修
每次发起解决请求之前,先看一下页面的网络请求,找到返回新 challenge 的那个接口调用。重放这个请求拿到新值,然后立刻提交给 CaptchaAI,中间不要停顿。
# Pseudocode: fetch a fresh challenge before each solve
import requests
def get_fresh_challenge(target_url):
"""Hit the GeeTest init endpoint to get a new challenge."""
resp = requests.get(f"{target_url}/geetest/register", timeout=10)
data = resp.json()
return data["challenge"], data["gt"]
challenge, gt = get_fresh_challenge("https://example.com")
# Now submit to CaptchaAI immediately — do not delay
经验之谈: 从抓到
challenge到提交解决请求,如果中间隔了超过几秒钟,直接重新抓一次,别赌它还有效。
错误码速查表
先看这张速查表。
| 错误 / 现象 | 阶段 | 可能原因 | 处理方式 |
|---|---|---|---|
ERROR_WRONG_USER_KEY |
提交 | API Key 格式错误 | 核对 32 位密钥 |
ERROR_KEY_DOES_NOT_EXIST |
提交 | Key 无效 | 检查控制台 |
ERROR_ZERO_BALANCE |
提交 | 无空闲线程 | 等待或升级套餐 |
ERROR_PAGEURL |
提交 | 缺少 pageurl |
补全完整页面地址 |
ERROR_BAD_PARAMETERS |
提交 | 缺少 gt、challenge 或 pageurl |
核对所有必填字段 |
CAPCHA_NOT_READY |
轮询 | 仍在求解中 | 等 5 秒再轮询 |
ERROR_WRONG_ID_FORMAT |
轮询 | ID 非纯数字 | 用 in.php 原样返回的 ID |
ERROR_WRONG_CAPTCHA_ID |
轮询 | ID 无效 | 核对提交时的 ID |
ERROR_EMPTY_ACTION |
轮询 | 缺少 action=get |
补上 action 参数 |
ERROR_CAPTCHA_UNSOLVABLE |
轮询 | challenge 过期或变体不支持 | 刷新 challenge 后重试 |
| API 返回值但页面拒绝 | 验证 | challenge 过期、字段映射错、URL 不对 | 刷新 challenge,核对字段映射 |
提交阶段报错
提交到 https://ocr.captchaai.com/in.php 时的常见错误,按现象分类:
- 密钥问题
- 余额不足
- 页面地址缺失
- 参数缺失或格式错误
以下四种:
| 错误码 | 原因 | 修复 |
|---|---|---|
ERROR_WRONG_USER_KEY |
API Key 格式不对(正确长度应该是 32 个字符) | 去captchaai.com/api.php核对一下密钥,别多复制空格或多余字符 |
ERROR_KEY_DOES_NOT_EXIST |
Key 格式没问题,但对不上任何有效账号 | 登录 CaptchaAI 控制台,确认这个 Key 是不是处于激活状态 |
ERROR_ZERO_BALANCE |
当前套餐没有空闲线程可用了 | 等线程释放、降低并发数,或者升级套餐 |
| 返回 HTML 或 500/502 | 服务端瞬时故障,不是你的参数问题 | 等 5–10 秒再重试一次 |
以下两种单独展开:
ERROR_PAGEURL
原因: 请求里缺了 pageurl 参数。修复: 补上 GeeTest 组件所在页面的完整地址,例如:
pageurl=https://staging.example.com/qa-login
ERROR_BAD_PARAMETERS
原因: 有必填字段缺失或格式不对。GeeTest 的必填参数如下:
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
key |
String | 是 | 你的 CaptchaAI API 密钥 |
method |
String | 是 | 固定为 geetest |
gt |
String | 是 | 静态网站公钥 |
challenge |
String | 是 | 动态挑战码(必须新鲜) |
pageurl |
String | 是 | 完整页面地址 |
修复: 逐个检查 gt、challenge、pageurl 是否都存在、格式是否正确。
轮询阶段报错
这一类报错发生在你轮询 https://ocr.captchaai.com/res.php 的时候,本节按下列现象分类:
- 仍在处理中(不算错误)
- ID 格式或匹配问题
- 请求参数缺失
- challenge 过期或不支持
CAPCHA_NOT_READY 不是错误,以下三种:
| 错误码 | 原因 | 修复 |
|---|---|---|
CAPCHA_NOT_READY |
还在解决中,GeeTest v3 在 CaptchaAI 上通常 12 秒以内出结果 | 等 5 秒再轮询一次,不要把它当失败处理 |
ERROR_WRONG_ID_FORMAT |
captcha ID 格式不对——ID 应该是纯数字 | 确认用的是 in.php 原样返回的 ID,没有被截断或改动过 |
ERROR_WRONG_CAPTCHA_ID |
这个 ID 对不上任何已提交的任务 | 检查是不是用了提交响应里正确的那个 ID;如果同时提交了多个任务,确认轮询的是对的那一个 |
还有一种需要代码示例,单独展开:
ERROR_EMPTY_ACTION
原因: 轮询请求里 action 参数缺失或为空。修复: 每次轮询都带上 action=get:
https://ocr.captchaai.com/res.php?key=YOUR_KEY&action=get&id=CAPTCHA_ID
剩下两种:
| 错误码 | 原因 | 修复 |
|---|---|---|
ERROR_CAPTCHA_UNSOLVABLE |
挑战无法求解——多半是 challenge 已过期,也可能碰到了不支持的 GeeTest 变体 |
刷新 challenge 后重试 |
ERROR_INTERNAL_SERVER_ERROR |
CaptchaAI 服务端问题 | 等 10 秒后重试 |
目标页面验证失败
这类问题最难排查:API 返回正常结果,目标页面却依然拒绝。
GeeTest v3 求解成功后,API 会返回三个值:
{
"challenge": "1a2b3456cd67890e12345fab678901c2de",
"validate": "09fe8d7c6ba54f32e1dcb0a9fedc8765",
"seccode": "12fe3d4c56789ba01f2e345d6789c012|jordan"
}
这三个值要按下面的对应关系提交到目标页面:
| API 响应字段 | 目标页面字段 |
|---|---|
challenge |
geetest_challenge |
validate |
geetest_validate |
seccode |
geetest_seccode |
四种常见故障,按出现频率排列:
| 故障 | 现象 | 原因 | 修复 |
|---|---|---|---|
| 字段映射错了 | API 返回了值,页面却立刻拒绝 | 返回值填进了错误的字段,或者提交到了错误的请求路径 | 在目标页面手动完成一次 GeeTest 验证,观察网络请求,找到那个提交 GeeTest 结果的 POST 请求,逐字段核对字段名是否一致 |
上游用的还是过期的 challenge |
API 返回了值,但页面提示 challenge 已过期或无效 | challenge 抓得太早,或者被重复使用了 |
每次解决请求前立刻现抓一个新的 challenge,不要缓存,也不要复用 |
| 页面上下文不对 | 就算参数都是新鲜的,验证还是失败 | 提交给 CaptchaAI 的 pageurl 和 GeeTest 组件实际加载的页面对不上 |
用精确的地址,包括协议和路径;如果组件是通过 AJAX 在别的路由上加载的,就用那个路由的地址 |
| 请求结构不匹配 | 字段名都对,但请求格式不对 | 目标页面对内容类型有特定要求(比如要 JSON 而不是表单编码),或者还需要额外的表单字段一起提交 | 对比你的提交请求和手动验证时抓到的网络流量,核对内容类型、字段顺序和其他随附字段 |
四种故障常叠加出现,建议按上表逐条排查。
场景参考:staging 环境反复触发 ERROR_CAPTCHA_UNSOLVABLE
国内不少注册 / 登录页面用的正是 GeeTest(极验)滑块,QA 团队在自己的 staging 环境里测试这类页面时,经常会遇到"本地手动过一遍没问题,脚本跑就报 ERROR_CAPTCHA_UNSOLVABLE"的情况。多数时候根子还是那个老问题:脚本在抓取 challenge 和实际提交之间隔了太久,或者把同一个 challenge 用在了循环里的多次测试请求上。把"抓取 challenge → 立即提交"这两步在代码里写成一个不可拆分的动作,这类偶发报错基本就能消失。
Python:完整的 GeeTest v3 求解流程(含 challenge 刷新)
import time
import requests
API_KEY = "YOUR_CAPTCHAAI_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
def get_fresh_challenge(target_url):
"""Fetch a fresh GeeTest challenge from the target page."""
resp = requests.get(f"{target_url}/api/geetest/register", timeout=10)
data = resp.json()
return data["gt"], data["challenge"]
def solve_geetest_v3(api_key, gt, challenge, pageurl):
"""Submit a GeeTest v3 challenge and return the validation package."""
# Submit
submit_resp = requests.post(
SUBMIT_URL,
data={
"key": api_key,
"method": "geetest",
"gt": gt,
"challenge": challenge,
"pageurl": pageurl,
"json": 1,
},
timeout=30,
)
submit_resp.raise_for_status()
submit_data = submit_resp.json()
if submit_data.get("status") != 1:
raise RuntimeError(f"Submit failed: {submit_data}")
captcha_id = submit_data["request"]
print(f"Task created — captcha ID: {captcha_id}")
# Wait before first poll
time.sleep(15)
# Poll for result
for _ in range(60):
result_resp = requests.get(
RESULT_URL,
params={
"key": api_key,
"action": "get",
"id": captcha_id,
"json": 1,
},
timeout=30,
)
result_resp.raise_for_status()
result_data = result_resp.json()
if result_data.get("request") == "CAPCHA_NOT_READY":
time.sleep(5)
continue
if result_data.get("status") == 1:
return result_data["request"]
raise RuntimeError(f"Polling error: {result_data}")
raise TimeoutError("GeeTest v3 solve timed out")
# Usage: always fetch a fresh challenge first
PAGE_URL = "https://staging.example.com/qa-login"
gt, challenge = get_fresh_challenge(PAGE_URL)
result = solve_geetest_v3(API_KEY, gt, challenge, PAGE_URL)
print(f"Result: {result}")
# The result contains: challenge, validate, seccode
# Map them to: geetest_challenge, geetest_validate, geetest_seccode
轮询逻辑里有两个数字值得记一下:首次轮询前先等 15 秒(GeeTest v3 的求解一般不会比这更快出结果),之后每 5 秒轮询一次,最多循环 60 次——也就是给了大约 5 分钟的总超时窗口。如果 5 分钟还没出结果,大概率不是"再等等"能解决的,该去检查 challenge 是不是从一开始就没抓对。
Node.js:完整的 GeeTest v3 求解流程(含 challenge 刷新)
const API_KEY = "YOUR_CAPTCHAAI_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function getFreshChallenge(targetUrl) {
const resp = await fetch(`${targetUrl}/api/geetest/register`);
const data = await resp.json();
return { gt: data.gt, challenge: data.challenge };
}
async function solveGeetestV3(apiKey, gt, challenge, pageurl) {
// Submit
const submitResp = await fetch(SUBMIT_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
key: apiKey,
method: "geetest",
gt: gt,
challenge: challenge,
pageurl: pageurl,
json: "1",
}),
});
const submitData = await submitResp.json();
if (submitData.status !== 1) {
throw new Error(`Submit failed: ${JSON.stringify(submitData)}`);
}
const captchaId = submitData.request;
console.log(`Task created — captcha ID: ${captchaId}`);
await sleep(15_000);
// Poll for result
for (let i = 0; i < 60; i++) {
const resultResp = await fetch(
`${RESULT_URL}?${new URLSearchParams({
key: apiKey,
action: "get",
id: captchaId,
json: "1",
})}`
);
const resultData = await resultResp.json();
if (resultData.request === "CAPCHA_NOT_READY") {
await sleep(5_000);
continue;
}
if (resultData.status === 1) {
return resultData.request;
}
throw new Error(`Polling error: ${JSON.stringify(resultData)}`);
}
throw new Error("GeeTest v3 solve timed out");
}
// Usage
const PAGE_URL = "https://staging.example.com/qa-login";
(async () => {
const { gt, challenge } = await getFreshChallenge(PAGE_URL);
const result = await solveGeetestV3(API_KEY, gt, challenge, PAGE_URL);
console.log("Result:", result);
// Map result fields to: geetest_challenge, geetest_validate, geetest_seccode
})();
Node.js 版本的结构和 Python 完全一致:先拿新鲜的 challenge,提交,等 15 秒,再进入最多 60 次、每次间隔 5 秒的轮询循环。两份代码可以直接对照着改成自己项目的错误处理和日志格式。
常见问题
GeeTest v3 报错了,第一步该查什么?
先确认是哪个阶段出的问题:提交时被 API 拒绝,还是轮询时报错,还是 API 给了结果但页面不认。三个阶段的排查方向完全不同,但绝大多数情况下,源头都指向同一件事——challenge 不新鲜了。养成"每次解决前现抓 challenge"的习惯,能提前排除掉一半以上的报错。
pageurl 到底应该填哪个地址?
填 GeeTest 组件实际加载所在的页面完整地址,包括协议和路径。如果登录框是通过 AJAX 从别的路由拉进来的,就填那个路由的地址,而不是浏览器地址栏里看到的那个。地址填错是 ERROR_PAGEURL 和验证阶段"页面上下文不对"这两类问题共同的根源。
API 明明返回了 validate 和 seccode,页面却提示验证码无效,是怎么回事?
按顺序查三件事:
challenge是不是提交前才现抓的;geetest_challenge、geetest_validate、geetest_seccode三个字段是不是准确对应到了目标页面要求的字段名;- 提交请求的格式(JSON 还是表单编码、有没有其他必填字段)是否和手动验证时的网络请求一致。
本地手动测试没问题,脚本一跑就在 staging 环境报 ERROR_CAPTCHA_UNSOLVABLE,是什么原因?
多数情况下,根子是脚本在抓取 challenge 和提交解决请求之间隔了太久,或者把同一个 challenge 用在了循环里的多次测试请求上。排查顺序建议:
- 先确认"抓取
challenge→ 立即提交"是不是写成了一步到位、不可拆分的操作; - 再核对
gt、pageurl是否和 staging 页面完全一致。
GeeTest v4,CaptchaAI 现在支持了吗?
本文只覆盖 GeeTest v3。GeeTest v4 目前还不在 CaptchaAI 支持范围内,具体以CaptchaAI API 文档公布的支持类型为准。同样是测试版的还有:
- CaptchaFox(测试版)
- Friendly Captcha(测试版)
- Lemin(测试版)
修复你的 GeeTest 工作流
如果 GeeTest 集成一直不稳定,按这个顺序过一遍:
- 查 challenge —— 新不新鲜?每次解决前是不是都现抓了一个?
- 核对参数 ——
gt、challenge、pageurl三个是否都正确。 - 核对字段映射 —— 返回的
challenge、validate、seccode是否精确对应到了目标页面的字段名。 - 对照手动验证 —— 用浏览器 DevTools 抓一次成功的手动 GeeTest 验证,逐字段核对请求结构。
想快速上手,可以从CaptchaAI GeeTest v3 求解器开始,用API 文档核对参数,需要了解 challenge 流程背景的话,可以读一读GeeTest v3 验证码的工作原理。对于 reCAPTCHA v2 的类似排查思路,参见如何用 API 解决 reCAPTCHA v2。
迭代日志
| 迭代 | 重点 | 变化 |
|---|---|---|
| 草案1 | 结构和内容 | 初始故障排除草案 — 3 个错误阶段、错误修复表、常见问题解答 |
| 草案2 | 技术准确性 | 对照 captchaai.com/api-docs. 验证了所有错误代码和 GeeTest 参数 添加了 API 参数表。已确认challenge/validate/seccode字段映射。 |
| 草案3 | 代码示例 | 添加了带有新鲜挑战获取的完整 Python 和 Node.js 示例。添加了挑战刷新模式的伪代码。 |
| 草案4 | 验证失败深度 | 扩展了目标页面验证部分,具有 4 种不同的故障模式。添加了字段映射表。添加了请求结构不匹配诊断。 |
| 草案5 | 最终 QA 润色 | 已验证所有错误代码与官方文档相符。添加了快速参考表。收紧简介。添加了集群文章的交叉链接。已确认的常见问题解答已准备好架构。 |
| 草案6 | 中文本地化改写 | 全文按中文技术写作习惯重写,改用答案先行的开头;新增 staging 环境场景示例;常见问题改为 5 条、与英文版重合度控制在 50% 以内;标题保留,元描述与关键词改为中文原生搜索词。 |
视觉资产简介
以下为设计素材简报。
英雄形象
- 替代文本: 开发人员对 GeeTest v3 错误进行故障排除 — 请求、轮询和验证失败诊断
- 必须显示: 包含错误流程阶段和故障点的调试上下文
- 文件名: geetest-v3-errors-troubleshooting-hero.png
文章内视觉1
- 放置: 在"结果阶段错误"之后
- 类型: 决策树
- 替代文本: GeeTest v3 失败的决策树 - 请求错误与轮询错误与验证失败
- 文件名: geetest-v3-error-decision-tree.png
文章内视觉2
- 放置: 在"目标页面验证失败"之后
- 类型: 原因与修复图
- 替代文本: 显示 GeeTest v3 页面拒绝的常见原因及其修复的图表
- 文件名: geetest-v3-validation-causes-fixes.png
相关文章
延伸阅读: