Troubleshooting

常见 GeeTest v3 错误和修复

如果你的 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 提交 缺少 gtchallengepageurl 核对所有必填字段
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 完整页面地址

修复: 逐个检查 gtchallengepageurl 是否都存在、格式是否正确。


轮询阶段报错

这一类报错发生在你轮询 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 明明返回了 validateseccode,页面却提示验证码无效,是怎么回事?

按顺序查三件事:

  • challenge 是不是提交前才现抓的;
  • geetest_challengegeetest_validategeetest_seccode 三个字段是不是准确对应到了目标页面要求的字段名;
  • 提交请求的格式(JSON 还是表单编码、有没有其他必填字段)是否和手动验证时的网络请求一致。

本地手动测试没问题,脚本一跑就在 staging 环境报 ERROR_CAPTCHA_UNSOLVABLE,是什么原因?

多数情况下,根子是脚本在抓取 challenge 和提交解决请求之间隔了太久,或者把同一个 challenge 用在了循环里的多次测试请求上。排查顺序建议:

  • 先确认"抓取 challenge → 立即提交"是不是写成了一步到位、不可拆分的操作;
  • 再核对 gtpageurl 是否和 staging 页面完全一致。

GeeTest v4,CaptchaAI 现在支持了吗?

本文只覆盖 GeeTest v3。GeeTest v4 目前还不在 CaptchaAI 支持范围内,具体以CaptchaAI API 文档公布的支持类型为准。同样是测试版的还有:

  • CaptchaFox(测试版)
  • Friendly Captcha(测试版)
  • Lemin(测试版)

修复你的 GeeTest 工作流

如果 GeeTest 集成一直不稳定,按这个顺序过一遍:

  1. 查 challenge —— 新不新鲜?每次解决前是不是都现抓了一个?
  2. 核对参数 —— gtchallengepageurl 三个是否都正确。
  3. 核对字段映射 —— 返回的 challengevalidateseccode 是否精确对应到了目标页面的字段名。
  4. 对照手动验证 —— 用浏览器 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

相关文章

延伸阅读:

该文章已禁用评论。