Scarnox · Blog

C#でcaptcha解決:HttpClientを使った.NETの例(2026年)

C#では、captchaソルバーはHttpClientをラップした小さなクラスです。Scarnox APIにサイトキーとページURLをPOSTし、JSONレスポンスからトークンを読み取り、サイトのフォームに送信します。このガイドでは、.NET 8向けに、Scarnoxが現在プロキシなしで解決しているPopularCaptchaのクラスを作ります。

無料のトライアルクレジットで始める(カード登録は不要)と、まず自分が管理しているページでコードを試せます。他のものに使う前に、ぜひそうしてください。

必要なもの

プロジェクトを作る

dotnet new console -n CaptchaDemo
cd CaptchaDemo

追加のパッケージは不要です。System.Net.HttpとSystem.Text.Jsonはフレームワークに含まれています。

ソルバークラス

以下のクラスは、Bearerヘッダーと185秒のタイムアウトを持つHttpClientを1つ保持します。トークンを返すか、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;
        }

        // トークンの準備ができると戻る。ポーリングは不要。
        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が付くので、このクラスはその両方を表に出します。

トークンを送信する

トークンはサイトのフォームに送ります。フォームを読み込んだセッションをPOSTでも再利用するため、Cookieコンテナを持つ2つ目のHttpClientを使います。

// 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を1つ。** HttpClientは呼び出しごとに作るのではなく、再利用するためのものです。ソルバーが1つ持ち、終了時に破棄します。

185秒のタイムアウト。 APIはトークンの準備ができるまでブロックします。短いタイムアウトにすると、もうすぐ完了する解決をキャンセルしてしまいます。

型付きのデシリアライズ。 ReadFromJsonAsyncがJSONをCreateTaskResponseにマッピングするので、エラーとトークンのチェックが普通のC#のコードのように読めます。

フォーム送信。 サイトはh-captcha-responseとg-recaptcha-responseの両方を受け取ります。サイトによってどちらかを読むためです。Cookieコンテナは、GETとPOSTの間でセッションを維持します。

タイミング

トークンは約2分で期限切れになります。送信の直前に解決してください。バッチの中で早めに解決して、送信がずっと後になると、トークンは古くなっています。captchaトークンの期限切れで詳しく説明しています。

不可視のウィジェット

ウィジェットが不可視の場合は、SolveAsyncにinvisible: trueを渡してください。リクエストにはis_invisible: trueが含まれます。ページによっては、サーバー側のクライアントではトリガーできないJavaScriptのコールバックが必要です。その場合は、ブラウザの自動化ツールのほうが適しています。パターンは不可視captchaのガイドで扱っています。

責任あるスクレイピング

プログラムが公開ページをスクレイピングするなら、まずrobots.txtとサイトの利用規約を確認してください。リクエストの間には遅延を入れ、必要な個人データだけを保持してください。法的な背景はWebスクレイピングは合法かを参照してください。

料金

Scarnoxは成功した解決ごとに課金します。失敗した解決は無料です。料金の詳細は料金ページを参照してください。無料のトライアルクレジットで始める(カード登録は不要)こともできます。

次のステップ