在 C# 中将 CAPTCHA API 与 Selenium 配合使用
使用 HttpClient 调用 solvethree,等待任务就绪,然后将 solution.token 填入应用的 CAPTCHA 字段。Chrome 必须在打开目标页面之前使用同一个代理和返回的浏览器身份。
以下示例使用 .NET 8 和 Selenium Chrome 提交您自有的表单。这是直接通过 REST 进行的集成,并非官方 C# SDK 或 Selenium 插件。
本页内容
准备项目和目标
- 使用 .NET 8 控制台项目,并启用默认的隐式 using。添加 NuGet 包
Selenium.WebDriver和Selenium.Support,两者版本均为4.52.0。 - 在本地安装 Chrome。Selenium Manager 会提供兼容的 Chrome 驱动程序。
- 确保您的工作区有可供目标主机名使用的已启用站点配置。发送主机名和操作不会创建站点配置;请先按照站点配置设置说明操作。
- 使用 URL 中不含凭据的 IP 白名单代理。示例将同一个代理传给 solvethree 和 Chrome。
| 变量 | 需要提供的值 |
|---|---|
SOLVETHREE_API_KEY | 您的工作区 API 密钥。请将其保存在运行自动化的可信机器上,而不是页面中。 |
SOLVETHREE_PROXY | 解题和浏览器提交共同使用的 IP 白名单代理 URL。 |
TARGET_URL | 应用表单页面的完整 URL。示例会提取其中的主机名,作为任务的 site。 |
如果集成使用其他操作,请将示例的 action 从 login 改为相应值。示例使用默认的 recaptchaType,即 enterprise-v3;任务请求不需要 type 或 url 字段。
创建任务并提交表单
API 辅助方法使用工作区密钥进行身份验证,并在解析成功响应载荷之前拒绝非成功 HTTP 响应。程序创建一个任务,在状态为 pending 或 solving 时按其 ID 轮询,并且仅在任务为 ready 后启动 Chrome。
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;
var key = Environment.GetEnvironmentVariable("SOLVETHREE_API_KEY")
?? throw new Exception("Set SOLVETHREE_API_KEY");
var proxy = Environment.GetEnvironmentVariable("SOLVETHREE_PROXY")
?? throw new Exception("Set SOLVETHREE_PROXY");
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", key);
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(180));
async Task<JsonObject> Api(HttpMethod method, string path, JsonObject? body = null)
{
using var request = new HttpRequestMessage(method, "https://api.solvethree.com/v1" + path);
if (body is not null)
request.Content = new StringContent(body.ToJsonString(), Encoding.UTF8, "application/json");
using var response = await http.SendAsync(request, deadline.Token);
response.EnsureSuccessStatusCode();
return JsonNode.Parse(await response.Content.ReadAsStringAsync(deadline.Token))!.AsObject();
}
var requestId = Guid.NewGuid().ToString(); // Save with these inputs for recovery.
var input = new JsonObject {
["site"] = new Uri(Environment.GetEnvironmentVariable("TARGET_URL") ?? throw new Exception("Set TARGET_URL")).Host, ["action"] = "login",
["proxy"] = proxy, ["requestId"] = requestId
};
var task = await Api(HttpMethod.Post, "/tasks?waitMs=0", input);
while (task["status"]!.GetValue<string>() is "pending" or "solving")
{
var id = Uri.EscapeDataString(task["id"]!.GetValue<string>());
task = await Api(HttpMethod.Get, "/tasks/" + id + "?waitMs=15000");
if (task["status"]!.GetValue<string>() is "pending" or "solving")
await Task.Delay(1000, deadline.Token);
}
if (task["status"]!.GetValue<string>() != "ready")
throw new Exception("Task failed: " + task["errorCode"]?.GetValue<string>()
+ ": " + task["error"]?.GetValue<string>());
var solution = task["solution"]!.AsObject();
// Use solution["token"], ["userAgent"] and ["secChUa"] with the original proxy.
Console.WriteLine("Ready task: " + task["id"]!.GetValue<string>());
var target = Environment.GetEnvironmentVariable("TARGET_URL") ?? throw new Exception("Set TARGET_URL");
if (!string.IsNullOrEmpty(new Uri(proxy).UserInfo)) throw new Exception("Use an IP-allowlisted proxy");
var options = new ChromeOptions();
options.AddArgument("--proxy-server=" + proxy);
options.AddArgument("--user-agent=" + solution["userAgent"]!.GetValue<string>());
using var browser = new ChromeDriver(options);
browser.ExecuteCdpCommand("Network.enable", new Dictionary<string, object>());
browser.ExecuteCdpCommand("Network.setExtraHTTPHeaders", new Dictionary<string, object> {
["headers"] = new Dictionary<string, object> { ["sec-ch-ua"] = solution["secChUa"]!.GetValue<string>() }
});
browser.Navigate().GoToUrl(target);
var wait = new WebDriverWait(browser, TimeSpan.FromSeconds(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["token"]!.GetValue<string>());
browser.FindElement(By.CssSelector("button[type=submit]")).Click();
wait.Until(driver => driver.FindElement(By.Id("success"))); // Your application's success marker.30 秒的 HTTP 超时、180 秒的 API 截止时间和 20 秒的 Selenium 等待时间都是本示例中的设置,并非服务时效保证。请根据应用的超时策略进行调整。
使令牌传递方式与应用匹配
- 1.
将代理与返回的身份配套使用
ChromeOptions 应用原始代理和
solution.userAgent。CDP 命令会在导航前启用网络功能,并使用solution.secChUa设置sec-ch-ua。请保持此顺序;不要先打开页面,再更改身份。 - 2.
将令牌写入应用读取的位置
示例定位名为
g-recaptcha-response的字段,赋值为solution.token,并派发一个 input 事件。如果应用从其他位置读取令牌,请替换此传递方式。只有当应用发出的请求实际包含字段值时,填充字段才有用。 - 3.
替换提交和成功状态选择器
示例点击
button[type=submit],然后等待 ID 为success的元素。请使用真实应用中的选择器,并选择能确认提交操作成功的标记,而不只是确认按钮被点击。
任务就绪表示解决结果已可用。提交后的检查是独立步骤:目标仍会自行决定是否接受应用请求。
处理失败,避免创建不必要的任务
- API 辅助方法因 HTTP 响应而抛出异常。
检查 HTTP 状态和错误响应。这与任务进入
failed状态不同。试试这样做: 重试前先修正报告的请求或身份验证问题。不要持续重新发送未经修改的无效输入。
- 任务进入
failed状态。 读取
errorCode和error;site-unreachable是可能出现的任务错误之一。试试这样做: 停止轮询该任务,并处理报告的原因。解题失败会退款。
- 任务已就绪,但目标拒绝提交。
先检查应用发出的请求:其中包含的是
solution.token,而不是任务 ID 或旧令牌吗?然后检查代理、用户代理、客户端提示和目标 URL。试试这样做: 在付费再次解题前,先修正令牌传递方式或身份不匹配问题。
- 点击提交后 Selenium 超时。
检查页面和应用响应。
success选择器只是示例中的固定设置,并非通用的接受信号。试试这样做: 将其替换为应用的实际成功条件,或处理应用返回的拒绝结果。
示例会生成 requestId,但不会为您持久化保存。如果需要在超时后恢复,请在创建前将它与完全一致的任务输入一并保存。如果创建超时,请使用同一个 ID 重新发送相同输入,以恢复现有任务;不要仅因响应丢失就生成新 ID。将同一个 ID 用于不同输入会发生冲突。如果已有任务 ID,请改为获取该任务。
运行您的第一个任务。
使用 $5 工作区额度开始体验。