Solveur de captcha en Java avec HttpClient : exemple complet (2026)
Un solveur de captcha en Java tient en un seul appel HTTP. Java 11 et les versions suivantes fournissent java.net.http.HttpClient, donc la seule dépendance dont ce guide a besoin est Gson, pour le JSON. Vous envoyez la sitekey et l'URL de la page à l'API Scarnox, lisez solution.token, et soumettez le token au formulaire du site. Ce guide traite de PopularCaptcha, que Scarnox résout aujourd'hui sans proxy.
Démarrez avec un crédit d'essai gratuit, sans carte bancaire et testez le code sur une page que vous contrôlez avant de l'utiliser ailleurs.
Prérequis
- Java 17 ou plus récent (
java -version). - Gson 2.10 ou plus récent. Avec Maven :
com.google.code.gson:gson:2.10.1. - Une clé d'API Scarnox, lue depuis une variable d'environnement.
- La sitekey du widget PopularCaptcha de la page. Pour savoir comment la trouver, consultez comment trouver la sitekey d'un captcha.
La classe du solveur
La classe encapsule un HttpClient qui conserve les cookies, pour qu'une session puisse traverser la soumission du formulaire. La méthode de résolution définit l'en-tête Bearer, envoie le corps JSON et vérifie 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;
}
/** Renvoie le token. L'appel bloque jusqu'à ce que le token soit prêt. */
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();
}
/** Envoie le token au formulaire du site et renvoie le statut HTTP. */
public int submit(String pageUrl, String email, String token) throws Exception {
// Charger d'abord la page pour que le cookie de session soit défini.
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'en-tête Authorization utilise le préfixe Bearer. Le délai d'attente de la requête est de 185 secondes, car l'API bloque jusqu'à ce que le token soit prêt. Il n'y a pas d'étape de polling.
Le programme principal
La méthode main résout avec quelques réessais, puis soumet le 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);
}
}
Exécutez-le :
export SCARNOX_API_KEY=scarnox_YOURAPIKEY
javac -cp gson-2.10.1.jar *.java
java -cp .:gson-2.10.1.jar Main
Pourquoi cette structure
Un client par solveur. HttpClient est thread-safe et conçu pour être réutilisé. En créer un par requête gaspille les connexions.
Cookies activés. Le CookieManager du client conserve la session qui a chargé le formulaire, pour que le token parte avec le cookie attendu par le site.
Vérification explicite de l'erreur. L'API renvoie errorId à 0 en cas de succès. Toute autre valeur s'accompagne de errorCode et errorDescription, et le solveur lève une exception avec ce texte. Le token se trouve dans solution.token.
Les deux champs de captcha. Le formulaire reçoit h-captcha-response et g-recaptcha-response. La plupart des sites lisent l'un ou l'autre.
Timing
Le token expire en environ deux minutes. Résolvez juste avant la soumission, et non au début d'un long job. L'expiration des tokens de captcha explique comment dimensionner la fenêtre et distinguer un token expiré d'un token rejeté.
Widgets invisibles
Passez true comme troisième argument de solve lorsque le widget est invisible. La requête inclut alors is_invisible: true. Si la page a besoin d'un callback JavaScript avec le token, un client côté serveur ne peut pas le déclencher, et un outil d'automatisation de navigateur est le meilleur choix. Le guide sur le captcha invisible présente les schémas à suivre.
Gestion des erreurs
Les réessais couvrent les échecs transitoires. Arrêtez de relancer les erreurs qui ne changeront pas, comme une sitekey erronée ou une URL de page sur laquelle le widget ne fonctionne pas, et journalisez-les pour examen. La politique complète, y compris les codes qui doivent arrêter les réessais, est dans la gestion des erreurs du solveur de captcha.
Scraper de manière responsable
Si le programme scrape des pages publiques, consultez d'abord le robots.txt et les conditions du site. Ajoutez un délai entre les requêtes et ne conservez que les données personnelles dont vous avez besoin. Pour le cadre juridique, consultez le web scraping est-il légal.
Tarifs
Vous payez par résolution réussie, et les résolutions échouées sont gratuites. Consultez les tarifs actuels pour les montants. Pour commencer, démarrez avec un crédit d'essai gratuit, sans carte bancaire.