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

solve geetest in python - How to Solve GeeTest v3 in Python and Post It Back

大多数验证码代码假设传入一个 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, validateseccode。提交那三个。

我可以在两次运行之间缓存挑战吗?

不可以。它是一次性的,约一分钟后过期。 gt 可以安全缓存;挑战则绝不可以。

这涵盖极验 v4 吗?

加载的 geetest 方法针对 v3,即围绕 gtchallenge 参数对构建的滑块拼图版本。v4 改变了参数模型,因此在假设同一调用可用之前,请查看当前的 API 文档 在假设同一调用适用之前请检查。

小结

获取 gtchallenge 构建的滑块拼图,在识别前用一个缓存破坏的请求立即获取,调用 geetest,然后把 challenge, validateseccode 一起回传。使用返回的 challenge 而非你的输入,并且绝不要在识别之前批量获取挑战。

其他语言见 GeeTest 识别工具 页面,更广泛的 Python 用法见 Python 验证码识别 页面,我们的 GeeTest v3 演示上尝试一个实时拼图。CapSkip 在本地处理 验证码绕过 在本地进行,因此识别量是免费的。