必须用rate.limiter而非time.sleep实现限速,因其基于令牌桶可精准控qps与突发,支持上下文取消;需全局复用实例、配合复用http客户端及正确关闭resp.body。

并发请求控制必须用 rate.Limiter,别信 time.Sleep
time.Sleep 在高并发下既不准又浪费资源:100 个 goroutine 同时 sleep 100ms,实际可能有 99 个在空等,CPU 毛刺高,还压不住 QPS。真正可控的限速得靠 rate.Limiter。
初始化别写死 rate.Every(100 * time.Millisecond)——它等价于每秒 10 次,但不支持突发;更稳妥的是 rate.NewLimiter(rate.Limit(10), 3),允许最多 3 次突发请求。
- 每次发请求前调用
limiter.Wait(ctx),自然融入上下文取消逻辑 - 如果目标站对单 IP 敏感,可为不同域名配独立
rate.Limiter - 别把限速逻辑塞进 HTTP 回调里——它应在请求发起前、URL 去重后统一拦截
HTTP 客户端必须复用且显式设超时
每个请求都 new 一个 http.Client,很快会报 dial tcp: lookup xxx: no such host 或 too many open files——本质是底层连接没复用,文件描述符耗尽。
全局复用一个 *http.Client 实例,并显式配置 Transport:
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
-
MaxIdleConns和MaxIdleConnsPerHost建议设为 200~500(默认 100 远不够) -
Timeout必须设(如 10s),IdleConnTimeout和TLSHandshakeTimeout也得填(如 90s 和 10s) - 别依赖
http.DefaultClient:它的Timeout是 0(无限等待)
goroutine 泄漏最常见原因是 resp.Body 没关
并发爬虫跑着跑着 OOM,八成不是数据量大,而是 goroutine 没收干净。典型场景:用 http.Do 或 http.Get 发请求后,只取状态码就 return,没读响应体也没关 resp.Body,底层连接池卡住,后续所有依赖该连接的 goroutine 都悬着。
- 每次调用后必须显式
defer resp.Body.Close(),哪怕你只关心resp.StatusCode - 用
pprof快速验证:/debug/pprof/goroutine?debug=2看是否大量net/http.(*persistConn).readLoop卡在 waiting - select 里别丢掉 case 分支——超时或取消时,要同步停止发请求、关闭管道、回收 goroutine
Colly 框架默认配置根本不能直接用
直接调 colly.NewCollector() 会漏抓页面:默认不启用重试、不处理重定向、不设 User-Agent,遇 302/403 就放弃。真实网站多数校验 UA,没设的话大概率被 nginx 拦在第一层。
- 必须显式设
c.UserAgent = "Mozilla/5.0 (X11; Linux x86_64)" - 开重定向:
c.AllowURLRevisit = true(否则重复 URL 被跳过),c.MaxDepth = 3(防无限爬) - 并发控制不是设
c.Async = true就完事,真正起作用的是c.Limit(&colly.LimitRule{DomainGlob: "*", Parallelism: 4}) -
Parallelism: 2~6是较安全起点;不同域名建议分规则限流,比如&colly.LimitRule{DomainGlob: "*.github.io", Parallelism: 1}
真正的难点不在并发数调多大,而在错误分类和连接生命周期管理——403 和 503 的处理逻辑完全不同,而一次 resp.Body.Close() 忘记,就能让整个 worker pool 慢慢僵死。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










