Captcha solver w C# z HttpClient: przykład dla .NET (2026)
W C# solver captcha to niewielka klasa opakowująca HttpClient. Wysyłasz sitekey i adres strony do API Scarnox, odczytujesz token z odpowiedzi JSON i przesyłasz go do formularza strony. Ten przewodnik buduje taką klasę dla .NET 8, dla PopularCaptcha, które Scarnox dziś rozwiązuje bez proxy.
Zacznij od darmowego kredytu próbnego, bez karty i wypróbuj kod na stronie, którą kontrolujesz, zanim uruchomisz go na czymkolwiek innym.
Wymagania
- SDK .NET 8 (
dotnet --version). - Klucz API Scarnox odczytywany ze zmiennej środowiskowej.
- Sitekey widżetu PopularCaptcha na stronie. Pomoc w jego znalezieniu znajdziesz w artykule jak znaleźć sitekey captcha.
Utwórz projekt
dotnet new console -n CaptchaDemo
cd CaptchaDemo
Nie potrzebujesz dodatkowych pakietów. System.Net.Http i System.Text.Json są częścią frameworka.
Klasa solvera
Poniższa klasa posiada jeden HttpClient z nagłówkiem Bearer i timeoutem 185 sekund. Zwraca token albo rzuca wyjątek z tekstem błędu z 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;
}
// Wywołanie zwraca wynik, gdy token jest gotowy. Nie ma odpytywania.
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; }
}
Kluczowa linia to sprawdzenie errorId. API zwraca 0 przy sukcesie. Każda inna wartość przychodzi z errorCode i errorDescription, a klasa przekazuje oba.
Prześlij token
Token trafia do formularza strony. Użyj drugiego HttpClient z kontenerem ciasteczek, aby sesja, która załadowała formularz, była ponownie użyta przy 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 = "";
// Ponawiaj przejściowe błędy. Nieudane rozwiązania nie są naliczane.
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) };
// Najpierw GET, aby serwis ustawił ciasteczko sesji.
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}");
Uruchom:
set SCARNOX_API_KEY=scarnox_YOURAPIKEY
dotnet run
W PowerShell użyj zamiast tego $env:SCARNOX_API_KEY = "scarnox_YOURAPIKEY".
Dlaczego jest zbudowane w ten sposób
**Jeden HttpClient na solver.** HttpClient ma być używany ponownie, a nie tworzony przy każdym wywołaniu. Solver posiada jeden i zwalnia go na końcu.
Timeout 185 sekund. API blokuje się, aż token będzie gotowy. Krótszy timeout anuluje rozwiązania, które właśnie miały się zakończyć.
Deserializacja z typami. ReadFromJsonAsync mapuje JSON na CreateTaskResponse, więc sprawdzenia błędu i tokenu czytają się jak zwykły kod C#.
Wysłanie formularza. Strona otrzymuje zarówno h-captcha-response, jak i g-recaptcha-response, ponieważ serwisy czytają jedno z nich. Kontener ciasteczek utrzymuje sesję między GET a POST.
Czas
Token wygasa po około dwóch minutach. Rozwiązuj tuż przed wysłaniem. Jeśli rozwiążesz wcześnie w partii, a wyślesz dużo później, token będzie nieaktualny. Wygasanie tokenu captcha omawia szczegóły.
Niewidoczne widżety
Przekaż invisible: true do SolveAsync, gdy widżet jest niewidoczny. Żądanie zawiera wtedy is_invisible: true. Niektóre strony wymagają wywołania zwrotnego JavaScript, którego klient po stronie serwera nie może uruchomić. W takim przypadku lepszym wyborem jest narzędzie do automatyzacji przeglądarki. Wzorce opisuje przewodnik o niewidocznej captcha.
Scrapuj odpowiedzialnie
Jeśli program scrapuje publiczne strony, najpierw sprawdź robots.txt i regulamin serwisu. Stosuj opóźnienie między żądaniami i zachowuj tylko dane osobowe, których potrzebujesz. Tło prawne znajdziesz w artykule czy web scraping jest legalny.
Cennik
Scarnox nalicza opłatę za każde udane rozwiązanie, a nieudane rozwiązania są bezpłatne. Stawki znajdziesz na stronie aktualnego cennika. Zacznij od darmowego kredytu próbnego, bez karty.