如何用 Python 识别极验 v3 并回传

大多数验证码代码假设传入一个 token、传出一个 token。极验打破了这一点。一次识别返回 三个 必须一起提交的值,而且你传入的挑战在你获取后约六十秒过期。这两点都让人栽跟头,且都不会以有用的信息报错。
两个行为不同的输入
| 值 | 有效期 |
|---|---|
gt | 对站点是静态的。可自由缓存 |
challenge | 一次性,约 60 秒后失效 |
两者都来自站点自己的极验初始化端点。 gt 实际上是一个站点标识符。 challenge 是按次的,把它当作可重用,正是极验集成在测试中有效、在负载下崩溃的最常见原因。
设置
# 需要 Python 3.10 或更高版本。 pip install capskip
from capskip import CapSkip
solver = CapSkip(
host="127.0.0.1",
port=8080,
recaptchaTimeout=300, # GeeTest uses this, not defaultTimeout
)那个超时区别很容易被忽略。 defaultTimeout 仅管辖图片验证码;所有交互式类型都使用 recaptchaTimeout.
识别
result = solver.geetest(
gt="81388ea1fc187e0c335c0a8907ff2625",
challenge="7cf6a8b1a2c34d5e6f7089abcdef0123",
url="https://example.com/login",
)
print(result["challenge"])
print(result["validate"])
print(result["seccode"])那三个键就是答案。 result["code"] 也会被填充,但对于极验,它保存的是原始 JSON 字符串,而不是可以直接提交的内容,因此出于习惯去使用它会导致令人困惑的失败。
使用 challenge 返回的那个,而不是你传入的那个。它们并不总是相同的值。
完整流程,集中在一处
获取和识别必须相邻。两者之间任何慢操作都有过期风险:
import time
import requests
from capskip import CapSkip
solver = CapSkip()
LOGIN = "https://example.com/login"
# 1. Fresh pair, cache-busted. Cached init responses hand back dead challenges.
init = requests.get(
"https://example.com/geetest/init",
params={"t": int(time.time() * 1000)},
).json()
# 2. Solve immediately. Do not queue this.
result = solver.geetest(gt=init["gt"], challenge=init["challenge"], url=LOGIN)
# 3. Post all three together, alongside the real form fields.
response = requests.post(LOGIN, data={
"geetest_challenge": result["challenge"],
"geetest_validate": result["validate"],
"geetest_seccode": result["seccode"],
"username": "...",
"password": "...",
})缓存破坏时间戳比看起来更重要。极验初始化端点常被 CDN 或代理缓存,缓存的响应会给你一个已被消费的挑战。
那三个字段名是常见的极验 v3 约定,但站点可以重命名它们。在假设之前请检查真实表单。
同时进行多个
这里的陷阱是错误地批处理。获取一堆挑战再一起识别,必然导致靠后的那些在轮到之前就过期。请在同一任务中获取并识别:
import asyncio
from capskip import AsyncCapSkip
async def solve_one(solver, url):
init = await fetch_pair(url) # your own fetch, awaited
return await solver.geetest(
gt=init["gt"], challenge=init["challenge"], url=url,
)
async def main():
solver = AsyncCapSkip()
return await asyncio.gather(*[solve_one(solver, u) for u in urls])
asyncio.run(main())Python 是唯一一个 AsyncCapSkip 是真正异步客户端而非别名的 CapSkip SDK,因此这确实能并行运行识别。
当它失败时
from capskip import (
ValidationException, NetworkException, ApiException, TimeoutException,
)
try:
result = solver.geetest(gt=gt, challenge=challenge, url=page_url)
except ValidationException:
pass # missing gt or challenge
except NetworkException:
pass # CapSkip is not running
except ApiException:
pass # usually a challenge that expired before the solve finished
except TimeoutException:
pass # exceeded recaptchaTimeout实际上,大多数极验失败会以 ApiException 形式出现,意味着挑战已失效。修复方法是稍后再获取,而不是用相同的参数对重试,因为已消费的挑战永远不会再次生效。
常见问题
为什么 result["code"] 不可用?
因为答案是三个值而非一个。 code 保留原始 JSON 以求完整,而 SDK 将有用的部分展开到 challenge, validate 和 seccode。提交那三个。
我可以在两次运行之间缓存挑战吗?
不可以。它是一次性的,约一分钟后过期。 gt 可以安全缓存;挑战则绝不可以。
这涵盖极验 v4 吗?
加载的 geetest 方法针对 v3,即围绕 gt 和 challenge 参数对构建的滑块拼图版本。v4 改变了参数模型,因此在假设同一调用可用之前,请查看当前的 API 文档 在假设同一调用适用之前请检查。
小结
获取 gt 和 challenge 构建的滑块拼图,在识别前用一个缓存破坏的请求立即获取,调用 geetest,然后把 challenge, validate 和 seccode 一起回传。使用返回的 challenge 而非你的输入,并且绝不要在识别之前批量获取挑战。
其他语言见 GeeTest 识别工具 页面,更广泛的 Python 用法见 Python 验证码识别 页面,我们的 GeeTest v3 演示上尝试一个实时拼图。CapSkip 在本地处理 验证码绕过 在本地进行,因此识别量是免费的。
