API Tutorials

为 CaptchaAI API 构建 Go 客户端库

Go 的强类型、内置并发和单二进制部署使其成为自动化系统的可靠选择。本指南构建了一个遵循 Go 约定的 CaptchaAI 客户端库 - context.Context 支持、自定义 http.Client 注入和键入的错误值。

封装结构

captchaai/
├── client.go       # Main client and solve logic
├── errors.go       # Error types
├── types.go        # Request/response structs
└── client_test.go  # Tests

错误类型

// errors.go
package captchaai

import "fmt"

// APIError represents a CaptchaAI API error response.
type APIError struct {
    Code    string
    Message string
}

func (e *APIError) Error() string {
    return fmt.Sprintf("captchaai: %s (%s)", e.Message, e.Code)
}

// IsFatal returns true if this error should not be retried.
func (e *APIError) IsFatal() bool {
    switch e.Code {
    case "ERROR_WRONG_USER_KEY", "ERROR_KEY_DOES_NOT_EXIST",
        "ERROR_ZERO_BALANCE", "ERROR_IP_NOT_ALLOWED":
        return true
    }
    return false
}

// TimeoutError indicates the solve exceeded the configured timeout.
type TimeoutError struct {
    TaskID string
}

func (e *TimeoutError) Error() string {
    return fmt.Sprintf("captchaai: task %s timed out", e.TaskID)
}

类型

// types.go
package captchaai

import "time"

// ClientOption configures the CaptchaAI client.
type ClientOption func(*Client)

// WithPollInterval sets the polling interval between result checks.
func WithPollInterval(d time.Duration) ClientOption {
    return func(c *Client) { c.pollInterval = d }
}

// WithTimeout sets the maximum time to wait for a solution.
func WithTimeout(d time.Duration) ClientOption {
    return func(c *Client) { c.timeout = d }
}

// RecaptchaV2Params holds parameters for reCAPTCHA v2 solving.
type RecaptchaV2Params struct {
    SiteKey   string
    PageURL   string
    Invisible bool
    Cookies   string
}

// RecaptchaV3Params holds parameters for reCAPTCHA v3 solving.
type RecaptchaV3Params struct {
    SiteKey  string
    PageURL  string
    Action   string
    MinScore float64
}

// TurnstileParams holds parameters for Cloudflare Turnstile solving.
type TurnstileParams struct {
    SiteKey string
    PageURL string
    Action  string
    CData   string
}

// ImageParams holds parameters for image/OCR CAPTCHA solving.
type ImageParams struct {
    Base64Image   string
    CaseSensitive bool
    MinLength     int
    MaxLength     int
}

type submitResponse struct {
    Status  int    `json:"status"`
    Request string `json:"request"`
}

type pollResponse struct {
    Status  int    `json:"status"`
    Request string `json:"request"`
}

客户端实施

// client.go
package captchaai

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
    "strconv"
    "time"
)

const (
    submitURL           = "https://ocr.captchaai.com/in.php"
    resultURL           = "https://ocr.captchaai.com/res.php"
    defaultPollInterval = 5 * time.Second
    defaultTimeout      = 180 * time.Second
)

// Client interacts with the CaptchaAI API.
type Client struct {
    apiKey       string
    httpClient   *http.Client
    pollInterval time.Duration
    timeout      time.Duration
}

// New creates a CaptchaAI client with the given API key and options.
func New(apiKey string, opts ...ClientOption) *Client {
    c := &Client{
        apiKey:       apiKey,
        httpClient:   http.DefaultClient,
        pollInterval: defaultPollInterval,
        timeout:      defaultTimeout,
    }
    for _, opt := range opts {
        opt(c)
    }
    return c
}

// WithHTTPClient sets a custom HTTP client (e.g., for proxy support).
func WithHTTPClient(hc *http.Client) ClientOption {
    return func(c *Client) { c.httpClient = hc }
}

func (c *Client) submit(ctx context.Context, params url.Values) (string, error) {
    params.Set("key", c.apiKey)
    params.Set("json", "1")

    req, err := http.NewRequestWithContext(ctx, http.MethodPost, submitURL, nil)
    if err != nil {
        return "", fmt.Errorf("captchaai: build request: %w", err)
    }
    req.URL.RawQuery = params.Encode()

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return "", fmt.Errorf("captchaai: submit: %w", err)
    }
    defer resp.Body.Close()

    var result submitResponse
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return "", fmt.Errorf("captchaai: decode submit response: %w", err)
    }

    if result.Status != 1 {
        return "", &APIError{Code: result.Request, Message: "submit failed"}
    }

    return result.Request, nil
}

