Go 验证码求解器:使用 net/http 的实例(2026)
在 Go 中实现验证码求解器无需任何第三方包。标准库中的 net/http 和 encoding/json 就足以覆盖整个流程:一次向 Scarnox API 发起的 POST 请求,一个 JSON 响应,以及一次向网站的表单提交。本指南针对 PopularCaptcha 构建这一流程,Scarnox 目前以无代理方式支持该类型的求解。
先获取免费试用额度,无需信用卡,并在你控制的页面上运行该程序,然后再将其用于其他地方。
环境要求
- Go 1.21 或更新版本。
- 一个 Scarnox API 密钥,从环境变量中读取。
- 页面 PopularCaptcha 组件的 sitekey。如果你需要帮助找到它,请参阅如何找到验证码 sitekey。
完整程序
这个单文件程序会求解验证码、重试临时性失败,并将 token 提交到表单中。将其保存为 main.go:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"time"
)
const apiURL = "https://api.scarnox.com/api/tasks/create"
// 求解调用会阻塞直到 token 准备好,因此超时时间设置得较长。
var apiClient = &http.Client{Timeout: 185 * time.Second}
type createRequest struct {
CaptchaType string `json:"captcha_type"`
SiteKey string `json:"site_key"`
SiteURL string `json:"site_url"`
IsInvisible bool `json:"is_invisible,omitempty"`
}
type createResponse struct {
ErrorID int `json:"errorId"`
ErrorCode string `json:"errorCode"`
ErrorDescription string `json:"errorDescription"`
Solution struct {
Token string `json:"token"`
} `json:"solution"`
}
func solveCaptcha(apiKey, sitekey, pageURL string, invisible bool) (string, error) {
body, err := json.Marshal(createRequest{
CaptchaType: "PopularCaptchaTaskProxyless",
SiteKey: sitekey,
SiteURL: pageURL,
IsInvisible: invisible,
})
if err != nil {
return "", err
}
req, err := http.NewRequest(http.MethodPost, apiURL, bytes.NewReader(body))
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := apiClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
raw, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var out createResponse
if err := json.Unmarshal(raw, &out); err != nil {
return "", fmt.Errorf("HTTP %d, response was not JSON: %s", resp.StatusCode, raw)
}
if out.ErrorID != 0 {
return "", fmt.Errorf("%s: %s", out.ErrorCode, out.ErrorDescription)
}
return out.Solution.Token, nil
}
// solveWithRetry 重试临时性失败。失败的求解不会被计费。
func solveWithRetry(apiKey, sitekey, pageURL string, attempts int) (string, error) {
var lastErr error
for i := 1; i <= attempts; i++ {
token, err := solveCaptcha(apiKey, sitekey, pageURL, false)
if err == nil {
return token, nil
}
lastErr = err
log.Printf("attempt %d failed: %v", i, err)
time.Sleep(time.Duration(i) * time.Second)
}
return "", lastErr
}
func main() {
apiKey := os.Getenv("SCARNOX_API_KEY")
if apiKey == "" {
log.Fatal("set SCARNOX_API_KEY")
}
const pageURL = "https://example.com/signup"
const sitekey = "YOUR-SITEKEY-UUID"
token, err := solveWithRetry(apiKey, sitekey, pageURL, 3)
if err != nil {
log.Fatal(err)
}
// cookie jar 会保留网站与验证码绑定的会话。
jar, err := cookiejar.New(nil)
if err != nil {
log.Fatal(err)
}
site := &http.Client{Jar: jar, Timeout: 60 * time.Second}
form := url.Values{}
form.Set("email", "you@example.com")
form.Set("h-captcha-response", token)
form.Set("g-recaptcha-response", token)
resp, err := site.PostForm(pageURL, form)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
fmt.Println("Status:", resp.Status)
}
运行方式:
export SCARNOX_API_KEY=scarnox_YOURAPIKEY
go run main.go
各部分如何配合
请求。 solveCaptcha 将结构体序列化为 API 所需的 JSON 请求体,并设置 Authorization 请求头。Bearer 前缀是必需的。
超时。 API 会阻塞直到 token 准备好,因此客户端超时为 185 秒。不要把它设得更短,否则会取消本来即将完成的求解。
错误检查。 API 成功时 errorId 为 0。其他任何值都会附带 errorCode 和 errorDescription,函数会将它们作为错误返回。token 位于 solution.token。
提交。 token 放入 h-captcha-response 字段,g-recaptcha-response 则填入相同的值。大多数网站会读取其中一个。
使用 cookie jar
许多注册和搜索页面会将验证码与会话绑定。网站客户端上的 cookiejar 会在显示表单的 GET 请求与提交表单的 POST 请求之间保留 cookie。如果跳过这一步,网站可能会拒绝一个本来有效的 token。
时机
token 约在两分钟内过期,因此请在提交前立即求解。不要在长时间任务开始时求解,然后再等待。验证码 token 过期一文说明了如何估算这段时间窗口。
无感验证码组件
如果页面上的组件是无感模式,请将 invisible 参数设为 true。请求中就会包含 is_invisible: true。有些页面还需要调用一个以 token 为参数的回调函数。请检查页面脚本以找到它。无感验证码指南列出了相关模式。
并发
Go 可以很方便地借助 goroutine 同时求解多个验证码。请将并发求解数控制在你的账户和目标网站能够承受的范围内。即使你的求解器跟得上,也不应让单个域名接收到突发的请求。请遵守网站的 robots.txt 和服务条款,并将抓取范围限定在你有权收集的公开数据上。
价格
Scarnox 按每次成功的求解计费,失败的求解不收费。费率请查看当前定价。要试用该 API,请先获取免费试用额度,无需信用卡。