colly本身不提供分布式能力,所谓“分布式爬取”必须手动分层设计——任务调度、节点隔离、状态共享三者缺一不可;单个colly.collector实例跨机器运行等于无分布式。

直接说结论:Colly 本身不提供分布式能力,所谓“分布式爬取”必须靠你手动分层设计——任务调度、节点隔离、状态共享三者缺一不可;用单个 colly.Collector 实例跨机器跑,等于没分布式。
为什么 colly.NewCollector() 不能直接用于分布式
Colly 的 Collector 是纯内存态对象,所有限流规则(LimitRule)、CookieJar、Visited URL 缓存都只在本进程生效。你在 A 机器上设了 Parallelism: 5,对 B 机器完全无约束;A 节点去重过的 URL,B 节点照样重复请求。更麻烦的是:c.Visit() 发起的请求不会自动广播到其他节点,也不会从共享队列取任务。
常见错误现象:
- 多个 worker 同时抓同一 URL,结果重复入库、触发风控
- 某节点突发大量 429,其他节点毫无感知,仍持续发请求
- 任务分配不均,有的节点空转,有的 OOM 崩溃
必须分层实现:任务调度 + 工作节点 + 状态中心
真实可落地的分布式结构只有这三层,少一层就不是分布式:
-
任务调度层:用
Redis或Kafka存待抓 URL 队列,加唯一 key 去重(如 URL 的 SHA256) -
工作节点层:每个节点运行独立
colly.Collector实例,且必须绑定专属域名限流规则(如&colly.LimitRule{DomainGlob: "*.github.io", Parallelism: 1}) -
状态中心层:用
Redis记录已抓 URL、失败重试次数、QPS 统计;别用本地 map 或文件,否则节点间状态割裂
关键细节:
- 每个工作节点启动时,应从 Redis 队列
BRPOP取一个任务,处理完再LPUSH下级链接(如有),而不是在OnHTML里直接e.Request.Visit()—— 否则递归失控 - 别把
colly.Collector实例全局复用,每个 goroutine 拿到的 collector 必须是新创建或严格隔离的,否则并发写 panic - 所有节点共用同一个
*rate.Limiter实例(通过 Redis + Lua 或共享内存),控制全集群总 QPS,不能只靠单节点LimitRule
防风控关键配置:随机延迟 + UA + 编码强制统一
真实网站拦截第一关就是静态特征,光靠并发控制没用:
- 必须显式设置
c.UserAgent = "Mozilla/5.0 (X11; Linux x86_64)",空 UA 几乎必 403 - 开启 gzip 解压和 UTF-8 强制转换:
c.OnResponse(func(r *colly.Response) { r.Collector.UnifyUTF8 = true }) - 延迟不能固定:
c.Limit(&colly.LimitRule{Delay: 1 * time.Second, RandomDelay: 500 * time.Millisecond}),否则流量模式太规律 - 加 Referer 和 Accept 头:
c.OnRequest(func(r *colly.Request) { r.Headers.Set("Referer", "https://example.com"); r.Headers.Set("Accept", "text/html") })
容易被忽略的坑:
- 目标页返回的是 JS 渲染内容?Colly 不执行 JS,右键“查看网页源代码”搜关键词确认数据是否在原始 HTML 里
- 相对路径链接(如
/news/123)必须转绝对地址:c.AbsoluteURL(href),否则c.Visit()静默失败 - 中文 CSV 保存要加 BOM 头,否则 Excel 打开乱码:
file.Write([]byte("\xef\xbb\xbf"))
并发与资源边界:别迷信 Parallelism 数值
并发数不是越大越好,它直接受限于本地系统资源和目标站反爬强度:
-
Parallelism: 2~4是安全起点,电商详情页建议 ≤2,新闻列表可试 6 - 超过 10 就得同步调大系统限制:
ulimit -n 65536,否则报too many open files - 每个节点的
MaxConcurrentRequests必须配合 DNS 缓存:自定义http.Transport,配.DialContext套net.Resolver缓存,否则高频 DNS 查询拖慢整体速度 - 数据库写入必须复用
*sql.DB实例,并设db.SetMaxOpenConns(10),否则瞬间打爆连接池
最常被跳过的动作:上线前用 c.OnResponse 和 c.OnError 打印真实状态码和错误,而不是只看 OnHTML 是否触发——很多失败根本没走到解析阶段。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











