Go爬虫必须动态换User-Agent且轮换代理,否则几分钟即被封;固定UA会被秒识别并触发403或验证码,需每次请求前从多设备UA池随机选取,并用带校验、认证和重试的代理获取函数替代简单轮换。

直接上结论:Go语言爬虫光靠硬编码一个 User-Agent 或写死一个代理地址,基本跑不过 5 分钟就会被封;必须同时做两件事——每次请求动态换 User-Agent,且每个请求走不同代理(或至少轮换),缺一不可。
为什么固定 User-Agent 会被秒识别
目标网站看到连续几十个请求都带着 Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ... 这种一模一样的字符串,根本不用查 IP,直接 403 或返回验证码页。这不是“可能被发现”,而是反爬系统默认触发的最基础规则。
常见错误现象:
- 用
req.Header.Set("User-Agent", fixedUA)全局设一次,所有请求共用 - 从网上抄了 5 条 UA 字符串,但没做随机取值,实际只用了第一条
- UA 字符串里含空格或括号未转义,导致 HTTP 请求头解析失败,服务端收到空 UA
正确做法是:每次构造 *http.Request 前,从真实、多版本、跨设备的 UA 池中随机取一条。例如:
userAgents = []string{
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0",
}
// 每次请求前:
randIdx := rand.Intn(len(userAgents))
req.Header.Set("User-Agent", userAgents[randIdx])
代理轮换不能只靠 proxy.RoundRobinProxySwitcher
Colly 的 proxy.RoundRobinProxySwitcher 看起来开箱即用,但它只解决“按顺序切代理”,不解决三个关键问题:代理失效、认证缺失、无失败重试逻辑。
实际使用中容易踩的坑:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 传入的代理 URL 格式错误,比如漏了
http://前缀,url.Parse()返回 nil 导致 panic - 代理需要用户名密码(如
http://user:pass@1.2.3.4:8080),但 Go 标准库http.Transport不自动加Proxy-Authorization头,得手动补 - 某个代理响应超时或返回 407,
RoundRobinProxySwitcher仍会继续轮到它,后续请求全卡住
推荐封装一个带校验的代理获取函数:
RandomUserAgent
- 扫公开静态页(如新闻列表):用 Colly 内置扩展够用
- 登录态采集、电商详情页、JS 渲染页:必须自己维护 UA 池,包含移动端、旧版 Safari、甚至微信内置浏览器 UA
- 目标站明确限制“仅允许 iOS 用户访问”:你再怎么轮换 Chrome UA 也没用,得强制用
iPhone类 UA
参数差异注意点:
- Colly 的扩展在
c.OnRequest钩子中注入 UA,但如果中间用了c.Clone()或并发新建 Collector,每个实例需单独启用 - 扩展不处理
Accept-Encoding、Sec-Ch-Ua等 Chromium 新增头,这些字段和 UA 不匹配也会被识别为异常请求
并发请求下 UA 和代理的绑定关系必须可控
很多人以为“开了 10 个 goroutine,每个都随机 UA + 随机代理”就安全了,但实际出问题的是绑定逻辑错乱:比如 goroutine A 拿到代理 P1 后,还没发请求,goroutine B 把 P1 从池里取走了,A 发请求时用的其实是空代理或旧配置。
关键控制点:
- UA 和代理必须成对生成、成对使用,不能“UA 来自池 A,代理来自池 B”
- 如果用全局变量存当前 UA/代理,必须加
sync.Mutex,否则高并发下读写竞争导致 UA 和代理错配 - 更稳妥的做法是:每个 worker goroutine 初始化时独立获取一组 UA+代理,并在该 goroutine 生命周期内复用(配合 jitter 延迟),避免频繁争抢共享资源
真正难的不是“怎么换”,而是“换得干净、换得一致、换得不露痕迹”。UA 和代理只是表层,背后要对齐的还有 Referer、Cookie 生命周期、TLS 指纹、HTTP/2 支持状态——这些一旦脱节,服务器一眼就能看出你在模拟。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










