Solver de captcha em Java com HttpClient: exemplo funcional (2026)
Um solver de captcha em Java é uma única chamada HTTP. Java 11 e versões posteriores trazem o java.net.http.HttpClient, então a única dependência deste guia é o Gson, para JSON. Você envia o sitekey e a URL da página para a API da Scarnox, lê solution.token e envia o token ao formulário do site. Este guia cobre o PopularCaptcha, que a Scarnox resolve hoje de forma proxyless.
Comece com crédito de teste grátis, sem cartão e teste o código em uma página que você controla antes de usá-lo em qualquer outro lugar.
Requisitos
- Java 17 ou mais recente (
java -version). - Gson 2.10 ou mais recente. No Maven:
com.google.code.gson:gson:2.10.1. - Uma chave de API da Scarnox, lida de uma variável de ambiente.
- O sitekey do widget PopularCaptcha da página. Para ajuda para encontrá-lo, veja como encontrar o sitekey de um captcha.
A classe solver
A classe encapsula um HttpClient que mantém cookies, para que a sessão possa seguir até o envio do formulário. O método de resolução define o cabeçalho Bearer, envia o corpo JSON e verifica 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;
}
/** Retorna o token. A chamada fica bloqueada até o token estar 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();
}
/** Envia o token ao formulário do site e retorna o status HTTP. */
public int submit(String pageUrl, String email, String token) throws Exception {
// Carregue a página primeiro para que o cookie de sessão seja definido.
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);
}
}
O cabeçalho Authorization usa o prefixo Bearer. O timeout da requisição é de 185 segundos, porque a API fica bloqueada até o token estar pronto. Não há etapa de polling.
O programa principal
O método main resolve com algumas retentativas e depois envia o 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);
}
}
Execute:
export SCARNOX_API_KEY=scarnox_YOURAPIKEY
javac -cp gson-2.10.1.jar *.java
java -cp .:gson-2.10.1.jar Main
Por que é estruturado assim
Um cliente por solver. O HttpClient é thread-safe e foi feito para ser reaproveitado. Criar um a cada requisição desperdiça conexões.
Cookies ativados. O CookieManager no cliente mantém a sessão que carregou o formulário, então o token é enviado junto com o cookie que o site espera.
Verificação explícita de erro. A API retorna errorId com 0 em caso de sucesso. Qualquer outro valor inclui errorCode e errorDescription, e o solver lança uma exceção com esse texto. O token fica em solution.token.
Os dois campos do captcha. O formulário recebe h-captcha-response e g-recaptcha-response. A maioria dos sites lê um ou outro.
Tempo
O token expira em cerca de dois minutos. Resolva logo antes do envio, e não no início de um job longo. Expiração de token de captcha explica como dimensionar essa janela e distinguir um token expirado de um token rejeitado.
Widgets invisíveis
Passe true como terceiro argumento de solve quando o widget for invisível. A requisição então inclui is_invisible: true. Se a página precisar de um callback em JavaScript com o token, um cliente do lado do servidor não consegue disparar esse callback, e uma ferramenta de automação de navegador é a escolha melhor. O guia captcha invisível lista os padrões.
Tratamento de erros
As retentativas cobrem falhas transitórias. Pare de repetir em erros que não vão mudar, como um sitekey errado ou uma URL de página em que o widget não roda, e registre-os para revisão. A política completa, incluindo quais códigos fazem parar, está em tratamento de erros do solver de captcha.
Faça scraping com responsabilidade
Se o programa faz scraping de páginas públicas, verifique antes o robots.txt e os termos do site. Adicione um intervalo entre as requisições e guarde apenas os dados pessoais de que você precisa. Para o contexto legal, veja web scraping é legal.
Preços
Você paga por resolução bem-sucedida, e resoluções com falha são gratuitas. Veja os preços atuais para as tarifas. Para começar, comece com crédito de teste grátis, sem cartão.