golang的colly爬虫中多个onHTML方法是如何执行的?

在 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

重要注意事项

  1. 性能影响:每个 OnHTML回调都会独立遍历整个DOM树。如果注册了大量回调或HTML文档很大,可能会影响性能。
  2. 元素处理顺序:在单个 OnHTML回调中,匹配元素的处理顺序是文档顺序(从上到下)。
  3. 错误处理:如果一个回调中发生panic,会终止整个爬取过程。建议在每个回调中使用defer+recover进行错误处理。
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("回调执行出错: %v\n", r)
        }
    }()
    // 主要的处理逻辑
})

最佳实践建议

  1. 合并相关选择器:如果逻辑相关,尽量合并到一个回调中:
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"))
    }
})
  1. 使用适当的执行顺序:将最重要的处理逻辑放在前面注册,确保先执行。
  2. 避免阻塞操作:在回调中避免耗时的同步操作,考虑使用goroutine:
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
    go func(link string) {
        // 异步处理耗时操作
        processLink(link)
    }(e.Attr("href"))
})

Comments

No comments yet. Why don’t you start the discussion?

发表回复