在 Go 中并发求解 CAPTCHA 任务
使用一个共享的 Go SDK 客户端和固定大小的工作池。为每个作业创建独立的上下文,并在每个解决结果就绪后立即提交,不要收集令牌留待后续批量提交。
本页内容
准备客户端和目标站点
- 使用 Go 1.22 或更高版本,以及版本为
v0.1.0的官方solvethree.com/sdk模块。安装详情请参阅 Go 集成文档。 - 在服务器环境中设置
SOLVETHREE_API_KEY和SOLVETHREE_PROXY。不要将 API 密钥放入浏览器端代码。 - 将
accounts.example.com和login替换为目标主机名和操作。工作区中需要有该站点的有效配置;提交主机名不会创建配置。请参阅站点配置设置。
此示例请求默认的 reCAPTCHA Enterprise。它提供一个代理,并特意不设置 Type 和 URL:reCAPTCHA 请求使用站点主机名和操作,而不是这些字段。
限制并发工作量,而不是客户端数量
该客户端可安全地并发使用。此示例启动四个工作协程,并通过无缓冲通道向它们分配八个作业。当所有工作协程都忙碌时,生产者会等待,而不是为每个作业启动一个 goroutine。四个仅是示例应用的设置,并非 solvethree 的服务限制。
package main
import (
"context"
"fmt"
"log"
"os"
"sync"
"time"
solvethree "solvethree.com/sdk"
)
func main() {
client, err := solvethree.NewClient(os.Getenv("SOLVETHREE_API_KEY"), nil)
if err != nil { log.Fatal(err) }
jobs := make(chan int)
var workers sync.WaitGroup
for worker := 0; worker < 4; worker++ {
workers.Add(1)
go func() {
defer workers.Done()
for job := range jobs {
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
input := solvethree.TaskRequest{Site: "accounts.example.com", Action: "login", Proxy: os.Getenv("SOLVETHREE_PROXY")}
result, err := client.Solve(ctx, input, solvethree.SolveOptions{})
if err != nil { cancel(); log.Printf("Job %d: %v", job, err); continue }
fmt.Println("Ready task:", result.ID)
// Submit this solution now, with its proxy and browser identity.
cancel()
}
}()
}
for job := 0; job < 8; job++ { jobs <- job }
close(jobs)
workers.Wait()
}每个工作协程会为每个作业创建独立的 180 秒上下文。Solve 只创建一次任务,然后等待结果。由于示例省略了 RequestID,SDK 会为此次创建生成一个 ID。调用失败时会记录日志,continue 仅让该工作协程转到下一个作业,不会停止整个批次。
如示例所示,在循环内部执行取消操作,以便每个作业及时释放上下文资源。取消只会停止本地等待,不会取消远程工作。
先处理解决结果,再领取下一个作业
此示例会打印已就绪任务的 ID,不会向目标站点提交任何内容。请将注释替换为目标请求,并在工作协程领取下一个作业之前,报告该作业的提交结果。不要将令牌追加到切片中,留到 workers.Wait 之后再使用。
- 提交
result.Solution.Token,而不是result.ID。 - 将原始任务和已就绪结果传给
SuccessfulProxy,以确定成功使用的代理。通过该代理提交,并使用返回的UserAgent和SecChUA浏览器身份信息。 - 在作业上下文内、调用
cancel()之前完成提交。这样,180 秒的截止时间就同时涵盖求解和提交;如果需要单独的时间预算,请使用独立的提交上下文。
分别报告求解结果和目标提交结果。已就绪的任务会提供可用的解决结果,但只有目标站点的响应才能说明它是否接受了应用请求。
恢复中断的作业,无需重启整个批次
对于这个小示例,记录错误就足够了,但支持恢复的集成应保留每个作业的确切任务输入,以及可获取的请求 ID 和任务 ID。超时并不能证明任务创建失败。
- 提取
*solvethree.Error以获取RequestID和TaskID。如果已知任务 ID,请使用新的上下文,通过GetTask恢复结果获取。 - 如果无法确定创建是否成功,且你持有请求 ID,请使用相同的
RequestID重放原始输入。相同的请求会返回现有任务;复用 ID 时更改输入会导致冲突。 - 对于
*TaskFailedError,记录ErrorCode;如果需要其Error文本,请获取失败的任务。停止轮询该任务并解决原因,不要盲目再次提交相同的无效输入。
区分 HTTP 错误与任务的终止性失败。errors.As 可以提取嵌套的 *APIError,其中包含 StatusCode 和 RetryAfter。对于 HTTP 429、502、503 或 504 这类临时轮询响应,Solve 已会自动重试,并在上下文截止时间内遵循 Retry-After。任务创建绝不会自动重试。
运行您的第一个任务。
使用 $5 工作区额度开始体验。