recover必须在每个可能panic的goroutine内部用defer调用才有效,仅捕获本协程panic,跨goroutine无效;需配合显式资源清理与前置校验,不可替代错误处理。

recover 在爬虫 goroutine 中根本不起作用?
直接在启动爬虫的 goroutine 里 defer recover() 是无效的——Go 的 recover() 只对**当前 goroutine 内 panic** 生效,且必须在 defer 中调用。如果爬虫逻辑在子 goroutine(比如 go crawlPage(url))里 panic,主 goroutine 的 recover() 完全捕获不到。
常见错误写法:
go func() {
defer recover() // ❌ 这里 recover 永远不执行,因为 panic 发生在别的 goroutine
crawlPage("https://example.com")
}()
正确做法是:每个可能 panic 的 goroutine 内部,自己做 defer recover()。
如何给每个爬虫 goroutine 加上独立 recover
把 recover 包裹成可复用的“安全执行器”,避免重复写 defer 模板。关键点是:recover 后别吞掉错误,至少要 log;同时确保 panic 不扩散、不阻塞调度。
- 用匿名函数封装,传入实际任务函数和参数
-
recover()必须紧跟在 defer 后,且不能被 if/else 等打断 - 恢复后建议 sleep 短暂时间(如
time.Sleep(100 * time.Millisecond)),避免高频 panic 导致 CPU 打满 - 不要在 recover 里重试原任务——那可能造成无限 panic 循环
示例:
func safeGo(f func()) {
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("panic recovered: %v", r)
time.Sleep(100 * time.Millisecond)
}
}()
f()
}()
}
调用:safeGo(func() { parseHTML(resp.Body) })
哪些地方最容易 panic 导致爬虫崩掉
不是所有 panic 都来自业务逻辑,很多源于 HTTP 和 HTML 处理的边界情况。这些地方不加 recover,一次异常就 kill 整个 goroutine:
-
json.Unmarshal(nil, &v)→ panic: "invalid memory address" -
doc.Find("div.item").Each(...)中 doc 为 nil(比如goquery.NewDocumentFromReader(nil)) -
resp.Body.Read(...)前没检查resp == nil || resp.Body == nil - 正则匹配空字符串:
regexp.MustCompile("").FindStringSubmatch(...)不 panic,但MustCompile传空串会直接 panic - 并发 map 写:多个 goroutine 同时写一个未加锁的
map[string]int
建议在所有外部输入(HTTP 响应、HTML 字符串、JSON 字节流)进入解析前,先做非空/有效性校验,比靠 recover 更高效。
recover 无法替代错误处理和资源清理
recover() 是兜底手段,不是错误处理主力。它救不了已泄露的资源:
- panic 发生时,
defer os.File.Close()不会执行 → 文件句柄泄漏 - HTTP 连接未
resp.Body.Close()→ 连接池耗尽,后续请求 hang 住 - 数据库事务未 rollback → 锁残留或数据不一致
所以必须坚持:所有可能失败的 I/O 操作,都要显式检查 err;所有需要释放的资源,都用 defer 在函数入口附近注册关闭逻辑——哪怕这个函数内部还套了 recover。
真正难处理的是 panic 发生在 defer 链中间:比如你 defer 了 A、B、C,B panic 了,C 就不会执行。这种链式依赖场景,recover 覆盖不到,得靠设计解耦。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











