Java 使用 HttpClient 实现验证码求解器:可运行示例(2026)
在 Java 中,验证码求解就是一次 HTTP 调用。Java 11 及更新版本内置了 java.net.http.HttpClient,因此本指南唯一需要的依赖是用于处理 JSON 的 Gson。你把 sitekey 和页面 URL 发送到 Scarnox API,读取 solution.token,然后将 token 提交到网站的表单中。本指南针对 PopularCaptcha,Scarnox 目前以无代理方式支持该类型的求解。
先获取免费试用额度,无需信用卡,并在你自己的页面上试用这段代码,然后再把它用在其他任何地方。
环境要求
- Java 17 或更新版本(使用
java -version检查)。 - Gson 2.10 或更新版本。在 Maven 中为
com.google.code.gson:gson:2.10.1。 - 一个 Scarnox API 密钥,从环境变量中读取。
- 页面 PopularCaptcha 组件的 sitekey。如果你需要帮助找到它,请参阅如何找到验证码 sitekey。
求解器类
该类封装了一个保存 cookie 的 HttpClient,使会话能够在表单提交时延续。求解方法设置 Bearer 请求头、发送 JSON 请求体,并检查 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;
}
/** 返回 token。调用会阻塞直到 token 准备好。 */
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();
}
/** 将 token 提交到网站的表单,并返回 HTTP 状态码。 */
public int submit(String pageUrl, String email, String token) throws Exception {
// 先加载页面,以便设置会话 cookie。
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);
}
}
Authorization 请求头使用 Bearer 前缀。请求超时为 185 秒,因为 API 会阻塞直到 token 准备好。不存在轮询步骤。
主程序
main 方法会进行几次重试求解,然后提交 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);
}
}
运行方式:
export SCARNOX_API_KEY=scarnox_YOURAPIKEY
javac -cp gson-2.10.1.jar *.java
java -cp .:gson-2.10.1.jar Main
为什么这样组织代码
每个求解器使用一个客户端。 HttpClient 是线程安全的,设计上就是用来复用的。每次请求都新建一个,只会浪费连接。
开启 cookie。 客户端上的 CookieManager 会保留加载表单时建立的会话,因此 token 会连同网站期望的 cookie 一起发送。
显式的错误检查。 API 成功时 errorId 为 0。其他任何值都会附带 errorCode 和 errorDescription,求解器会以该文本抛出异常。token 位于 solution.token。
两个验证码字段。 表单会同时收到 h-captcha-response 和 g-recaptcha-response。大多数网站会读取其中一个。
时机
token 约在两分钟内过期。请在提交前立即求解,而不是在长时间任务开始时求解。验证码 token 过期一文说明了如何估算时间窗口,以及如何区分过期的 token 与被拒绝的 token。
无感验证码组件
当组件为无感模式时,将 solve 的第三个参数设为 true。请求中就会包含 is_invisible: true。如果页面需要以 token 为参数的 JavaScript 回调,服务端客户端无法触发它,此时浏览器自动化工具是更好的选择。无感验证码指南列出了相关模式。
错误处理
重试用于应对临时性失败。对于不会自行改变的错误,例如 sitekey 错误,或者组件并不在其上运行的页面 URL,应停止重试,并记录下来以便复查。完整的策略,包括哪些错误码应当停止重试,见验证码求解器错误处理。
负责任地抓取
如果该程序抓取的是公开页面,请先检查 robots.txt 和网站的服务条款。请求之间添加延迟,并只保留你需要的个人数据。关于法律背景,请参阅网页抓取是否合法。
价格
你按每次成功的求解付费,失败的求解不收费。费率请查看当前定价。要开始使用,请先获取免费试用额度,无需信用卡。