在 Java 中将 CAPTCHA API 与 Selenium 配合使用
先请求令牌,然后在导航之前为 Chrome 配置相同的代理以及返回的 userAgent 和 secChUa。将 solution.token 放入应用的 CAPTCHA 字段,提交表单,并等待应用进入成功状态,而不只是等待任务就绪。
示例使用 Java REST 辅助类和 Selenium ChromeDriver,不涉及自动 CAPTCHA 检测或浏览器插件。
本页内容
准备 Java 项目和目标站点
- 1.
配置项目
使用 Java 21 或更高版本。在 Maven 项目中,将 Java 编译级别设为 21,并添加 Gson 2.15.0 和
org.seleniumhq.selenium:selenium-java:4.52.0。按照 Selenium Chrome 文档配置 Chrome 和 ChromeDriver。 - 2.
保存两个 Java 文件
将第一个示例保存为
src/main/java/Solve.java,第二个保存为src/main/java/BrowserSolve.java。将它们放在同一个包中;提供的文件没有包声明。BrowserSolve调用的是Solve.api,而不是Solve.main。 - 3.
启用站点配置文件
在 solvethree 控制台中设置目标主机名,并确保其配置文件已为工作区激活。发送主机名和操作不会创建配置文件。请参阅站点配置文件设置。
- 4.
设置环境
在将运行
BrowserSolve的进程中配置以下三个环境变量。将工作区 API 密钥保留在受信任的 Java 进程中,不要放入页面或浏览器 JavaScript。
| 变量 | 值 |
|---|---|
SOLVETHREE_API_KEY | 工作区 API 密钥。 |
SOLVETHREE_PROXY | 采用 IP 白名单的 HTTP 代理 URL,不含嵌入的用户名或密码、路径、查询参数或片段。 |
TARGET_URL | 包含表单的应用页面的完整 URL。 |
此示例需要采用 IP 白名单的代理,因为它不支持通过 Chrome 的代理 URL 传递代理凭据。任务 API 和 Chrome 使用相同的代理值。
添加 Solve.java 以发送 API 请求
Solve.api 向 https://api.solvethree.com/v1 发送经过身份验证的请求,在解析成功响应之前检查 HTTP 状态,并将每个请求的时限限制为本地截止时间的剩余时间。
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.UUID;
public class Solve {
static final HttpClient HTTP = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(15)).build();
static final String KEY = System.getenv("SOLVETHREE_API_KEY");
static JsonObject api(String path, JsonObject body, long deadline) throws Exception {
long remaining = deadline - System.nanoTime();
if (remaining <= 0) throw new IllegalStateException("Keep the task ID and resume polling");
var builder = HttpRequest.newBuilder(URI.create("https://api.solvethree.com/v1" + path))
.timeout(Duration.ofNanos(Math.min(remaining, Duration.ofSeconds(30).toNanos())))
.header("Authorization", "Bearer " + KEY)
.header("Content-Type", "application/json");
if (body == null) builder.GET();
else builder.POST(HttpRequest.BodyPublishers.ofString(body.toString()));
var response = HTTP.send(builder.build(), HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("API HTTP " + response.statusCode());
return JsonParser.parseString(response.body()).getAsJsonObject();
}
public static void main(String[] args) throws Exception {
String proxy = System.getenv("SOLVETHREE_PROXY");
if (KEY == null || proxy == null) throw new IllegalArgumentException("Set the API key and proxy");
var input = new JsonObject();
input.addProperty("site", "accounts.example.com");
input.addProperty("action", "login");
input.addProperty("proxy", proxy);
input.addProperty("requestId", UUID.randomUUID().toString());
// Save input before creation if the task must be recovered after restart.
long deadline = System.nanoTime() + Duration.ofSeconds(180).toNanos();
var task = api("/tasks?waitMs=0", input, deadline);
while (task.get("status").getAsString().equals("pending") || task.get("status").getAsString().equals("solving")) {
String id = URLEncoder.encode(task.get("id").getAsString(), StandardCharsets.UTF_8);
task = api("/tasks/" + id + "?waitMs=15000", null, deadline);
if (task.get("status").getAsString().equals("pending") || task.get("status").getAsString().equals("solving"))
Thread.sleep(1000);
}
if (!task.get("status").getAsString().equals("ready"))
throw new IllegalStateException("Task failed: " + task.get("errorCode")
+ ": " + task.get("error"));
var solution = task.getAsJsonObject("solution");
// Submit solution.token with userAgent/secChUa and the original proxy.
System.out.println("Ready task: " + task.get("id").getAsString());
}
}独立的 main 方法演示了任务创建和轮询。其中的 accounts.example.com 主机名和 login 操作是占位值。对于 Selenium 集成,请将此文件用作辅助类,并改为运行 BrowserSolve;运行两个 main 方法会创建两个独立任务。
reCAPTCHA 请求需要在 site 中提供主机名、提供应用的 action,并且必须且只能提供 proxy 或 proxyCandidates 其中之一。不要添加 type 或 url。省略 recaptchaType 会选择 enterprise-v3,这些示例使用的正是此类型。
使用就绪解决方案的身份启动 Chrome
BrowserSolve 从 TARGET_URL 推导出 site,创建任务,并在状态为 pending 或 solving 时持续轮询。只有 ready 任务才会提供浏览器使用的解决方案。
// Save as BrowserSolve.java beside Solve.java from the Java REST quickstart.
// Add Selenium Java 4.52.0 and Gson 2.15.0 to your Maven dependencies; Java 21+.
import com.google.gson.JsonObject;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Map;
import java.util.UUID;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.WebDriverWait;
public class BrowserSolve {
public static void main(String[] args) throws Exception {
String target = System.getenv("TARGET_URL");
String proxy = System.getenv("SOLVETHREE_PROXY");
URI proxyUri = URI.create(proxy);
if (proxyUri.getUserInfo() != null) throw new IllegalArgumentException("Use an IP-allowlisted proxy");
var input = new JsonObject();
input.addProperty("site", URI.create(target).getHost());
input.addProperty("action", "login");
input.addProperty("proxy", proxy);
input.addProperty("requestId", UUID.randomUUID().toString());
long deadline = System.nanoTime() + Duration.ofSeconds(180).toNanos();
var task = Solve.api("/tasks?waitMs=0", input, deadline);
while (task.get("status").getAsString().matches("pending|solving")) {
String id = URLEncoder.encode(task.get("id").getAsString(), StandardCharsets.UTF_8);
task = Solve.api("/tasks/" + id + "?waitMs=15000", null, deadline);
if (task.get("status").getAsString().matches("pending|solving")) Thread.sleep(1000);
}
if (!task.get("status").getAsString().equals("ready")) throw new IllegalStateException("Task failed");
var solution = task.getAsJsonObject("solution");
var options = new ChromeOptions();
options.addArguments("--proxy-server=" + proxy, "--user-agent=" + solution.get("userAgent").getAsString());
var browser = new ChromeDriver(options);
try {
browser.executeCdpCommand("Network.enable", Map.of());
browser.executeCdpCommand("Network.setExtraHTTPHeaders", Map.of("headers",
Map.of("sec-ch-ua", solution.get("secChUa").getAsString())));
browser.get(target);
var wait = new WebDriverWait(browser, Duration.ofSeconds(20));
var field = wait.until(driver -> driver.findElement(By.name("g-recaptcha-response")));
browser.executeScript("arguments[0].value=arguments[1]; arguments[0].dispatchEvent(new Event('input',{bubbles:true}));",
field, solution.get("token").getAsString());
browser.findElement(By.cssSelector("button[type=submit]")).click();
wait.until(driver -> driver.findElement(By.id("success"))); // Your application's success marker.
} finally { browser.quit(); }
}
}顺序很重要:ChromeOptions 设置代理和返回的用户代理,然后 ChromeDriver 在 browser.get(target) 之前通过 CDP 设置返回的 sec-ch-ua 请求头。不要先使用不相关的身份访问页面,直到提交令牌时才更改身份。
本示例特意只提交一个 proxy,这样 Chrome 就能使用同一个代理,而不必从候选代理中选择。请确保目标 URL 与任务使用的主机名和操作一致。
运行 BrowserSolve 前调整表单
浏览器示例面向您自有的表单,其中包含 CAPTCHA 响应字段、提交按钮和成功状态元素。在将 Maven 依赖加入类路径并运行 BrowserSolve.main 之前,请修改这些与应用相关的值。
action:将login替换为您的应用在此次提交中使用的操作。g-recaptcha-response:确认表单实际提交的字段名。示例将solution.token赋给该字段,并派发一个冒泡的input事件。button[type=submit]:如果此选择器无法定位目标表单的提交按钮,请替换它。success:将此元素 ID 替换为仅在当前提交成功后才出现的状态元素。
设置字段并派发事件并不是通用的 CAPTCHA 回调方式。如果应用的表单逻辑不使用该字段或事件,请根据实际绑定方式调整提交步骤。检查发出的请求,确认其中包含的是 solution.token,而不是任务 ID 或旧令牌。
最后的 WebDriver 等待会检查应用的成功状态元素。请谨慎选择该标记:提交前就已存在的元素无法证明新请求的结果。任务就绪表示 solvethree 已生成解决结果;目标是否接受提交,则由应用响应来确认。
处理失败,避免不必要地创建新任务
- API 返回非成功 HTTP 响应。
将 HTTP 响应与任务状态分开检查。Solve.api 会先抛出异常,而不会将其作为成功响应载荷处理。
试试这样做: 重试前先诊断请求错误。确认请求字段和已启用的站点配置,而不是反复发送未经修改的无效输入。
- 任务进入 failed 状态。
读取任务响应中的 errorCode 和 error。BrowserSolve 的通用异常不会显示这些字段,但 Solve.java 独立示例会显示。
试试这样做: 停止轮询该任务,并在错误处理中展示这些字段。解题失败会退款;不要认为所有失败都是额度问题。
- 创建超时或达到本地截止时间。
确认您是否已有任务 ID,或是否保存了原始请求体和 requestId。连接超时并不能证明创建失败。
试试这样做: 使用任务 ID 继续获取结果,或使用相同的 requestId 和完全相同的输入恢复创建。如果需要在重启后恢复,请在创建前保存请求体;重新运行提供的 main 方法会生成新的 requestId。
- 任务已就绪,但应用拒绝提交。
检查发出的令牌、操作、目标 URL、代理、用户代理和 sec-ch-ua。确认应用实际提交了新赋给字段的值。
试试这样做: 在付费再次解题前,先修正不匹配的值或提交绑定方式。将成功使用的代理与返回的身份配套使用。
180 秒的 API 截止时间和 20 秒的应用等待时间是示例选择的本地限制,并非服务保证。更多 API 错误详情,请参阅 CAPTCHA API 错误。
运行您的第一个任务。
使用 $5 工作区额度开始体验。