func (c *Client) poll(ctx context.Context, taskID string) (string, error) {
    deadline := time.After(c.timeout)

    for {
        select {
        case <-ctx.Done():
            return "", ctx.Err()
        case <-deadline:
            return "", &TimeoutError{TaskID: taskID}
        case <-time.After(c.pollInterval):
        }

        params := url.Values{
            "key":    {c.apiKey},
            "action": {"get"},
            "id":     {taskID},
            "json":   {"1"},
        }

        req, err := http.NewRequestWithContext(ctx, http.MethodGet, resultURL+"?"+params.Encode(), nil)
        if err != nil {
            return "", fmt.Errorf("captchaai: build poll request: %w", err)
        }

        resp, err := c.httpClient.Do(req)
        if err != nil {
            continue // Retry on network error
        }

        var result pollResponse
        if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
            resp.Body.Close()
            continue
        }
        resp.Body.Close()

        if result.Request == "CAPCHA_NOT_READY" {
            continue
        }

        if result.Status == 1 {
            return result.Request, nil
        }

        return "", &APIError{Code: result.Request, Message: "solve failed"}
    }
}

// SolveRecaptchaV2 solves a reCAPTCHA v2 challenge.
func (c *Client) SolveRecaptchaV2(ctx context.Context, p RecaptchaV2Params) (string, error) {
    params := url.Values{
        "method":    {"userrecaptcha"},
        "googlekey": {p.SiteKey},
        "pageurl":   {p.PageURL},
    }
    if p.Invisible {
        params.Set("invisible", "1")
    }
    if p.Cookies != "" {
        params.Set("cookies", p.Cookies)
    }

    taskID, err := c.submit(ctx, params)
    if err != nil {
        return "", err
    }
    return c.poll(ctx, taskID)
}

// SolveRecaptchaV3 solves a reCAPTCHA v3 challenge.
func (c *Client) SolveRecaptchaV3(ctx context.Context, p RecaptchaV3Params) (string, error) {
    params := url.Values{
        "method":    {"userrecaptcha"},
        "version":   {"v3"},
        "googlekey": {p.SiteKey},
        "pageurl":   {p.PageURL},
    }
    if p.Action != "" {
        params.Set("action", p.Action)
    }
    if p.MinScore > 0 {
        params.Set("min_score", strconv.FormatFloat(p.MinScore, 'f', 1, 64))
    }

    taskID, err := c.submit(ctx, params)
    if err != nil {
        return "", err
    }
    return c.poll(ctx, taskID)
}

// SolveTurnstile solves a Cloudflare Turnstile challenge.
func (c *Client) SolveTurnstile(ctx context.Context, p TurnstileParams) (string, error) {
    params := url.Values{
        "method":  {"turnstile"},
        "sitekey": {p.SiteKey},
        "pageurl": {p.PageURL},
    }
    if p.Action != "" {
        params.Set("action", p.Action)
    }
    if p.CData != "" {
        params.Set("data", p.CData)
    }

    taskID, err := c.submit(ctx, params)
    if err != nil {
        return "", err
    }
    return c.poll(ctx, taskID)
}

// SolveImage solves an image/text CAPTCHA from base64.
func (c *Client) SolveImage(ctx context.Context, p ImageParams) (string, error) {
    params := url.Values{
        "method": {"base64"},
        "body":   {p.Base64Image},
    }
    if p.CaseSensitive {
        params.Set("regsense", "1")
    }
    if p.MinLength > 0 {
        params.Set("min_len", strconv.Itoa(p.MinLength))
    }
    if p.MaxLength > 0 {
        params.Set("max_len", strconv.Itoa(p.MaxLength))
    }

    taskID, err := c.submit(ctx, params)
    if err != nil {
        return "", err
    }
    return c.poll(ctx, taskID)
}

// GetBalance returns the current account balance.
func (c *Client) GetBalance(ctx context.Context) (float64, error) {
    params := url.Values{
        "key":    {c.apiKey},
        "action": {"getbalance"},
        "json":   {"1"},
    }

    req, err := http.NewRequestWithContext(ctx, http.MethodGet, resultURL+"?"+params.Encode(), nil)
    if err != nil {
        return 0, err
    }

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return 0, err
    }
    defer resp.Body.Close()

    var result pollResponse
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return 0, err
    }

    return strconv.ParseFloat(result.Request, 64)
}

