colly 接入动态代理池必须实现自定义 http.roundtripper,通过 atomic.value 管理可热更新的代理列表,在 roundtrip 中动态选择代理并克隆 transport;不能在 onrequest 中设置 req.proxy,因其不参与连接复用且会被覆盖。

http.Transport 并结合运行时更新的后端列表,否则所有请求都走默认出口,根本达不到“代理池”效果。
Colly 怎么接入动态代理池?
Colly 本身不管理代理,它只负责调度请求和解析响应。真正决定请求从哪个 IP 发出的,是底层 http.Client 的 Transport。直接在 c.OnRequest 里改 req.Proxy 是常见错误——它只影响当前请求,且无法复用连接池、不支持健康检查、无法统一控制超时重试。
- 必须用自定义
http.RoundTripper实现代理选择逻辑,比如轮询、随机或按响应延迟加权 -
RoundTripper内部需维护一个可原子更新的代理列表(推荐atomic.Value或带读写锁的sync.Map) - 每次
RoundTrip调用时,从代理列表中选一个可用项,构造*url.URL传给http.ProxyURL - 别在
RoundTrip里做同步 HTTP 探活,会卡死整个连接池;健康检查应单独起 goroutine 定期异步执行
示例关键片段:
type ProxyRoundTripper struct {
proxies atomic.Value // 存 []string
}
func (p *ProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
proxies := p.proxies.Load().([]string)
if len(proxies) == 0 {
return nil, errors.New("no available proxy")
}
proxyURL, _ := url.Parse(proxies[rand.Intn(len(proxies))])
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.Proxy = http.ProxyURL(proxyURL)
return transport.RoundTrip(req)
}
为什么不能在 OnRequest 里设置 req.Proxy?
因为 req.Proxy 是 *http.Request 字段,而 Colly 的 OnRequest 回调发生在请求构造完成、但尚未交给底层 http.Client 之前。此时设置它看似生效,但实际会被后续流程覆盖——尤其是当 Colly 复用连接池或重试请求时,req 可能被多次复用,导致代理错乱或全部 fallback 到直连。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
-
OnRequest是 Hook,不是 Transport 层,它不参与连接复用决策 - 设置
req.Proxy后,Colly 仍会用默认http.DefaultTransport发请求,该 Transport 忽略req.Proxy - 重试时 Colly 会 clone 原始
req,但不会重新触发OnRequest,代理丢失
代理池如何与 Colly 的并发模型协同?
Colly 默认启动多个 goroutine 并发请求,而代理池的选型必须是线程安全的。若用简单全局变量或未加锁切片,在高并发下极易出现 panic 或返回空代理。
- 代理列表更新(如新增/剔除代理)必须原子化,
atomic.Value.Store是最轻量方案 - 轮询索引不能用普通
int,得用atomic.Uint64避免竞态 - 验证代理可用性不能阻塞
RoundTrip,应另起 goroutine 每 30s 扫描一次,结果写回原子变量 - 避免在
OnResponse中同步更新代理状态——它可能被多个 goroutine 并发调用
负载均衡策略该放在哪一层?
负载均衡不是 Colly 的职责,而是 Transport 层的事。把策略写在 RoundTripper 里,才能确保:连接复用、超时控制、重试机制全部生效;且策略对所有请求一视同仁,包括重试、重定向后的请求。
- 轮询、最少连接、加权随机等算法,必须基于代理的实时健康度(如最近 5 次响应时间均值)计算,而非静态配置
- 不要在 Collector 初始化时硬编码代理列表;运行时通过 API 或文件热更新,用
atomic.Value替换 - 如果后端是 socks5 代理,注意
http.ProxyURL不支持,得用golang.org/x/net/proxy构造 Dialer 并挂到 Transport.DialContext
真正的难点不在代码长度,而在代理状态的一致性维护——你永远不知道哪个代理下一秒就失效,而连接池又不允许你每次请求都探测一遍。所以健康检查必须异步、有缓存、带衰减权重,而不是非黑即白的“在线/离线”二值判断。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










