Scarnox · Blog

Captcha-solver in Java met HttpClient: werkend voorbeeld (2026)

Een captcha-solver in Java is één HTTP-aanroep. Java 11 en nieuwer bevatten java.net.http.HttpClient, dus de enige afhankelijkheid die deze gids nodig heeft is Gson, voor JSON. Je stuurt de sitekey en de paginaURL naar de Scarnox-API, leest solution.token, en dient het token in bij het formulier van de site. Deze gids gaat over PopularCaptcha, dat Scarnox vandaag zonder proxy oplost.

Begin met gratis proefcredit, zonder creditcard en probeer de code op een pagina die je beheert voordat je hem ergens anders gebruikt.

Vereisten

De solverklasse

De klasse omhult één HttpClient die cookies bewaart, zodat een sessie door het verzenden van het formulier heen kan blijven gaan. De solve-methode zet de Bearer-header, stuurt de JSON-body en controleert 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;
    }

    /** Geeft het token terug. De aanroep blokkeert totdat het token klaar is. */
    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();
    }

    /** Plaatst het token in het formulier van de site en geeft de HTTP-status terug. */
    public int submit(String pageUrl, String email, String token) throws Exception {
        // Laad eerst de pagina, zodat de sessiecookie wordt ingesteld.
        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);
    }
}

De Authorization-header gebruikt het voorvoegsel Bearer. De time-out van het verzoek is 185 seconden, omdat de API blokkeert totdat het token klaar is. Er is geen polling-stap.

Het hoofdprogramma

De main-methode lost op met een paar nieuwe pogingen en dient daarna het token in:

// 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);
    }
}

Uitvoeren:

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

Waarom het zo is opgezet

Eén client per solver. HttpClient is thread-safe en bedoeld om hergebruikt te worden. Er één per verzoek maken verspilt verbindingen.

Cookies aan. De CookieManager op de client bewaart de sessie die het formulier heeft geladen, zodat het token wordt verstuurd met de cookie die de site verwacht.

Expliciete foutcontrole. De API geeft errorId als 0 bij succes. Elke andere waarde bevat errorCode en errorDescription, en de solver gooit een uitzondering met die tekst. Het token staat in solution.token.

Beide captchavelden. Het formulier krijgt h-captcha-response en g-recaptcha-response. De meeste sites lezen het ene of het andere.

Timing

Het token verloopt na ongeveer twee minuten. Los op vlak voor het verzenden, niet aan het begin van een lange job. Verlopen captchatokens legt uit hoe je het tijdvenster inschat en hoe je een verlopen token van een afgewezen token onderscheidt.

Onzichtbare widgets

Geef true mee als derde argument aan solve wanneer de widget onzichtbaar is. Het verzoek bevat dan is_invisible: true. Als de pagina een JavaScript-callback met het token nodig heeft, kan een server-side client die niet activeren, en is een browserautomatiseringstool de betere keuze. De gids over onzichtbare captcha somt de patronen op.

Foutafhandeling

Nieuwe pogingen dekken tijdelijke fouten. Stop met opnieuw proberen bij fouten die niet zullen veranderen, zoals een verkeerde sitekey of een paginaURL waarop de widget niet draait, en log ze voor beoordeling. Het volledige beleid, inclusief welke codes je moet laten stoppen, staat in foutafhandeling van de captcha-solver.

Verantwoord scrapen

Als het programma publieke pagina's scrapet, controleer dan eerst robots.txt en de voorwaarden van de site. Voeg een vertraging toe tussen verzoeken en houd alleen de persoonsgegevens die je nodig hebt. Voor de juridische achtergrond, zie is webscraping legaal.

Prijzen

Je betaalt per geslaagde oplossing, en mislukte oplossingen zijn gratis. Zie actuele prijzen voor de tarieven. Om te beginnen, begin met gratis proefcredit, zonder creditcard.

Volgende stappen