用法

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "your-module/captchaai"
)

func main() {
    client := captchaai.New("YOUR_API_KEY",
        captchaai.WithTimeout(120*time.Second),
        captchaai.WithPollInterval(5*time.Second),
    )

    ctx := context.Background()

    // Check balance
    balance, err := client.GetBalance(ctx)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Balance: $%.2f\n", balance)

    // Solve reCAPTCHA v2
    token, err := client.SolveRecaptchaV2(ctx, captchaai.RecaptchaV2Params{
        SiteKey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
        PageURL: "https://staging.example.com/qa-login",
    })
    if err != nil {
        var apiErr *captchaai.APIError
        if errors.As(err, &apiErr) && apiErr.IsFatal() {
            log.Fatalf("Fatal API error: %s", apiErr.Code)
        }
        log.Fatal(err)
    }
    fmt.Printf("Token: %s...\n", token[:40])

    // Solve with context timeout
    solveCtx, cancel := context.WithTimeout(ctx, 60*time.Second)
    defer cancel()

    turnstileToken, err := client.SolveTurnstile(solveCtx, captchaai.TurnstileParams{
        SiteKey: "0x4AAAAAAADnPIDROrmt1Wwj",
        PageURL: "https://example.com/checkout",
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Turnstile: %s...\n", turnstileToken[:40])
}

故障排除

问题 原因 处理方式
context deadline exceeded 解决花费的时间比上下文超时时间长 使用更长的上下文超时或增加客户端 WithTimeout
captchaai: submit failed (ERROR_ZERO_BALANCE) 没有资金 在 CaptchaAI 仪表板充值
民意调查从未完成 网络问题或 API URL 错误 检查连通性;验证 URL 常量
errors.As 上的编译器错误 缺少导入 "errors" 添加到导入中
未使用自定义 HTTP 客户端 忘记 WithHTTPClient 选项 New() 中的通行证选项:captchaai.New(key, captchaai.WithHTTPClient(myClient))

常问问题

为什么使用 context.Context 而不是简单的超时?

Context 与 Go 的标准取消模式集成。如果父 HTTP 处理程序或 goroutine 被取消,CAPTCHA 解决会立即停止 - 不会有消耗 API 积分的孤立轮询循环。

我如何通过代理使用它?

使用代理传输注入自定义 http.Client。这将通过您的代理路由所有 SDK 流量,而无需修改库。

我应该使用 go install 还是供应商?

对于私人项目,请使用 go mod vendor。对于可重用库,发布为具有语义版本控制的 Go 模块,并让消费者使用 go get 导入。

相关文章

下一步

构建您的 Go CAPTCHA 客户端 –获取您的 CaptchaAI API 密钥并从上面的包开始。

相关指南:

该文章已禁用评论。

相关文章

DevOps & Scaling 用于 CaptchaAI Worker 部署的 Ansible Playbook
使用 Captcha AI Worker 部署 Ansible Playbook 的 Dev Ops 指南,包括生产中 Captcha AI 工作流程的架构决策、操作注意事项和自动化模式。

使用 Captcha AI Worker 部署 Ansible Playbook 的 Dev Ops 指南,包括生产中 Captcha AI 工作流程的架构决策、操作注...

Apr 19, 2026
DevOps & Scaling AWS Lambda + CaptchaAI:无服务器验证码解决
AWS Lambda + Captcha AI 的开发运营指南:无服务器验证码解决方案,包含生产中 Captcha AI 工作流程的架构决策、操作注意事项和自动化模式。

AWS Lambda + Captcha AI 的开发运营指南:无服务器验证码解决方案,包含生产中 Captcha AI 工作流程的架构决策、操作...

Apr 21, 2026
Comparisons 最佳验证码解决服务比较(2025 年)
最佳验证码解决服务比较(2025 年)的实际比较,重点关注 Captcha AI 的成本、准确性、速度和集成工作方面的差异。

最佳验证码解决服务比较(2025 年)的实际比较,重点关注 Captcha AI 的成本、准确性、速度和集成工作方面的差异。

Apr 24, 2026