Scarnox · Blog

C#으로 만드는 캡차 솔버: HttpClient를 이용한 .NET 예제 (2026)

C#에서 캡차 솔버는 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 하나를 관리합니다. 토큰을 반환하거나, 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과 함께 오며, 이 클래스는 둘 다 드러냅니다.

토큰 제출하기

토큰은 사이트의 폼으로 보냅니다. 쿠키 컨테이너가 있는 두 번째 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 요청을 보냅니다.
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는 토큰이 준비될 때까지 블록됩니다. 더 짧은 타임아웃은 곧 끝날 풀이를 취소해 버립니다.

타입이 있는 역직렬화. ReadFromJsonAsync가 JSON을 CreateTaskResponse에 매핑하므로, 오류와 토큰 검사가 일반적인 C# 코드처럼 읽힙니다.

폼 제출. 사이트는 h-captcha-response와 g-recaptcha-response를 둘 다 받습니다. 사이트마다 둘 중 하나를 읽기 때문입니다. 쿠키 컨테이너는 GET과 POST 사이의 세션을 유지합니다.

타이밍

토큰은 약 2분 뒤에 만료됩니다. 제출 직전에 풀이하세요. 배치 작업에서 일찍 풀고 한참 뒤에 제출하면 토큰이 오래되어 버립니다. 자세한 내용은 캡차 토큰 만료에서 다룹니다.

비가시형 위젯

위젯이 보이지 않는 형태라면 SolveAsync에 invisible: true를 전달하세요. 그러면 요청에 is_invisible: true가 포함됩니다. 일부 페이지는 토큰을 받아 실행해야 하는 JavaScript 콜백을 필요로 하는데, 서버 측 클라이언트로는 이를 트리거할 수 없습니다. 이때는 브라우저 자동화 도구가 더 나은 선택입니다. 패턴은 비가시형 캡차 가이드에서 다룹니다.

스크래핑은 책임감 있게

프로그램이 공개 페이지를 스크래핑한다면, 먼저 robots.txt와 사이트 약관을 확인하세요. 요청 사이에 지연을 두고, 필요한 개인 데이터만 보관하세요. 법적 배경은 웹 스크래핑은 합법인가를 참고하세요.

요금

Scarnox는 성공한 풀이 건당 과금하며, 실패한 풀이는 청구되지 않습니다. 요금은 현재 요금 페이지를 참고하세요. 무료 체험 크레딧으로 시작하세요. 카드 등록은 필요 없습니다.

다음 단계