登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go Colly 怎么限制公开页面采集的并发和间隔

来源:17golang原创

时间:2026-10-13 15:44:53 285浏览 收藏

我第一次把 Colly 切到异步采集时,最先看到的不是速度提升,而是请求一下子挤到目标站点:代码里虽然写了 Async(),却没有给每个域名设置节奏。解决这类问题的关键不是在回调里手写一个全局 time.Sleep,而是给 Collector 配置 LimitRule,让域名、并发和间隔成为一组明确的策略。

这篇只讨论允许访问的公开页面采集。最实用的判断是:想限制“同时在飞”的请求,用 Parallelism;想让同一域名的请求按时间拉开,用 Delay,必要时再加 RandomDelay。如果启用了异步采集,最后还要调用 Wait(),否则主函数可能在回调完成前退出。

官方地址:https://github.com/gocolly/colly

LimitRule 到底限制了什么

Colly 的 LimitRule 先用 DomainGlob 或 DomainRegexp 找到要约束的域名,再决定如何限制连接。官方 API 文档将它拆成两个核心方向:Parallelism 是匹配域名允许同时存在的最大请求数;Delay 是创建下一次请求前要等待的时间,RandomDelay 则是在固定间隔上再增加一个随机等待。

这里有一个很容易踩到的语义:当规则使用 Delay 时,官方说明将该规则视为串行节奏,也就是并发按 1 处理。不要把 Delay: 2 * time.Second 和 Parallelism: 4 理解成“4 个并发、每个之间都延迟 2 秒”;如果目标是并发上限,就先只配 Parallelism,如果目标是同域请求之间的时间间隔,就使用 Delay。

Go Colly LimitRule 中域名匹配、并发上限和请求间隔的关系结构图
图1:Colly LimitRule 的域名匹配、并发上限与请求间隔关系说明图,不是运行截图或采集证据。

先把异步开关和限流参数分开

Async 解决的是 Collector 是否并行发起网络工作,不是目标域名的流量配额。开启它后,HTML 回调中继续调用 Visit 也不会自动变成“无限制采集”;真正的同域请求上限要交给 LimitRule。反过来,只配了 Parallelism,也不代表整个程序会自动等待所有异步请求结束。

下面的示例给一个公开站点设置最多两个并发请求,并在主流程结束前等待所有采集任务。示例里的域名只是演示匹配范围,换成你明确获准访问的站点,并遵守其 robots.txt、服务条款和访问频率要求。

package main

import (
	"log"
	"time"

	"github.com/gocolly/colly/v2"
)

func main() {
	// Async 只打开并行网络工作,不能代替每个域名的限流规则。
	c := colly.NewCollector(
		colly.Async(),
		colly.AllowedDomains("example.com"),
	)

	// DomainGlob 限定规则作用域,Parallelism 限制同域同时在飞的请求数。
	if err := c.Limit(&colly.LimitRule{
		DomainGlob:  "example.com",
		Parallelism: 2,
	}); err != nil {
		// 规则初始化失败时立即退出,避免用未预期的并发继续采集。
		log.Fatal(err)
	}

	c.OnHTML("a[href]", func(e *colly.HTMLElement) {
		// 回调内只提交符合范围的链接,实际请求仍由 Collector 调度。
		if err := e.Request.Visit(e.Attr("href")); err != nil {
			log.Printf("visit failed: %v", err)
		}
	})

	if err := c.Visit("https://example.com/"); err != nil {
		log.Fatal(err)
	}
	// Async 模式必须等待,否则进程可能在回调和请求完成前退出。
	c.Wait()
}

这段代码适合“可以有少量并发,但不希望同域请求无限增长”的场景。Parallelism: 2 是单个匹配域名的上限,不是所有目标域名的全局上限;如果采集器还会访问别的域名,需要给它们分别配置规则,或在业务层进一步控制总任务量。

需要固定间隔时怎么写

如果目标站点更关心请求之间的节奏,而不是尽可能多地同时处理请求,就使用 Delay。它表达的是“向匹配域名创建下一次请求前至少等待多久”。为了避免请求呈现完全固定的节拍,可以加上 RandomDelay,让每次等待在固定间隔之上增加随机时长。

package main

import (
	"log"
	"time"

	"github.com/gocolly/colly/v2"
)

func buildCollector() (*colly.Collector, error) {
	// 这里不打开 Async,让示例把“请求间隔”作为主要节奏;需要异步时可按任务模型开启。
	c := colly.NewCollector(colly.AllowedDomains("example.com"))

	// Delay 是固定等待,RandomDelay 是额外随机等待;该规则按串行节奏处理。
	if err := c.Limit(&colly.LimitRule{
		DomainGlob:  "example.com",
		Delay:       1500 * time.Millisecond,
		RandomDelay: 500 * time.Millisecond,
	}); err != nil {
		return nil, err
	}

	c.OnError(func(r *colly.Response, err error) {
		// 记录 URL 和错误,便于区分站点响应问题与本地解析问题。
		log.Printf("url=%s status=%d err=%v", r.Request.URL, r.StatusCode, err)
	})
	return c, nil
}

