如何用 C# 识别极验 v3

极验打破了大多数验证码代码所基于的心智模型。没有单一 token 可以放入表单字段。一次识别返回 三个 必须一起回传的值,而且你所依据的挑战约一分钟后过期。任一部分出错,请求就会失败,且没有有用的错误信息。
下面是 C# 中的完整流程,包括那些容易出错的部分。
两个输入,其中只有一个是稳定的
极验 v3 用 gt 识别一个站点,用 challenge识别单次尝试。它们的行为完全不同。
| 值 | 有效期 | 来源 |
|---|---|---|
gt | 对站点是静态的。可安全地硬编码或缓存 | 站点的极验初始化响应 |
challenge | 一次性,约 60 秒后过期 | 同一个初始化响应,每次都是新的 |
该过期时间是极验失败最常见的单一原因。如果你获取一对参数、把任务排队,然后在其他工作之后三十秒才识别,挑战可能已经失效。请在识别前立即获取它,绝不要从缓存获取。
设置
# 目标为 .NET Standard 2.0,因此 Framework 4.6.1+ 与 .NET 6-9 均可使用。 dotnet add package CapSkip
CapSkip 在你自己的机器上运行,因此先启动应用,并让客户端指向其设置中的端口:
using CapSkip;
var solver = new CapSkipClient(
apiKey: "capskip", // any string when key validation is off
host: "127.0.0.1",
port: 8080,
recaptchaTimeout: 300); // seconds, and this one covers GeeTest too请注意,极验使用 recaptchaTimeout,而不是 defaultTimeout。后者仅适用于图片验证码。
识别它
三个位置参数,按此顺序:
var result = await solver.GeetestAsync(
"81388ea1fc187e0c335c0a8907ff2625", // gt, static per site
"7cf6a8b1a2c34d5e6f7089abcdef0123", // challenge, fetched seconds ago
"https://example.com/login");
Console.WriteLine(result.Challenge);
Console.WriteLine(result.Validate);
Console.WriteLine(result.Seccode);那三个属性就是答案。 result.Code 也会被填充,但对极验来说它保存的是原始 JSON 字符串而非可用的 token,因此出于习惯去用 代码 是个错误。在 SolveResult, Challenge, Validate 和 Seccode 上,它们是极验专有的,对其他所有类型都为 null。
值得知道的是:返回的 challenge 并不总是你传入的那个。请使用返回值,而不是你的输入。
回传答案
将三个值完全按照站点自己前端的方式提交。大多数极验 v3 集成使用以下字段名,不过站点可能重命名它们,因此在假设之前请检查真实表单:
using System.Net.Http;
using System.Collections.Generic;
using var http = new HttpClient();
var form = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("geetest_challenge", result.Challenge),
new KeyValuePair<string, string>("geetest_validate", result.Validate),
new KeyValuePair<string, string>("geetest_seccode", result.Seccode),
new KeyValuePair<string, string>("username", "..."),
new KeyValuePair<string, string>("password", "..."),
});
var response = await http.PostAsync("https://example.com/login", form);只发送三个中的两个,或把新的 validate 与过期的 challenge配对,都会像错误答案一样被拒绝。
完整流程,按顺序
获取参数对和识别必须紧挨在一起。这种结构让它们保持相邻:
using System;
using System.Net.Http;
using System.Text.Json;
using CapSkip;
// 1. Fetch a fresh gt/challenge pair from the site's own init endpoint.
using var http = new HttpClient();
var initJson = await http.GetStringAsync(
"https://example.com/geetest/init?t=" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
using var doc = JsonDocument.Parse(initJson);
var gt = doc.RootElement.GetProperty("gt").GetString();
var challenge = doc.RootElement.GetProperty("challenge").GetString();
// 2. Solve immediately. Do not queue this or await anything slow in between.
var result = await solver.GeetestAsync(gt, challenge, "https://example.com/login");
// 3. Post all three values together.
Console.WriteLine($"{result.Challenge} {result.Validate} {result.Seccode}");初始化调用上的缓存破坏时间戳比看起来更重要。极验初始化端点经常被中间层缓存,而缓存的响应会给你一个已被消费的挑战。
当它失败时
using System;
using CapSkip;
try
{
var result = await solver.GeetestAsync(gt, challenge, pageUrl);
}
catch (CapSkip.ValidationException) { /* missing gt or challenge */ }
catch (NetworkException) { /* CapSkip is not running */ }
catch (ApiException) { /* API error, often a dead challenge */ }
catch (CapSkip.TimeoutException) { /* exceeded recaptchaTimeout */ }
catch (CapSkipError) { /* anything else from the SDK */ }Qualify ValidationException 和 TimeoutException 连同 CapSkip 命名空间。这两个名称在 System中也存在,当两个命名空间都被导入时,未限定的 catch 会绑定到 System 类型并静默地永不触发。捕获基类 CapSkipError 可完全规避这个问题。
实际上,大多数极验失败会以 ApiException 形式出现,意味着挑战在识别完成前就失效了。修复方法是稍后再获取,而不是用相同的值重试。
代理与并发
极验是接受代理的三种类型之一,另外两种是 reCAPTCHA 和 Turnstile。图片验证码不接受,因为它们从不接触目标站点。
using System.Collections.Generic;
var result = await solver.GeetestAsync(gt, challenge, pageUrl,
new Dictionary<string, object?>
{
["proxy"] = new Proxy("HTTPS", "user:[email protected]:3128"),
});并行运行多个是可行的,但每个都需要自己新获取的参数对。不要预先获取一批挑战再一起识别,因为最后那些会在轮到它们之前就已过期。
// Correct: fetch and solve inside the same task.
var tasks = urls.Select(async url =>
{
var (gt, challenge) = await FetchPairAsync(url);
return await solver.GeetestAsync(gt, challenge, url);
});
var results = await Task.WhenAll(tasks);常见问题
为什么 result.Code 不是可用的 token?
因为极验的答案是三个值,而非一个。 代码 保留原始 JSON 字符串以求完整,而 SDK 将有用的部分展开到 Challenge, Validate 和 Seccode中。使用那三个。
我可以缓存挑战以省下一次请求吗?
不可以。它是一次性的,约一分钟后过期。缓存它是极验集成在测试中有效、在负载下失败的最常见原因,因为排队延迟会把识别推过过期窗口。 gt 值可以安全缓存。
这适用于极验 v4 吗?
加载的 GeetestAsync 方法针对 v3,即围绕 gt 和 challenge 参数对构建的滑块拼图版本。v4 改变了参数模型,因此在假设同一调用可用之前,请查看当前的 API 文档 以了解支持内容。
我需要代理吗?
仅当站点对地理位置敏感,或已将你的地址视为可疑时。使用代理时,请从同一网络路径识别并提交,否则不匹配本身可能触发再次挑战。
小结
获取 gt 和 challenge ,在识别前立即获取,用两者加上页面 URL 调用 GeetestAsync ,然后把 Challenge, Validate 和 Seccode 一起回传。将挑战视为易腐的,使用返回的 challenge 而非你的输入,并捕获 CapSkipError 以避免命名空间冲突。
其他语言的方法签名见 GeeTest 识别工具 页面,完整的 .NET 用法见 C# 验证码识别 页面,你还可以在我们的 GeeTest v3 演示上尝试一个实时拼图。CapSkip 在本地处理 验证码绕过 ,因此识别量不产生任何按次费用。
