在 Colly 中,多个 OnHTML回调函数的执行顺序遵循特定的规则,理解这个规则对于编写可靠的爬虫至关重要。下面这个表格清晰地展示了它们的执行机制和顺序:
| 特性 | 执行规则 | 说明 |
|---|---|---|
| 注册顺序 | 先注册的先执行 | 第一个注册的 OnHTML回调会最先执行 |
| 执行范围 | 每个回调都独立遍历整个HTML文档 | 每个回调都会从文档根部开始查找匹配自己选择器的元素 |
| 独立性 | 回调之间相互独立 | 一个回调的执行不会影响另一个回调的匹配过程 |
| 同步执行 | 在同一文档中同步顺序执行 | 一个回调执行完毕后,下一个回调才开始执行 |
验证示例代码
让我们通过一个具体的示例来验证这个行为:
package main
import (
"fmt"
"time"
"github.com/gocolly/colly/v2"
)
func main() {
c := colly.NewCollector()
// 第一个注册的回调 - 会最先执行
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
fmt.Printf("回调1 [链接] 执行: %s\n", e.Text)
time.Sleep(100 * time.Millisecond) // 模拟处理时间
})
// 第二个注册的回调 - 会在第一个之后执行
c.OnHTML("div", func(e *colly.HTMLElement) {
fmt.Printf("回调2 [DIV] 执行: 找到div元素\n")
time.Sleep(50 * time.Millisecond)
})
// 第三个注册的回调 - 最后执行
c.OnHTML("p", func(e *colly.HTMLElement) {
fmt.Printf("回调3 [段落] 执行: %s\n", e.Text)
})
// 模拟HTML内容
htmlContent := `
<html>
<body>
<div>容器1
<a href="/link1">链接1</a>
<p>段落1</p>
</div>
<div>容器2
<a href="/link2">链接2</a>
<p>段落2</p>
</div>
</body>
</html>`
// 使用PostRaw直接处理HTML字符串
c.OnResponse(func(r *colly.Response) {
fmt.Println("=== 开始处理HTML ===")
})
// 模拟请求
c.PostRaw("http://example.com", []byte(htmlContent))
}
预期输出:
=== 开始处理HTML ===
回调1 [链接] 执行: 链接1
回调1 [链接] 执行: 链接2
回调2 [DIV] 执行: 找到div元素
回调2 [DIV] 执行: 找到div元素
回调3 [段落] 执行: 段落1
回调3 [段落] 执行: 段落2
重要注意事项
- 性能影响:每个
OnHTML回调都会独立遍历整个DOM树。如果注册了大量回调或HTML文档很大,可能会影响性能。 - 元素处理顺序:在单个
OnHTML回调中,匹配元素的处理顺序是文档顺序(从上到下)。 - 错误处理:如果一个回调中发生panic,会终止整个爬取过程。建议在每个回调中使用defer+recover进行错误处理。
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("回调执行出错: %v\n", r)
}
}()
// 主要的处理逻辑
})
最佳实践建议
- 合并相关选择器:如果逻辑相关,尽量合并到一个回调中:
c.OnHTML("a[href], img[src], link[rel='stylesheet']", func(e *colly.HTMLElement) {
switch e.Name {
case "a":
fmt.Printf("链接: %s\n", e.Attr("href"))
case "img":
fmt.Printf("图片: %s\n", e.Attr("src"))
case "link":
fmt.Printf("样式表: %s\n", e.Attr("href"))
}
})
- 使用适当的执行顺序:将最重要的处理逻辑放在前面注册,确保先执行。
- 避免阻塞操作:在回调中避免耗时的同步操作,考虑使用goroutine:
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
go func(link string) {
// 异步处理耗时操作
processLink(link)
}(e.Attr("href"))
})