Scarnox · Blog

Go 验证码求解器:使用 net/http 的实例(2026)

在 Go 中实现验证码求解器无需任何第三方包。标准库中的 net/http 和 encoding/json 就足以覆盖整个流程:一次向 Scarnox API 发起的 POST 请求,一个 JSON 响应,以及一次向网站的表单提交。本指南针对 PopularCaptcha 构建这一流程,Scarnox 目前以无代理方式支持该类型的求解。

先获取免费试用额度,无需信用卡,并在你控制的页面上运行该程序,然后再将其用于其他地方。

环境要求

完整程序

这个单文件程序会求解验证码、重试临时性失败,并将 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 则填入相同的值。大多数网站会读取其中一个。

许多注册和搜索页面会将验证码与会话绑定。网站客户端上的 cookiejar 会在显示表单的 GET 请求与提交表单的 POST 请求之间保留 cookie。如果跳过这一步,网站可能会拒绝一个本来有效的 token。

时机

token 约在两分钟内过期,因此请在提交前立即求解。不要在长时间任务开始时求解,然后再等待。验证码 token 过期一文说明了如何估算这段时间窗口。

无感验证码组件

如果页面上的组件是无感模式,请将 invisible 参数设为 true。请求中就会包含 is_invisible: true。有些页面还需要调用一个以 token 为参数的回调函数。请检查页面脚本以找到它。无感验证码指南列出了相关模式。

并发

Go 可以很方便地借助 goroutine 同时求解多个验证码。请将并发求解数控制在你的账户和目标网站能够承受的范围内。即使你的求解器跟得上,也不应让单个域名接收到突发的请求。请遵守网站的 robots.txt 和服务条款,并将抓取范围限定在你有权收集的公开数据上。

价格

Scarnox 按每次成功的求解计费,失败的求解不收费。费率请查看当前定价。要试用该 API,请先获取免费试用额度,无需信用卡。

下一步