使用 Rust 求解 reCAPTCHA Enterprise
从 Rust 调用 solvethree 的 REST API:创建任务,轮询至就绪,然后使用返回的浏览器身份信息和同一代理向目标提交 solution.token。以下示例直接使用 reqwest、Tokio 和 serde_json,而不是官方 Rust SDK。
本页内容
配置站点和请求
首先,在工作区控制台中为目标主机名创建启用的配置。发送主机名和操作不会创建配置。如果尚未配置目标,请参阅站点配置设置。
在运行 Rust 客户端的服务器上设置以下环境变量:
SOLVETHREE_API_KEY:你的工作区 API 密钥。请将其保存在服务器端。SOLVETHREE_PROXY:用于求解和后续目标请求的代理 URL。REQUEST_ID:此逻辑任务的可选稳定 ID。在发送请求前保存它,以便在连接中断后恢复。
| 字段 | 发送内容 |
|---|---|
site | 目标主机名。替换示例中的 accounts.example.com。 |
action | 目标要求的操作。必要时替换 login。 |
proxy | 从 SOLVETHREE_PROXY 读取的单个代理 URL。 |
requestId | 包含 1–64 个 ASCII 字母、数字、下划线或连字符的幂等键。 |
recaptchaType | 此处省略,因为默认值为 enterprise-v3。 |
不要向此 reCAPTCHA 请求添加 type 或 url 字段。proxy 和 proxyCandidates 必须且只能提供其中一个;示例使用 proxy,以便向目标交接时使用同一连接。
创建 Rust 客户端
在 Cargo 二进制项目中,将依赖文件放入 Cargo.toml,将客户端放入 src/main.rs。设置环境变量并更改目标主机名和操作后,使用 Cargo 运行项目。依赖配置将 reqwest 固定为版本 0.13.5。
[package]
name = "solvethree-example"
version = "0.1.0"
edition = "2021"
[dependencies]
reqwest = { version = "=0.13.5", features = ["json"] }
serde_json = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }use std::{env, error::Error, time::{Duration, SystemTime, UNIX_EPOCH}};
use reqwest::{Client, redirect::Policy};
use serde_json::{json, Value};
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let api = "https://api.solvethree.com/v1";
let key = env::var("SOLVETHREE_API_KEY")?;
let request_id = env::var("REQUEST_ID").unwrap_or_else(|_| format!("rust-{}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos()));
let input = json!({"site":"accounts.example.com", "action":"login", "proxy":env::var("SOLVETHREE_PROXY")?, "requestId":request_id});
let client = Client::builder().redirect(Policy::none()).timeout(Duration::from_secs(30)).build()?;
let mut task_id: Option<String> = None;
let outcome = tokio::time::timeout(Duration::from_secs(180), async {
let response = client.post(format!("{api}/tasks?waitMs=0")).bearer_auth(&key).json(&input).send().await?;
if !response.status().is_success() { return Err(format!("API HTTP {}", response.status()).into()); }
let mut result: Value = response.json().await?;
let id = result["id"].as_str().ok_or("Missing task ID")?.to_owned();
if !id.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-') { return Err("Invalid task ID".into()); }
task_id = Some(id.clone());
loop {
match result["status"].as_str() {
Some("ready") => return Ok::<Value, Box<dyn Error>>(result),
Some("failed") => return Err(format!("Task failed: {}", result["errorCode"]).into()),
Some("pending") | Some("solving") => {},
_ => return Err("Unexpected task status".into()),
}
tokio::time::sleep(Duration::from_secs(1)).await;
let response = client.get(format!("{api}/tasks/{id}?waitMs=15000")).bearer_auth(&key).send().await?;
if !response.status().is_success() { return Err(format!("API HTTP {}", response.status()).into()); }
result = response.json().await?;
}
}).await;
match outcome {
Ok(Ok(result)) => {
println!("Ready task: {}", result["id"].as_str().unwrap_or("unknown"));
// Submit result["solution"]["token"] with returned browser headers
// through the original proxy; do not log the solution.
Ok(())
},
Ok(Err(error)) => {
eprintln!("Recovery requestId={request_id} taskId={task_id:?}");
Err(error)
},
Err(_) => {
eprintln!("Recovery requestId={request_id} taskId={task_id:?}");
Err("Local deadline reached; recover the existing task".into())
}
}
}客户端向 POST /v1/tasks 发送一次创建请求,使用 Bearer 令牌进行身份验证。它保留返回的任务 ID,并使用 GET /v1/tasks/:id 发送后续状态请求。
两项限制可防止客户端无限等待:reqwest 的超时将每个 HTTP 请求限制为 30 秒,而 Tokio 的超时将创建和轮询的总时长限制为 180 秒。这些是客户端设置,并非服务完成时间保证。通过 reqwest 的 ClientBuilder 禁用重定向。
只有 pending 和 solving 会继续轮询循环。ready 返回结果,failed 以错误停止,遇到意外状态时也会停止,而不是无限轮询。成功路径打印的是任务 ID,而不是令牌。
恢复中断的请求,避免再次求解
示例将 requestId 和返回的任务 ID 保存在内存中,并在发生 HTTP、传输、解析或截止时间错误时打印它们。进程终止后,这些信息不会持久保留。要支持重启恢复,请在创建前存储请求 ID 和原始任务输入,并在收到任务 ID 后立即存储。
- 1.
如果已有任务 ID,则获取该任务
使用 GET /v1/tasks/:id 恢复,而不是创建另一个任务。示例的常规入口始终会创建任务;重启恢复路径应使用已保存的 ID。
- 2.
如果只有请求 ID,则恢复创建过程
使用相同的 requestId 提交相同的任务请求。这会返回现有任务。复用 ID 时更改输入会导致冲突。
- 3.
处理恢复后的状态
继续轮询 pending 或 solving 状态的任务。使用 ready 状态的求解结果,或在 failed 状态时停止并检查 errorCode 和 error。仅凭本地超时无法认定远程任务失败。
HTTP 失败和任务失败需要采用不同的处理方式。客户端会在解析成功响应数据前检查是否为非成功 HTTP 响应。任务终态失败则通过任务状态 failed 返回;求解失败会退款。示例的失败消息仅包含 errorCode,因此诊断时还应检查任务响应中的 error。在使用未更改的输入重试前,请参阅 API 错误处理。
使用令牌对应的浏览器身份信息提交令牌
示例在任务就绪时停止,不会向目标发送应用请求。该请求需要 solution.token,而不是任务 ID。通过原始代理使用返回的 userAgent 和 secChUa 身份信息,并向预期目标提交,不要混用其他浏览器会话中的值。
任务就绪表示已生成求解结果,并不表示目标接受了你的应用请求。在成功路径中标记的注释处添加针对目标的提交逻辑;有关令牌交接的指导,请参阅 reCAPTCHA Enterprise 集成指南。
运行您的第一个任务。
使用 $5 工作区额度开始体验。