C# 使用 HttpClient 实现验证码求解器:.NET 示例(2026)
在 C# 中,验证码求解器就是一个围绕 HttpClient 的小类。你把 sitekey 和页面 URL 发送到 Scarnox API,从 JSON 响应中读取 token,然后把它提交到网站的表单中。本指南在 .NET 8 中构建这样一个类,针对的是 PopularCaptcha,Scarnox 目前以无代理方式支持该类型的求解。
先获取免费试用额度,无需信用卡,并在你自己控制的页面上试用这段代码,然后再把它用在其他任何地方。
环境要求
- .NET 8 SDK(使用
dotnet --version检查)。 - 一个 Scarnox API 密钥,从环境变量中读取。
- 页面 PopularCaptcha 组件的 sitekey。关于如何找到它,请参阅如何找到验证码 sitekey。
创建项目
dotnet new console -n CaptchaDemo
cd CaptchaDemo
无需安装额外的包。System.Net.Http 和 System.Text.Json 都是框架自带的。
求解器类
下面的类持有一个带有 Bearer 请求头、超时时间为 185 秒的 HttpClient。求解成功时返回 token,失败时则抛出包含 API 错误文本的异常:
// ScarnoxSolver.cs
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
public sealed class ScarnoxSolver : IDisposable
{
private const string ApiUrl = "https://api.scarnox.com/api/tasks/create";
private readonly HttpClient _http;
public ScarnoxSolver(string apiKey)
{
_http = new HttpClient { Timeout = TimeSpan.FromSeconds(185) };
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
}
public async Task<string> SolveAsync(
string sitekey,
string pageUrl,
bool invisible = false,
CancellationToken ct = default)
{
var body = new Dictionary<string, object>
{
["captcha_type"] = "PopularCaptchaTaskProxyless",
["site_key"] = sitekey,
["site_url"] = pageUrl,
};
if (invisible)
{
body["is_invisible"] = true;
}
// 调用会在 token 准备好后返回,无需轮询。
using var resp = await _http.PostAsJsonAsync(ApiUrl, body, ct);
var data = await resp.Content.ReadFromJsonAsync<CreateTaskResponse>(cancellationToken: ct)
?? throw new InvalidOperationException(
$"HTTP {(int)resp.StatusCode}: empty response");
if (data.ErrorId != 0)
{
throw new InvalidOperationException($"{data.ErrorCode}: {data.ErrorDescription}");
}
return data.Solution?.Token
?? throw new InvalidOperationException("Response had no token");
}
public void Dispose() => _http.Dispose();
}
public sealed class CreateTaskResponse
{
[JsonPropertyName("errorId")]
public int ErrorId { get; set; }
[JsonPropertyName("errorCode")]
public string? ErrorCode { get; set; }
[JsonPropertyName("errorDescription")]
public string? ErrorDescription { get; set; }
[JsonPropertyName("solution")]
public Solution? Solution { get; set; }
}
public sealed class Solution
{
[JsonPropertyName("token")]
public string? Token { get; set; }
}
errorId 检查是最重要的一行。API 成功时返回 0。其他任何值都会附带 errorCode 和 errorDescription,该类会将两者都抛出。
提交 token
token 要提交到网站的表单中。使用第二个带有 cookie 容器的 HttpClient,使加载表单时所用的会话能够在 POST 中被复用:
// Program.cs
using System.Net;
var apiKey = Environment.GetEnvironmentVariable("SCARNOX_API_KEY")
?? throw new InvalidOperationException("Set SCARNOX_API_KEY");
const string pageUrl = "https://example.com/signup";
const string sitekey = "YOUR-SITEKEY-UUID";
using var solver = new ScarnoxSolver(apiKey);
string token = "";
// 重试临时性失败。失败的求解不会被计费。
for (int attempt = 1; attempt <= 3; attempt++)
{
try
{
token = await solver.SolveAsync(sitekey, pageUrl);
break;
}
catch (InvalidOperationException ex) when (attempt < 3)
{
Console.Error.WriteLine($"Attempt {attempt} failed: {ex.Message}");
await Task.Delay(TimeSpan.FromSeconds(attempt));
}
}
var cookies = new CookieContainer();
var handler = new HttpClientHandler { CookieContainer = cookies, UseCookies = true };
using var site = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(60) };
// 先发起 GET 请求,让网站设置其会话 cookie。
await site.GetAsync(pageUrl);
using var form = new FormUrlEncodedContent(new Dictionary<string, string>
{
["email"] = "you@example.com",
["h-captcha-response"] = token,
["g-recaptcha-response"] = token,
});
using var response = await site.PostAsync(pageUrl, form);
Console.WriteLine($"Status: {(int)response.StatusCode}");
运行方式:
set SCARNOX_API_KEY=scarnox_YOURAPIKEY
dotnet run
在 PowerShell 中请改用 $env:SCARNOX_API_KEY = "scarnox_YOURAPIKEY"。
为什么这样组织代码
**每个求解器使用一个 HttpClient。** HttpClient 的设计初衷是复用,而不是为每次调用都新建一个。求解器持有一个实例,并在结束时将其释放。
185 秒超时。 API 会阻塞直到 token 准备好。超时设置得更短,会取消本来即将完成的求解。
强类型反序列化。 ReadFromJsonAsync 将 JSON 映射到 CreateTaskResponse,因此错误和 token 的检查读起来就像普通的 C# 代码。
表单提交。 网站会同时收到 h-captcha-response 和 g-recaptcha-response,因为网站会读取其中一个。cookie 容器则在 GET 和 POST 之间保持会话。
时机
token 约在两分钟内过期。请在提交前立即求解。如果你在批处理中提前求解,却在很久之后才提交,token 就已经失效了。验证码 token 过期一文介绍了详细情况。
无感验证码组件
当组件为无感模式时,将 invisible: true 传给 SolveAsync。请求中就会包含 is_invisible: true。有些页面需要 JavaScript 回调,而服务端客户端无法触发该回调。在这种情况下,浏览器自动化工具是更好的选择。无感验证码指南介绍了相关模式。
负责任地抓取
如果该程序抓取的是公开页面,请先检查 robots.txt 和网站的服务条款。请求之间设置延迟,并只保留你需要的个人数据。关于法律背景,请参阅网页抓取是否合法。
价格
Scarnox 按每次成功的求解计费,失败的求解不收费。费率请查看当前定价。先获取免费试用额度,无需信用卡。