Scarnox · Blog

Solver captcha in Java con HttpClient: esempio funzionante (2026)

Un solver captcha in Java è una sola chiamata HTTP. Java 11 e successivi includono java.net.http.HttpClient, quindi l'unica dipendenza di questa guida è Gson, per il JSON. Invii la sitekey e l'URL della pagina all'API di Scarnox, leggi solution.token e invii il token al form del sito. Questa guida copre PopularCaptcha, che Scarnox risolve oggi in modalità proxyless.

Inizia con crediti di prova gratuiti, senza carta di credito e prova il codice su una pagina che controlli prima di usarlo altrove.

Requisiti

La classe solver

La classe incapsula un HttpClient che mantiene i cookie, così una sessione può arrivare fino all'invio del form. Il metodo di risoluzione imposta l'header Bearer, invia il corpo JSON e controlla 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;
    }

    /** Restituisce il token. La chiamata blocca finché il token non è pronto. */
    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();
    }

    /** Invia il token al form del sito e restituisce lo stato HTTP. */
    public int submit(String pageUrl, String email, String token) throws Exception {
        // Carica prima la pagina, così viene impostato il cookie di sessione.
        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);
    }
}

L'header Authorization usa il prefisso Bearer. Il timeout della richiesta è di 185 secondi, perché l'API si blocca finché il token non è pronto. Non c'è alcun passaggio di polling.

Il programma principale

Il metodo main risolve con qualche retry, poi invia il 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);
    }
}

Eseguilo:

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

Perché è strutturata così

Un client per solver. HttpClient è thread-safe e pensato per essere riutilizzato. Crearne uno per richiesta spreca connessioni.

Cookie attivi. Il CookieManager sul client mantiene la sessione che ha caricato il form, così il token viaggia con il cookie che il sito si aspetta.

Controllo esplicito dell'errore. L'API restituisce errorId uguale a 0 in caso di successo. Qualsiasi altro valore include errorCode e errorDescription, e il solver lancia un'eccezione con quel testo. Il token è in solution.token.

Entrambi i campi captcha. Il form riceve h-captcha-response e g-recaptcha-response. La maggior parte dei siti legge uno dei due.

Tempistiche

Il token scade in circa due minuti. Risolvi subito prima dell'invio, non all'inizio di un job lungo. Scadenza del token captcha spiega come dimensionare la finestra e distinguere un token scaduto da uno rifiutato.

Widget invisibili

Passa true come terzo argomento a solve quando il widget è invisibile. La richiesta include allora is_invisible: true. Se la pagina richiede una callback JavaScript con il token, un client lato server non può attivarla, e uno strumento di automazione del browser è la scelta migliore. La guida sul captcha invisibile elenca i pattern.

Gestione degli errori

I retry coprono gli errori temporanei. Smetti di riprovare sugli errori che non cambieranno, come una sitekey sbagliata o un URL di pagina su cui il widget non gira, e registrali per una verifica. La politica completa, incluso quali codici fermano i tentativi, è in gestione degli errori del solver captcha.

Scraping responsabile

Se il programma fa scraping di pagine pubbliche, controlla prima robots.txt e i termini del sito. Aggiungi un ritardo tra le richieste e conserva solo i dati personali di cui hai bisogno. Per il quadro legale, vedi il web scraping è legale.

Prezzi

Paghi per ogni risoluzione riuscita, e le risoluzioni fallite sono gratuite. Consulta i prezzi attuali per le tariffe. Per iniziare, inizia con crediti di prova gratuiti, senza carta di credito.

Prossimi passi