应使用 rate.Limiter 替代 time.Sleep 实现限速,基于令牌桶支持匀速与突发控制,需合理设置速率和 burst 值、传入带取消的 ctx、配合 HTTP 连接池复用、robots.txt 解析、User-Agent 规范及前置去重。

限速不是加个 time.Sleep 就完事——它会导致 goroutine 积压、QPS 不稳、上下文取消失效,还容易被目标站识别为异常流量。
用 rate.Limiter 替代 time.Sleep 做真正可控的限速
直接在请求前调用 limiter.Wait(ctx),比 sleep 更准、更轻、天然支持 cancel。它基于 token bucket,能同时控匀速和突发。
- 别写
rate.Every(100 * time.Millisecond)—— 这等价于rate.Limit(10),但没设 burst,实际并发可能突增到 1 - 推荐初始化:
rate.NewLimiter(rate.Limit(10), 3),表示「每秒最多 10 次,允许最多 3 次瞬时并发」 - 必须传入带超时或取消信号的
ctx,否则Wait可能永远阻塞 - 如果目标站对单 IP 限流严格,可为不同域名配独立
rate.Limiter实例
HTTP 客户端复用 + 连接池调优是限速生效的前提
限速再准,客户端不复用连接也会被 too many open files 或 DNS 卡住拖垮——因为每个新请求都新建 TCP 连接,rate.Limiter 控的是“发请求”动作,不是“建连接”动作。
- 全局只用一个
*http.Client,不要每次请求 new 一个 -
Transport必须设:MaxIdleConns: 50、MaxIdleConnsPerHost: 50、IdleConnTimeout: 30 * time.Second - 禁用
ExpectContinueTimeout(默认 1s),避免小请求卡在等待 100-continue - 若目标站支持 HTTP/2,确保 Go 版本 ≥ 1.6 且未显式禁用
Robots.txt 解析与 User-Agent 设置不是可选项,而是限速逻辑的一部分
跳过 /robots.txt 或伪造 UA,常导致 429/403 立即返回,让限速策略完全失效——你不是在“限速抓取”,而是在“高频触发封禁”。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
立即学习“go语言免费学习笔记(深入)”;
- 用
golang.org/x/net/robotstxt解析,调用txt.TestAgent(path, "your-bot")判断是否允许抓取 -
User-Agent必须真实可追溯,例如"my-crawler/1.2 (+https://example.com/bot)",不能填浏览器 UA - 若
robots.txt返回 5xx 或超时,应退避重试,而不是跳过;可用指数退避 +time.AfterFunc - 某些站点在
robots.txt中声明Crawl-delay: 2,这个值应和你的rate.Limiter配置对齐,而非忽略
去重与任务分发不配合限速,等于白做
URL 去重发生在请求发出前,但很多人把去重逻辑放在 OnResponse 或下游 goroutine 里——结果是重复 URL 已经排队进 limiter,占着令牌不干活。
- 去重必须前置:用单个 goroutine 消费待抓队列(
chan string),查sync.Map或布隆过滤器,确认未抓过才放进 limiter 等待队列 - 别用
map[string]bool做并发去重——几秒内必 panic: concurrent map writes - 如果用了
colly,注意它的c.Visit()是异步入队,需配合c.Limit()和自定义URLFilter,否则去重和限速不同步 - 发现新链接后,不要立刻
go fetch(url),而是推入统一任务 channel,由限速 goroutine 统一分发
最易被忽略的一点:限速单位是「请求发起」,不是「响应完成」。一个慢请求卡住 5 秒,会吃掉 50 个 100ms 间隔的令牌——所以 burst 值不能拍脑袋定,得结合 P95 响应延迟预估。

















