Scarnox · Blog

C# 使用 HttpClient 实现验证码求解器:.NET 示例(2026)

在 C# 中,验证码求解器就是一个围绕 HttpClient 的小类。你把 sitekey 和页面 URL 发送到 Scarnox API,从 JSON 响应中读取 token,然后把它提交到网站的表单中。本指南在 .NET 8 中构建这样一个类,针对的是 PopularCaptcha,Scarnox 目前以无代理方式支持该类型的求解。

先获取免费试用额度,无需信用卡,并在你自己控制的页面上试用这段代码,然后再把它用在其他任何地方。

环境要求

创建项目

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 按每次成功的求解计费,失败的求解不收费。费率请查看当前定价。先获取免费试用额度,无需信用卡。

下一步