Go Colly 异步采集为什么程序先退出
Go Colly 开启 Async 后,Visit 通常在请求被提交后就返回,main 不会自动等待这些异步工作。把 c.Wait() 放在入口请求提交完成之后、main 返回之前,才能让该 Collector 的工作有机会完成。不要用固定休眠代替等待,也不要在回调内部等待同一个 Collector。
官方地址:https://go-colly.org/
Async改变的是请求返回方式
Colly 支持同步和异步采集;这不是一条新版本发布消息,而是使用现有能力时容易忽略的生命周期差异。一个命令行任务循环调用 Visit,随后立即打印“结束”,可能只完成了提交,并没有收到响应或完成解析。
Go 程序在 main 返回时退出,不会等其他 goroutine。这个规则由 Go 语言规范说明。异步请求已经启动,不代表程序已经承担了等待责任。
Colly 的 官方实现为请求登记等待计数,异步模式通过 goroutine 获取响应;Wait 等待这份计数归零。它表达的是 Collector 工作边界,而不是某次 Visit 返回了 nil 就代表整项业务成功。

短生命周期任务要保留等待位置
一次性批处理、命令行导出和有限页面提取最容易遇到这个问题:入口请求提交完,主函数就没有其他阻塞点。正确的等待位置属于负责收尾的调用方,不属于响应回调。
先注册 OnHTML、OnError 等回调,再提交这一批入口 URL,最后在主函数调用 Wait。官方 并行采集示例也采用提交后等待的组织方式。如果另有 goroutine 负责未来才提交入口请求,还需要先协调生产者;Wait 不是“预知所有未来任务”的机制。
不要在 OnHTML 或 OnScraped 中调用同一个 c.Wait():当前工作还没退出,等待者却在当前工作里,容易形成自己等待自己完成的死锁。等待前也不应提前关闭本地服务或任务所依赖的输出资源。
用本地页面写出完整等待示例
下面只请求程序自己创建的两个本地页面,避免外站权限、页面变化和选择器漂移干扰判断。在 Go module 中添加 Colly v2 依赖,将代码保存为 main.go。这里不执行示例、不提供伪造日志;读者可以按自己的环境运行。
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"sync/atomic"
"time"
"github.com/gocolly/colly/v2"
)
func main() {
// 自有本地样本,只提供两个已知页面,不遍历外站。
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, "样本 %s
", r.URL.Path)
}))
defer srv.Close()
c := colly.NewCollector(colly.Async(true))
c.SetRequestTimeout(3 * time.Second)
var titles, failed int64
// 异步回调可能并发执行,用原子计数避免共享计数竞态。
c.OnHTML("h1", func(e *colly.HTMLElement) {
atomic.AddInt64(&titles, 1)
log.Printf("标题: %s", e.Text)
})
c.OnError(func(r *colly.Response, err error) {
atomic.AddInt64(&failed, 1)
log.Printf("请求失败: %v", err)
})
for _, path := range []string{"/alpha", "/beta"} {
// Visit的即时错误与后续HTTP错误分开记录。
if err := c.Visit(srv.URL + path); err != nil {
atomic.AddInt64(&failed, 1)
log.Printf("提交失败: %v", err)
}
}
// 所有入口请求已提交,在main返回和服务关闭前等待。
c.Wait()
log.Printf("收尾: 标题=%d 失败=%d",
atomic.LoadInt64(&titles), atomic.LoadInt64(&failed))
}
代码里的三个重点可以分别核对:回调在 Visit 前注册;Wait 位于两个入口提交之后;srv.Close 使用 defer,在主函数真正收尾时关闭服务。标题计数与错误计数采用原子操作,因为不同响应回调可能并发修改它们。
读者运行时,在没有请求错误且两个页面都匹配 h1 的条件下,标题计数应为 2,失败计数为 0;两个标题日志的先后顺序没有保证。收尾日志写在 Wait 之后,不能将“访问函数返回”与“回调已经完成”混淆。