固定间隔不是“保证每次响应都相隔固定秒数”。网络响应时间、回调处理和站点本身排队都会影响端到端耗时;它主要约束的是 Colly 创建下一次匹配请求的节奏。RandomDelay 也不是精准的速率控制器,而是给固定间隔增加一个随机上界,适合不希望请求呈现机械节拍的工程场景。

多站点采集怎么拆规则

我觉得最稳妥的做法是“一个站点一条规则”:把允许的域名、并发上限、固定间隔和随机间隔放在同一条规则里,避免用一套参数覆盖完全不同的服务。DomainGlob 适合简单的域名匹配;需要更细的边界时使用 DomainRegexp,并提前确认它实际能匹配到目标主机名。

package main

import (
	"log"
	"time"

	"github.com/gocolly/colly/v2"
)

func newMultiSiteCollector() *colly.Collector {
	// 让 Collector 负责异步调度,但把每个站点的节奏拆开管理。
	c := colly.NewCollector(colly.Async())

	// 新闻站点只允许少量并发,避免一个站点占满采集器。
	if err := c.Limit(&colly.LimitRule{
		DomainGlob:  "news.example.com",
		Parallelism: 2,
	}); err != nil {
		log.Fatal(err)
	}

	// 文档站点采用固定间隔,并额外增加一个小的随机等待。
	if err := c.Limit(&colly.LimitRule{
		DomainGlob:  "docs.example.org",
		Delay:       2 * time.Second,
		RandomDelay: 800 * time.Millisecond,
	}); err != nil {
		log.Fatal(err)
	}

	return c
}
Go Colly 为不同公开站点分别配置限流规则的结构关系图
图2:多个公开站点分别绑定 Colly 限流规则的静态边界说明图,不是浏览器或终端截图。

这两条规则的重点不是参数大小,而是作用域隔离:news.example.com 的两个并发不会自动变成 docs.example.org 的两个并发。对于同一组织下的多个子域名,要先决定限制对象是每个主机名,还是需要用更宽的匹配方式把它们纳入同一策略;不要只看 URL 路径就假设它们共享一个额度。

为什么设置了限制却感觉没有生效

  • 规则没有匹配域名:先检查 DomainGlob 或正则表达式对应的是主机名,而不是带协议的完整 URL。
  • 把 Async 当成限流器:Async 只影响调度方式,必须额外设置 LimitRule。
  • 把 Delay 和 Parallelism 当成叠加:使用 Delay 的规则按串行间隔理解,不要期待它同时保持高并发。
  • 忘记 Wait:异步任务还没有结束,程序就退出时,看起来像是只处理了一部分链接。
  • 只限制了入口域名:页面中的绝对链接可能指向别的主机名;要么收窄 AllowedDomains,要么为需要访问的站点分别配置规则。
  • 把回调耗时算成请求间隔:解析、写文件或入库在回调中执行时,会改变整体吞吐,不能只用它推断 LimitRule 的效果。

我会怎样收口参数

我通常先从较保守的配置开始:单站点任务用 Parallelism: 1 或一个明确的 Delay,确认错误率、响应时间和目标站点的访问约束后,再逐步提高并发。公开页面采集不应该以“越快越好”为唯一目标,稳定完成、可恢复和不影响对方服务更重要。

目标优先参数需要留意
限制同域同时请求数Parallelism只限制匹配域名,不是整个进程的总任务数
拉开同域请求时间Delay规则按串行节奏处理,不能理解为高并发加睡眠
避免固定节拍Delay + RandomDelay随机等待是额外范围,不是精确速率计
保证异步采集收尾c.Wait()放在入口任务提交之后,避免进程提前结束

归纳起来,Go Colly 限制公开页面采集并发和间隔的入口就是 LimitRule:先用域名把边界划清,再在并发上限和时间间隔之间选清楚语义。我的经验是,宁可先采用较小并发和可观察的间隔,再依据真实任务结果调整,也不要把全局 Sleep 塞进回调里,让不同站点互相拖慢。

相关问题

Colly 的 Async 会不会自动限制并发?

不会。Async 负责异步网络调度,匹配域名的最大并发需要通过 LimitRule 的 Parallelism 设置。

Delay 和 RandomDelay 应该一起使用吗?

不一定。只需要固定间隔时使用 Delay;不希望每次都呈现完全固定的节拍时,再在它上面增加 RandomDelay。

能不能用一个 LimitRule 限制所有站点?

可以使用更宽的匹配方式,但通常不建议直接套用同一参数。不同站点的承载能力和访问约束可能不同,拆成多条规则更容易解释和调整。

为什么调用了 Wait 还是很慢?

Wait 只负责等待异步任务结束,不会提高吞吐。实际速度还会受到 Delay、RandomDelay、Parallelism、网络响应、回调处理和站点响应时间影响。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>