抓取一个用 GeeTest(极验)保护注册接口的网站时,最常见的报错不是网络问题,而是参数本身不对。GeeTest v3 只依赖两个字段:gt(站点级别的静态标识符)和 challenge(一次性会话令牌)。提取得不新鲜,识别再准也会被拒绝。本文给出三种可落地的提取方式,并演示如何交给 CaptchaAI 完成识别、注入回表单。
gt 与 challenge:两个参数分别是什么
gt——站点标识符,32 位十六进制,例如019924a82c70bb123aae90d483b6a0ed。每个站点固定不变,可缓存复用。challenge——会话令牌,同样是 32 位十六进制,例如a3f5c8d2e1b04a6789012345678abcdf。每次加载都会变,缓存旧值只会换来ERROR_BAD_PARAMETERS或结果被拒。api_server——GeeTest API 子域名,可选,例如api-na.geetest.com,同样固定不变。
举个例子:给东南亚跨境电商比价工具抓商品详情页,注册接口大概率用的是 GeeTest 而不是 reCAPTCHA——出海类站点常见这套方案,也更少遇到 reCAPTCHA 那种连通性问题。
三种方法怎么选,看参数放在哪:走注册接口用方法一;写死在页面源码用方法二;框架异步渲染、页面里找不到参数就用方法三挂钩 initGeetest。
方法一:拦截注册接口的返回值
大多数 GeeTest v3 集成会先向注册端点发请求,拿到 challenge 后再渲染滑块,响应通常长这样:
{
"success": 1,
"challenge": "a3f5c8d2e1b04a6789012345678abcdf",
"gt": "019924a82c70bb123aae90d483b6a0ed",
"new_captcha": true
}
用 requests.Session 请求页面、正则匹配出注册接口地址,再请求一次拿 JSON,是最直接的做法:
Python(requests)
import requests
import re
session = requests.Session()
html = session.get("https://staging.example.com/qa-login").text
# Find the register endpoint
register_url = re.search(
r'(https?://[^"\']+(?:register|captcha|geetest)[^"\']*)',
html
)
if register_url:
resp = session.get(register_url.group(1)).json()
gt = resp["gt"]
challenge = resp["challenge"]
print(f"gt: {gt}")
print(f"challenge: {challenge}")
若站点用前端框架异步渲染,页面源码里没有注册接口 URL,正则会直接落空——这时换 Puppeteer 监听真实网络响应:
JavaScript(Puppeteer 网络拦截)
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch();
const page = await browser.newPage();
let geetestParams = {};
page.on('response', async (response) => {
const url = response.url();
if (url.includes('register') || url.includes('captcha')) {
try {
const json = await response.json();
if (json.gt && json.challenge) {
geetestParams = {
gt: json.gt,
challenge: json.challenge,
};
console.log('Captured GeeTest params:', geetestParams);
}
} catch (e) {}
}
});
await page.goto('https://staging.example.com/qa-login', { waitUntil: 'networkidle2' });
console.log('Final params:', geetestParams);
方法二:直接从页面源码里抠出参数
有些站点直接把 gt/challenge 写死在 data-* 属性或内联脚本里,连注册接口都不用拦截:
import re
# From data attributes
gt_match = re.search(r'data-gt=["\']([a-f0-9]{32})', html)
challenge_match = re.search(r'data-challenge=["\']([a-f0-9]{32})', html)
# From JavaScript variables
if not gt_match:
gt_match = re.search(r'gt\s*[=:]\s*["\']([a-f0-9]{32})', html)
if not challenge_match:
challenge_match = re.search(r'challenge\s*[=:]\s*["\']([a-f0-9]{32})', html)
gt = gt_match.group(1) if gt_match else None
challenge = challenge_match.group(1) if challenge_match else None
print(f"gt={gt}, challenge={challenge}")
最省事也最脆弱——站点改版把参数挪到 XHR 里,正则就全落空,退回方法一。
方法三:挂钩 initGeetest 拿到完整配置
GeeTest v3 通过 initGeetest() 完成初始化。与其去猜参数藏在哪,不如在页面脚本运行前把这个函数替换掉,让配置对象自己“交出来”:
// Puppeteer: inject before page scripts
await page.evaluateOnNewDocument(() => {
window.__geetestConfig = null;
const origInit = window.initGeetest;
Object.defineProperty(window, 'initGeetest', {
set(fn) {
this._initGeetest = function(config, callback) {
window.__geetestConfig = config;
console.log('GeeTest config:', JSON.stringify(config));
return fn(config, callback);
};
},
get() { return this._initGeetest; }
});
});
await page.goto('https://staging.example.com/qa-login', { waitUntil: 'networkidle2' });
const config = await page.evaluate(() => window.__geetestConfig);
// config = { gt: "019924a...", challenge: "a3f5c8d...", product: "bind", ... }
几乎不受页面改版影响,代价是要跑真实浏览器。三种方法按顺序兜底:先试方法一,拦截不到试方法三,参数写死在页面的站点用方法二。
提交给 CaptchaAI 完成识别
拿到 gt、challenge 后,提交 in.php、轮询 res.php,流程和其他类型一致,只是 method 换成 geetest:
Python
import requests
import time
API_KEY = "YOUR_API_KEY"
# Submit
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "geetest",
"gt": gt,
"challenge": challenge,
"pageurl": "https://staging.example.com/qa-login",
"json": "1",
}).json()
if resp["status"] != 1:
raise Exception(f"Submit error: {resp['request']}")
task_id = resp["request"]
# Poll
for _ in range(30):
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["status"] == 1:
solution = result["request"]
print(f"Solution: {solution}")
break
if result["request"] != "CAPCHA_NOT_READY":
raise Exception(f"Error: {result['request']}")
JavaScript
const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: 'YOUR_API_KEY',
method: 'geetest',
gt: geetestParams.gt,
challenge: geetestParams.challenge,
pageurl: 'https://staging.example.com/qa-login',
json: 1,
}
});
const taskId = submit.data.request;
let solution = null;
for (let i = 0; i < 30; i++) {
await new Promise(r => setTimeout(r, 5000));
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: 'YOUR_API_KEY', action: 'get', id: taskId, json: 1 }
});
if (poll.data.status === 1) {
solution = poll.data.request;
break;
}
}
console.log('Solution:', solution);
把识别结果注入表单
CaptchaAI 对 GeeTest v3 的识别结果包含三个字段:challenge、validate、seccode,都要以隐藏输入写回表单再触发提交:
# solution is a pipe-delimited string or JSON object
# Parse and inject into the form
import json
sol = json.loads(solution) if isinstance(solution, str) else solution
driver.execute_script("""
const form = document.querySelector('form');
function addHidden(name, value) {
let input = form.querySelector(`input[name="${name}"]`);
if (!input) {
input = document.createElement('input');
input.type = 'hidden';
input.name = name;
form.appendChild(input);
}
input.value = value;
}
addHidden('geetest_challenge', arguments[0]);
addHidden('geetest_validate', arguments[1]);
addHidden('geetest_seccode', arguments[2]);
""", sol["challenge"], sol["validate"], sol["seccode"])
常见问题
GeeTest 的 challenge 一般能保持多久有效?
通常 60–120 秒,具体因站点而异。稳妥做法是提取完立即提交给 CaptchaAI,中间不要插入其他耗时操作。
GeeTest v4 能用 CaptchaAI 处理吗?
目前还不支持,GeeTest v4 处于“即将支持”阶段。本文方法只适用于 v3——如果页面用的是 captcha_id 而不是 gt/challenge,说明站点已升级到 v4。
为什么方法一一直拦截不到 register 接口的返回?
两个常见原因:接口地址没有 register/captcha/geetest 关键词,正则匹配不到;或请求发得太早,监听器还没挂上。前者去网络面板手动找一次真实地址,后者改用方法三。
GeeTest v3 和 v4 具体有什么区别?
v3 用 gt/challenge,交互是滑块;v4 换成 captcha_id,挑战类型更多(点击、图形匹配、滑动),两者参数结构不兼容。
排查清单
ERROR_BAD_PARAMETERS——gt或challenge缺失,两个字段都必填,重新提取一遍。- 识别结果被拒绝——
challenge在识别期间过期,缩短提取和提交的间隔,别攒着批量提交。 gt值明显不对——从别的站点复制过来的,gt是站点专属的,从目标页面重新提取。- 提取脚本拿不到值——参数走了 XHR 而不是页面源码,换方法一或方法三。
开始识别 GeeTest v3
在 CaptchaAI 官网 免费注册即可拿到 API Key,用上面的代码几分钟内就能跑通第一次识别。