将 Go SDK 与 chromedp 配合使用

作者:solvethree阅读需 1 分钟

先求解,再使用相同的代理和返回的浏览器身份启动 Chrome。将令牌插入应用表单,提交表单,然后等待应用结果。任务就绪表示你已获得解决方案,并不表示网站已接受它。

本页内容

设置模块、配置和代理

使用 go get 安装 solvethree.com/[email protected] 和 github.com/chromedp/[email protected]。SDK 要求 Go 1.22+,但此 chromedp 版本要求 Go 1.25+,因此组合示例应使用后者。它的类型化 API 与较早的 chromedp 示例不同。

运行程序前,在工作区中为目标主机名创建一个已启用的站点配置。发送主机名和操作并不会创建配置。

  • TARGET_URL:包含表单的完整 HTTPS 页面 URL。任务使用其主机名作为 Site;Chrome 则访问完整 URL。
  • SOLVETHREE_PROXY:单个 HTTP 或 HTTPS 代理 URL。此示例要求代理通过 IP 白名单授权,且 URL 中不含凭据。
  • SOLVETHREE_API_KEY:工作区 API 密钥。将其保留在 Go 进程中,不要放入页面 JavaScript。

此示例请求 login 操作,并使用默认的 Enterprise v3 reCAPTCHA 模式。请将操作改为应用所期望的值。

求解、启动 Chrome 并提交

程序等待 SDK 求解完成后,才创建 Chrome 的分配器。它使用相同的单个代理和 result.Solution.UserAgent 配置 Chrome,然后在导航前将返回的 SecChUA 设置为 sec-ch-ua 请求头。不要替换为无关的浏览器身份,也不要在求解与提交之间切换代理。

chromedp · 通过 IP 白名单代理提交
// go get github.com/chromedp/[email protected] (requires Go 1.25+)
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "net/url"
    "os"
    "time"
    "github.com/chromedp/cdproto/network"
    "github.com/chromedp/chromedp"
    solvethree "solvethree.com/sdk"
)

func main() { if err := run(); err != nil { log.Fatal(err) } }

func run() error {
    target,err:=url.Parse(os.Getenv("TARGET_URL"));if err!=nil||target.Scheme!="https"||target.Hostname()=="" { return fmt.Errorf("set an HTTPS TARGET_URL") }
    proxy,err:=url.Parse(os.Getenv("SOLVETHREE_PROXY"));if err!=nil||proxy.User!=nil { return fmt.Errorf("use an IP-allowlisted proxy without URL credentials") }
    client,err:=solvethree.NewClient(os.Getenv("SOLVETHREE_API_KEY"),nil);if err!=nil { return err }
    ctx,cancel:=context.WithTimeout(context.Background(),180*time.Second);defer cancel()
    result,err:=client.Solve(ctx,solvethree.TaskRequest{Site:target.Hostname(),Action:"login",Proxy:proxy.String()},solvethree.SolveOptions{})
    if err!=nil { return err }
    options:=append(chromedp.DefaultExecAllocatorOptions[:],chromedp.ProxyServer(proxy.String()),chromedp.UserAgent(result.Solution.UserAgent))
    allocator,closeAllocator:=chromedp.NewExecAllocator(context.Background(),options...);defer closeAllocator()
    browser,closeBrowser:=chromedp.NewContext(allocator);defer closeBrowser()
    browser,stop:=context.WithTimeout(browser,60*time.Second);defer stop()
    tokenJSON,err:=json.Marshal(result.Solution.Token);if err!=nil { return err }
    script:=`(token => {const field=document.querySelector('[name="g-recaptcha-response"]'); field.value=token; field.dispatchEvent(new Event('input',{bubbles:true}));})(`+string(tokenJSON)+`)`
    _,err=chromedp.Call(browser,network.SetExtraHTTPHeaders,network.SetExtraHTTPHeadersParams{Headers:network.Headers{"sec-ch-ua":result.Solution.SecChUA}})
    if err!=nil { return err }
    err=chromedp.Do(browser,
        chromedp.Navigate(target.String()),chromedp.WaitReady(chromedp.CSS(`[name="g-recaptcha-response"]`)),
        chromedp.Evaluate[chromedp.Void](script),chromedp.Click(chromedp.CSS(`button[type="submit"]`)),
        chromedp.WaitVisible(chromedp.ID("success")))
    if err!=nil { return err }
    // #success is this owned demo form's outcome. Adapt its selectors, token
    // parameter and success check to your application, including CSRF/session fields.
    return nil
}

此示例特意只使用一个代理,因此无需确定选中了哪个候选代理。如果之后使用 ProxyCandidates,请通过 SuccessfulProxy(originalTask, readyResult) 获取成功使用的代理,并让 Chrome 使用该代理。

替换演示表单的假设条件

插入令牌时,会先使用 json.Marshal,再构造 JavaScript 表达式。请保留此转义步骤:它将令牌作为 JavaScript 字符串值传递,而不是将未经转义的文本插入可执行代码。

  • 将 [name="g-recaptcha-response"] 替换为应用实际提交的字段。示例会设置该字段的值并派发 input 事件;如果应用需要其他提交方式,请相应调整。
  • 将 button[type="submit"] 替换为预期的提交控件。点击前,请准备好所有必需的表单值、CSRF 字段和会话状态。
  • 将 #success 替换为真正表示应用已接受提交的结果。点击完成或令牌字段已填充,都不代表这一结果。

SDK 不会向目标提交令牌。如果目标拒绝请求,先检查发出的表单:确认其中包含 result.Solution.Token,而不是任务 ID 或旧的字段值,并检查代理和浏览器身份是否仍然一致。

处理截止时间,避免重复求解

求解上下文的截止时间为 180 秒。求解完成后,Chrome 使用单独的 60 秒截止时间。run 函数会返回错误,以便在 main 记录错误之前,先执行延迟的上下文取消和浏览器清理。

取消求解上下文只会停止等待,不会停止远程工作。SDK 仅创建一次任务,只重试临时的轮询失败,从不重试创建任务的 POST 请求。因此,超时并不意味着应该立即提交新任务。

  • 发生取消或到达截止时间时,使用 errors.Is 识别上下文错误。使用 errors.As 提取 *solvethree.Error,并保留其 TaskID 和 RequestID。
  • 如果有任务 ID,使用 GetTask 获取任务。否则,使用相同的请求 ID 重放完全相同的原始任务输入来恢复。不携带该 ID 再次调用会创建新的逻辑请求;复用该 ID 却更改输入会产生冲突。
  • 对于 *TaskFailedError,检查 ErrorCode,并获取失败任务以查看其错误文本。该任务已进入终态:停止轮询并修正原因,不要使用未更改的输入重试。
  • 对于 HTTP API 错误,提取嵌套的 *APIError,以检查 StatusCode 和 RetryAfter。不要将 HTTP 失败与已进入终态的任务失败混淆。

运行您的第一个任务。

使用 $5 工作区额度开始体验。

创建工作区
将 Go SDK 与 chromedp 配合使用 · solvethree