用 Colly 写 Go 爬虫,最常卡住的地方就是页面弹出 reCAPTCHA 或 Cloudflare Turnstile——Colly 不会自动处理验证码。思路很简单:检测到验证码时调用 CaptchaAI 的 HTTP API 拿到 token,再提交回目标页面,抓取继续。下面是完整的 Go 客户端和 Colly 接入代码。
涉及内容:CaptchaAI Go 客户端封装、Colly 回调接入、限流配置、Turnstile 识别,以及国内网络环境下的排查思路。
开始之前
开始接入前,先确认三件事:
- 已注册 CaptchaAI 账号并拿到 API Key,免费额度足够跑通本文的示例。
- 项目里已经引入 Colly:
go get github.com/gocolly/colly/v2。 - 如果是在国内网络环境下开发,先执行
go env -w GOPROXY=https://goproxy.cn,direct,避免go get超时卡住。
CaptchaAI Go 客户端
先封装一个最小客户端,负责提交任务和轮询结果:
package captchaai
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
type Client struct {
APIKey string
HTTPClient *http.Client
}
type apiResponse struct {
Status int `json:"status"`
Request string `json:"request"`
}
func NewClient(apiKey string) *Client {
return &Client{
APIKey: apiKey,
HTTPClient: &http.Client{Timeout: 30 * time.Second},
}
}
func (c *Client) SolveRecaptchaV2(sitekey, pageurl string) (string, error) {
// Submit task
data := url.Values{
"key": {c.APIKey},
"method": {"userrecaptcha"},
"googlekey": {sitekey},
"pageurl": {pageurl},
"json": {"1"},
}
resp, err := c.HTTPClient.PostForm("https://ocr.captchaai.com/in.php", data)
if err != nil {
return "", fmt.Errorf("submit error: %w", err)
}
defer resp.Body.Close()
var result apiResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", fmt.Errorf("decode error: %w", err)
}
if result.Status != 1 {
return "", fmt.Errorf("submit failed: %s", result.Request)
}
taskID := result.Request
// Poll for result
time.Sleep(15 * time.Second)
for i := 0; i < 24; i++ {
pollURL := fmt.Sprintf(
"https://ocr.captchaai.com/res.php?key=%s&action=get&id=%s&json=1",
c.APIKey, taskID,
)
resp, err := c.HTTPClient.Get(pollURL)
if err != nil {
time.Sleep(5 * time.Second)
continue
}
var pollResult apiResponse
json.NewDecoder(resp.Body).Decode(&pollResult)
resp.Body.Close()
if pollResult.Status == 1 {
return pollResult.Request, nil
}
if pollResult.Request != "CAPCHA_NOT_READY" {
return "", fmt.Errorf("solve error: %s", pollResult.Request)
}
time.Sleep(5 * time.Second)
}
return "", errors.New("solve timeout")
}
在 Colly 里接入验证码识别
在 OnHTML 回调里检测 data-sitekey,触发识别并把 token 提交回表单:
package main
import (
"fmt"
"log"
"os"
"strings"
"github.com/gocolly/colly/v2"
)
func main() {
apiKey := os.Getenv("CAPTCHAAI_API_KEY")
solver := captchaai.NewClient(apiKey)
c := colly.NewCollector(
colly.AllowedDomains("example.com"),
colly.MaxDepth(2),
)
// Detect CAPTCHA pages
c.OnHTML("[data-sitekey]", func(e *colly.HTMLElement) {
sitekey := e.Attr("data-sitekey")
pageURL := e.Request.URL.String()
log.Printf("CAPTCHA detected on %s, solving...", pageURL)
token, err := solver.SolveRecaptchaV2(sitekey, pageURL)
if err != nil {
log.Printf("Solve failed: %v", err)
return
}
log.Printf("CAPTCHA solved, token length: %d", len(token))
// Post form with token
err = c.Post(pageURL, map[string]string{
"g-recaptcha-response": token,
})
if err != nil {
log.Printf("Form submit failed: %v", err)
}
})
// Extract data
c.OnHTML("table tr", func(e *colly.HTMLElement) {
cols := []string{}
e.ForEach("td", func(_ int, td *colly.HTMLElement) {
cols = append(cols, strings.TrimSpace(td.Text))
})
if len(cols) > 0 {
fmt.Printf("Row: %s\n", strings.Join(cols, " | "))
}
})
c.OnError(func(r *colly.Response, err error) {
log.Printf("Error %s: %v", r.Request.URL, err)
})
c.Visit("https://example.com/data")
}
给 Colly 设置限流
抓取频率太高容易触发验证码或被限制访问,建议在 Colly 里配一条限流规则:
package main
import (
"time"
"github.com/gocolly/colly/v2"
)
func main() {
c := colly.NewCollector()
// Rate limit: 1 request per 3 seconds per domain
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Parallelism: 1,
Delay: 3 * time.Second,
RandomDelay: 2 * time.Second,
})
// ... CAPTCHA handling as above ...
c.Visit("https://example.com")
}
用 Go 解决 Cloudflare Turnstile
目标站点用 Turnstile 而不是 reCAPTCHA 时,思路一样,只是把 method 换成 turnstile:
func (c *Client) SolveTurnstile(sitekey, pageurl string) (string, error) {
data := url.Values{
"key": {c.APIKey},
"method": {"turnstile"},
"sitekey": {sitekey},
"pageurl": {pageurl},
"json": {"1"},
}
resp, err := c.HTTPClient.PostForm("https://ocr.captchaai.com/in.php", data)
if err != nil {
return "", fmt.Errorf("submit error: %w", err)
}
defer resp.Body.Close()
var result apiResponse
json.NewDecoder(resp.Body).Decode(&result)
if result.Status != 1 {
return "", fmt.Errorf("submit failed: %s", result.Request)
}
// Poll (same as reCAPTCHA)
time.Sleep(5 * time.Second)
for i := 0; i < 20; i++ {
pollURL := fmt.Sprintf(
"https://ocr.captchaai.com/res.php?key=%s&action=get&id=%s&json=1",
c.APIKey, result.Request,
)
resp, err := c.HTTPClient.Get(pollURL)
if err != nil {
time.Sleep(3 * time.Second)
continue
}
var pr apiResponse
json.NewDecoder(resp.Body).Decode(&pr)
resp.Body.Close()
if pr.Status == 1 {
return pr.Request, nil
}
if pr.Request != "CAPCHA_NOT_READY" {
return "", fmt.Errorf("error: %s", pr.Request)
}
time.Sleep(3 * time.Second)
}
return "", errors.New("timeout")
}
轮询参数速查
reCAPTCHA v2 和 Turnstile 的轮询节奏不同,上面两段客户端代码已经按下表配置好:
| 类型 | 首次等待 | 轮询间隔 | 最大轮询次数 |
|---|---|---|---|
| reCAPTCHA v2 | 15 秒 | 5 秒 | 24 次 |
| Turnstile | 5 秒 | 3 秒 | 20 次 |
按最大轮询次数估算,reCAPTCHA v2 最长等待约 135 秒,Turnstile 最长约 65 秒,正常识别通常比这个上限快得多。
抓取国内站点的一个实用提示
国内网站更常见 GeeTest(极验)而不是 reCAPTCHA。遇到 GeeTest v3,把 method 换成 geetest 即可复用同一套轮询逻辑。国内主流验证码产品目前的支持情况:
- GeeTest(极验)—— CaptchaAI 支持 v3,v4 目前是即将支持。
- 腾讯防水墙、阿里云验证码、网易易盾 —— CaptchaAI 暂不支持,遇到这些产品需要用其他方式处理。
抓取国内网站涉及数据采集时,建议先对照《网络安全法》《数据安全法》和 PIPL(个人信息保护法)确认采集范围在授权之内,同时遵守目标站点的 robots 协议——这不是法律建议,只是提醒你在写抓取脚本前先想清楚这一步。
常见错误排查
轮询一直返回 CAPCHA_NOT_READY 怎么办?
这是正常状态,说明 CaptchaAI 还在识别中,不是报错。等到最大轮询次数用完仍未成功,再检查 sitekey 和 pageurl 是否取值正确。
token 提交后表单还是失败?
先确认字段名和验证码类型匹配——reCAPTCHA 用 g-recaptcha-response,Turnstile 用 cf-turnstile-response。再确认 token 没有在提交前超时失效,token 通常只有一两分钟的有效期。
常见问题
Colly 遇到验证码会自动报错吗?
不会。Colly 请求照常返回,拿到的只是验证码页面而不是目标数据。需要你在 OnHTML 回调里检测 [data-sitekey],调用 CaptchaAI 识别拿到 token 后再提交表单。
Colly 能配合无头浏览器一起用吗?
可以。静态页面用 Colly 处理更快,动态渲染或需要交互的页面切换到 chromedp 或 rod,两者可以在同一个项目里搭配使用,各自负责擅长的部分。
Go 项目需要装官方 SDK 吗?
不需要。CaptchaAI 走标准 HTTP 接口,net/http 直接发请求即可,本文的 Client 结构体就是最小封装示例。
轮询间隔怎么调?
参考上面的轮询参数表:reCAPTCHA v2 先等 15 秒再每 5 秒查一次,Turnstile 更快,先等 5 秒之后每 3 秒即可。超时上限可以按目标站点的识别难度适当放宽。
相关指南
给 Go 爬虫接入验证码识别——试用 CaptchaAI。