在 C# 中将 CAPTCHA API 与 Selenium 配合使用

作者:solvethree阅读需 1 分钟

使用 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。

C# · 创建任务并提交自有表单
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. 1.

    将代理与返回的身份配套使用

    ChromeOptions 应用原始代理和 solution.userAgent。CDP 命令会在导航前启用网络功能,并使用 solution.secChUa 设置 sec-ch-ua。请保持此顺序;不要先打开页面,再更改身份。

  2. 2.

    将令牌写入应用读取的位置

    示例定位名为 g-recaptcha-response 的字段,赋值为 solution.token,并派发一个 input 事件。如果应用从其他位置读取令牌,请替换此传递方式。只有当应用发出的请求实际包含字段值时,填充字段才有用。

  3. 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 工作区额度开始体验。

创建工作区
在 C# 中将 CAPTCHA API 与 Selenium 配合使用 · solvethree