Scarnox · Blog

Solver captcha w Javie z HttpClient: działający przykład (2026)

Solver captcha w Javie to jedno wywołanie HTTP. Java 11 i nowsze zawierają java.net.http.HttpClient, więc jedyną zależnością tego przewodnika jest Gson do obsługi JSON. Wysyłasz sitekey i adres strony do API Scarnox, odczytujesz solution.token i przesyłasz token do formularza strony. Ten przewodnik dotyczy 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 użyjesz go gdziekolwiek indziej.

Wymagania

Klasa solvera

Klasa opakowuje jeden HttpClient, który przechowuje ciasteczka, dzięki czemu sesja może przetrwać do wysłania formularza. Metoda solve ustawia nagłówek Bearer, wysyła treść JSON i sprawdza errorId:

// ScarnoxSolver.java
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;

public class ScarnoxSolver {
    private static final String API_URL = "https://api.scarnox.com/api/tasks/create";
    private static final Gson GSON = new Gson();

    private final String apiKey;
    private final HttpClient http = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ALL))
            .build();

    public ScarnoxSolver(String apiKey) {
        this.apiKey = apiKey;
    }

    /** Zwraca token. Wywołanie blokuje się, aż token będzie gotowy. */
    public String solve(String sitekey, String pageUrl, boolean invisible) throws Exception {
        Map<String, Object> body = new LinkedHashMap<>();
        body.put("captcha_type", "PopularCaptchaTaskProxyless");
        body.put("site_key", sitekey);
        body.put("site_url", pageUrl);
        if (invisible) {
            body.put("is_invisible", true);
        }

        HttpRequest request = HttpRequest.newBuilder(URI.create(API_URL))
                .timeout(Duration.ofSeconds(185))
                .header("Authorization", "Bearer " + apiKey)
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(GSON.toJson(body)))
                .build();

        HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());

        JsonObject data;
        try {
            data = JsonParser.parseString(response.body()).getAsJsonObject();
        } catch (RuntimeException e) {
            throw new IllegalStateException("HTTP " + response.statusCode() + ": not JSON");
        }

        if (data.get("errorId").getAsInt() != 0) {
            throw new IllegalStateException(
                    data.get("errorCode").getAsString() + ": "
                    + data.get("errorDescription").getAsString());
        }

        return data.getAsJsonObject("solution").get("token").getAsString();
    }

    /** Wysyła token do formularza strony i zwraca status HTTP. */
    public int submit(String pageUrl, String email, String token) throws Exception {
        // Najpierw załaduj stronę, aby ustawić ciasteczko sesji.
        http.send(HttpRequest.newBuilder(URI.create(pageUrl)).GET().build(),
                HttpResponse.BodyHandlers.discarding());

        String form = "email=" + enc(email)
                + "&h-captcha-response=" + enc(token)
                + "&g-recaptcha-response=" + enc(token);

        HttpRequest request = HttpRequest.newBuilder(URI.create(pageUrl))
                .header("Content-Type", "application/x-www-form-urlencoded")
                .POST(HttpRequest.BodyPublishers.ofString(form))
                .build();

        return http.send(request, HttpResponse.BodyHandlers.discarding()).statusCode();
    }

    private static String enc(String value) {
        return URLEncoder.encode(value, StandardCharsets.UTF_8);
    }
}

Nagłówek Authorization używa prefiksu Bearer. Timeout żądania wynosi 185 sekund, bo API blokuje się, aż token będzie gotowy. Nie ma etapu odpytywania.

Główny program

Metoda main rozwiązuje captcha z kilkoma próbami, a następnie wysyła token:

// Main.java
public class Main {
    public static void main(String[] args) throws Exception {
        String apiKey = System.getenv("SCARNOX_API_KEY");
        if (apiKey == null || apiKey.isBlank()) {
            throw new IllegalStateException("Set SCARNOX_API_KEY");
        }

        String pageUrl = "https://example.com/signup";
        String sitekey = "YOUR-SITEKEY-UUID";
        ScarnoxSolver solver = new ScarnoxSolver(apiKey);

        String token = null;
        for (int attempt = 1; attempt <= 3 && token == null; attempt++) {
            try {
                token = solver.solve(sitekey, pageUrl, false);
            } catch (IllegalStateException e) {
                System.err.println("Attempt " + attempt + " failed: " + e.getMessage());
                Thread.sleep(attempt * 1000L);
            }
        }
        if (token == null) {
            throw new IllegalStateException("No token after 3 attempts");
        }

        int status = solver.submit(pageUrl, "you@example.com", token);
        System.out.println("Status: " + status);
    }
}

Uruchom:

export SCARNOX_API_KEY=scarnox_YOURAPIKEY
javac -cp gson-2.10.1.jar *.java
java -cp .:gson-2.10.1.jar Main

Dlaczego jest zbudowane w ten sposób

Jeden klient na solver. HttpClient jest bezpieczny wątkowo i ma być używany ponownie. Tworzenie go przy każdym żądaniu marnuje połączenia.

Ciasteczka włączone. CookieManager na kliencie zachowuje sesję, która załadowała formularz, dzięki czemu token wysyłany jest z ciasteczkiem, którego oczekuje serwis.

Jawne sprawdzenie błędu. API zwraca errorId równe 0 przy sukcesie. Każda inna wartość zawiera errorCode i errorDescription, a solver rzuca wyjątek z tym tekstem. Token znajduje się w solution.token.

Oba pola captcha. Formularz dostaje h-captcha-response i g-recaptcha-response. Większość serwisów czyta jedno z nich.

Czas

Token wygasa po około dwóch minutach. Rozwiązuj tuż przed wysłaniem, a nie na początku długiego zadania. Wygasanie tokenu captcha wyjaśnia, jak dobrać okno czasowe i odróżnić token wygasły od odrzuconego.

Niewidoczne widżety

Przekaż true jako trzeci argument do solve, gdy widżet jest niewidoczny. Żądanie zawiera wtedy is_invisible: true. Jeśli strona wymaga wywołania zwrotnego JavaScript z tokenem, klient po stronie serwera nie może go uruchomić i lepszym wyborem jest narzędzie do automatyzacji przeglądarki. Wzorce opisuje przewodnik o niewidocznej captcha.

Obsługa błędów

Ponawianie obejmuje przejściowe błędy. Przestań ponawiać przy błędach, które się nie zmienią, na przykład przy złym sitekey lub adresie strony, na której widżet nie działa, i zapisz je do przeglądu. Pełna polityka, w tym kody, przy których należy się zatrzymać, znajduje się w artykule obsługa błędów solvera captcha.

Scrapuj odpowiedzialnie

Jeśli program scrapuje publiczne strony, najpierw sprawdź robots.txt i regulamin serwisu. Dodaj 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

Płacisz za każde udane rozwiązanie, a nieudane rozwiązania są bezpłatne. Stawki znajdziesz na stronie cennika. Aby zacząć, zacznij od darmowego kredytu próbnego, bez karty.

Następne kroki