与同步模式和固定休眠比较
| 写法 | 主要差别 | 使用注意 |
|---|---|---|
| 同步Collector | 调用方等待单次请求处理 | 少量页面时更直观 |
| Async配合Wait | 请求可并发,统一等待收尾 | 共享数据需要同步保护 |
| 固定Sleep | 仅延迟指定时间 | 无法判断请求是否完成 |
如果原来是同步写法,增加 Async 并不是只改一行参数:还要明确谁等待、资源什么时候关闭、哪些回调共享状态。固定 Sleep 看似能让日志出现,但网络变慢时仍可能提前退出,网络变快时又徒增等待。应围绕完成条件组织代码,而不是估算时间。
等待结束不等于业务全部成功
Wait 没有错误返回值。Visit 的即时错误和异步过程的 OnError 需要分别处理;没有 OnHTML 日志,也可能是页面未匹配、请求失败或输入不对,不一定是程序先退出。示例用独立错误计数说明这一点,不把等待结束解释成零错误。
回调如果自己再启动写数据库或落盘 goroutine,那些工作不自动属于 Colly 的等待计数。需要额外的完成协调,或者在回调内完成有限的写入。不要在后台写入还未完成时关闭文件或连接。
若修复等待后程序反而长时间不结束,查看请求超时和回调是否阻塞。Wait 本身不提供超时参数;示例设置的是请求超时,不是整个任务的截止时间。采集范围应保持有限,实际外站任务须取得授权并遵守访问规则,避免用扩大并发掩盖生命周期问题。
只调用一次Visit也要Wait吗?
在异步模式下,只要主函数可能提前结束,就应等待。请求数量为一不改变生命周期要求。
Wait要写在每次Visit后面吗?
有限批次通常在所有入口提交后统一等待;每次立即等待会削弱并发提交的意义。
为什么Wait返回后输出文件还是空的?
先区分回调未命中、请求错误和额外后台写入;Collector 的等待不替业务输出做成功判定。
解决提前退出的核心是把 Wait 放在正确的收尾边界。保留错误记录、保护共享状态,并明确额外后台任务的责任,才能让“任务结束”真正对应你的业务完成条件。
特效变音魔术师能在通话中实时变声吗?录音处理与跨应用边界
- 上一篇
- 特效变音魔术师能在通话中实时变声吗?录音处理与跨应用边界
- 下一篇
- Go Colly 同一个地址第二次 Visit 为什么被拒绝
-
- Golang · Go问答 | 9分钟前 |
- Go Participle 增加前瞻和重写歧义语法有什么区别
- 426浏览 收藏
-
- Golang · Go问答 | 10分钟前 | Go问答 · Go 注释 ast 语法树 Participle Elide
- Go Participle 被 Elide 的注释为何不出现在语法树
- 198浏览 收藏
-
- Golang · Go问答 | 12分钟前 | go · Go 递归下降解析器 Participle 左递归
- Go Participle 左递归语法为什么无法直接解析
- 240浏览 收藏
-
- Golang · Go问答 | 20分钟前 |
- Go Goldmark 软换行与硬换行渲染结果有什么区别
- 161浏览 收藏
-
- Golang · Go问答 | 22分钟前 | Markdown · Go问答 · Go Goldmark parser.Context Markdown 转换 解析上下文 状态复用
- Go Goldmark 多次转换时为什么不能复用可变解析上下文
- 252浏览 收藏
-
- Golang · Go问答 | 32分钟前 |
- Go SJSON 原地替换选项为什么不能任意扩展字节长度
- 473浏览 收藏
-
- Golang · Go问答 | 35分钟前 |
- Go SJSON 数组末尾追加元素时路径应该怎么写
- 408浏览 收藏
-
- Golang · Go问答 | 44分钟前 |
- Go GJSON 数组条件中的单个匹配与全部匹配有什么区别
- 242浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 460次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 521次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 539次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 487次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 307次使用
-
- 用Nginx反向代理部署go写的网站。
- 2023-01-17 502浏览
-
- GoLand调式动态执行代码
- 2023-01-13 502浏览
-
- Go crypto/rand.Text 的长度为什么不是固定字符数
- 2026-10-04 501浏览
-
- Go strings.ToValidUTF8 清洗日志内容的边界
- 2026-10-03 501浏览
-
- Go tls.GetCertificate 为什么收不到空 ServerName 请求
- 2026-09-27 501浏览

