2

我编写了一个 colly 脚本来从站点收集端口授权信息。

func main() {
    // Temp Variables
    var tcountry, tport string

    // Colly collector
    c := colly.NewCollector()

    //Ignore the robot.txt
    c.IgnoreRobotsTxt = true
    // Time-out after 20 seconds.
    c.SetRequestTimeout(20 * time.Second)
    //use random agents during requests
    extensions.RandomUserAgent(c)

    //set limits to colly opoeration
    c.Limit(&colly.LimitRule{
        //  // Filter domains affected by this rule
        DomainGlob: "searates.com/*",
        //  // Set a delay between requests to these domains
        Delay: 1 * time.Second,
        //  // Add an additional random delay
        RandomDelay: 3 * time.Second,
    })

    // Find and visit all country links
    c.OnHTML("#clist", func(e *colly.HTMLElement) {
        // fmt.Println("Country List: ", h.ChildAttrs("a", "href"))
        e.ForEach("li.col-xs-6.col-md-3", func(_ int, el *colly.HTMLElement) {
            tcountry = el.ChildText("a")
            link := el.ChildAttr("a", "href")
            fmt.Println("Country: ", tcountry, link)
            e.Request.Visit(link)
        })

    })

    // Find and visit all ports links
    c.OnHTML("#plist", func(h *colly.HTMLElement) {
        // fmt.Println("Port List: ", h.ChildAttrs("a", "href"))
        h.ForEach("li.col-xs-6.col-md-3", func(_ int, el *colly.HTMLElement) {
            tport = el.ChildText("a")
            link := el.ChildAttr("a", "href")
            fmt.Println("Port: ", tport, link)
            h.Request.Visit(link)
        })
    })

    // Find and visit all ports info page
    c.OnHTML("div.row", func(e *colly.HTMLElement) {
        portAuth := e.ChildText("table#port_det tbody:nth-child(1) tr:nth-child(2) td:nth-child(2)")
        fmt.Println("Port Authority: ", portAuth)
    })

    c.Visit("https://www.searates.com/maritime/")
}

我有以下两个问题:

  1. 此外,我有点被迫使用e.Request.Visit,因为d.Visit(如果我克隆 c)没有被执行。我看到当我将 c 克隆为 d 并用于获取“端口信息”部分时,整个块都被跳过了。我在这里做错了什么/为什么会出现这种行为?

  2. 在当前代码中,fmt.Println("Port Authority: ", portAuth)get 执行了两次。我得到如下打印:

❯ go run .
Country:  Albania /maritime/albania
Port:  Durres /port/durres_al
Port Authority:  Durres Port Authority
Port Authority:  
Port:  Sarande /port/sarande_al
Port Authority:  Sarande Port Authority
Port Authority:  
Port:  Shengjin /port/shengjin_al
Port Authority:  Shengjin Port Authority
Port Authority:  

同样,我无法理解为什么它会被打印两次。请帮助:)

4

1 回答 1

1

来自 Go 文档:

collector.Visit- Visit 通过创建对参数中指定的 URL 的请求来启动 Collector 的收集作业。访问也会调用之前提供的回调

Request.Visit- 访问通过创建请求继续收集器的收集工作并保留前一个请求的上下文。Visit 还会调用之前提供的回调。

区别在于深度参数和上下文。如果您在事件处理程序内部使用 collector.Visit,则深度始终为 1。

以下是调用差异:

collector.Visit

if c.CheckHead {
    if check := c.scrape(URL, "HEAD", 1, nil, nil, nil, true); check != nil {
        return check
    }
}
return c.scrape(URL, "GET", 1, nil, nil, nil, true)

Request.Visit

return r.collector.scrape(r.AbsoluteURL(URL), "GET", r.Depth+1, nil, r.Ctx, nil, true)

具体解决您的问题,要调用克隆的 d,您需要d.Visitc.OnHTML事件处理程序中触发 a 。请参阅coursera示例。您还需要使用 ,AbsoluteURL因为克隆的收集器没有链接的上下文(例如,如果它是相对的)。以下是所有内容:

func main() {
    // Temp Variables
    var tcountry, tport string

    // Colly collector
    c := colly.NewCollector()

    //Ignore the robot.txt
    c.IgnoreRobotsTxt = true
    // Time-out after 20 seconds.
    c.SetRequestTimeout(20 * time.Second)
    //use random agents during requests
    extensions.RandomUserAgent(c)

    //set limits to colly opoeration
    c.Limit(&colly.LimitRule{
        //  // Filter domains affected by this rule
        DomainGlob: "searates.com/*",
        //  // Set a delay between requests to these domains
        Delay: 1 * time.Second,
        //  // Add an additional random delay
        RandomDelay: 3 * time.Second,
    })

    d := c.Clone()

    // Find and visit all country links
    c.OnHTML("#clist", func(e *colly.HTMLElement) {
        // fmt.Println("Country List: ", h.ChildAttrs("a", "href"))
        e.ForEach("li.col-xs-6.col-md-3", func(_ int, el *colly.HTMLElement) {
            tcountry = el.ChildText("a")
            link := el.ChildAttr("a", "href")
            fmt.Println("Country: ", tcountry, link)
            e.Request.Visit(link)
        })

    })

    // Find and visit all ports links
    c.OnHTML("#plist", func(h *colly.HTMLElement) {
        // fmt.Println("Port List: ", h.ChildAttrs("a", "href"))
        h.ForEach("li.col-xs-6.col-md-3", func(_ int, el *colly.HTMLElement) {
            tport = el.ChildText("a")
            link := el.ChildAttr("a", "href")
            fmt.Println("Port: ", tport, link)

            absoluteURL := h.Request.AbsoluteURL(link)
            d.Visit(absoluteURL)
        })
    })

    // Find and visit all ports info page
    d.OnHTML("div.row", func(e *colly.HTMLElement) {
        portAuth := e.ChildText("table#port_det tbody:nth-child(1) tr:nth-child(2) td:nth-child(2)")
        if len(portAuth) > 0 {
            fmt.Println("Port Authority: ", portAuth)
        }
    })

    c.Visit("https://www.searates.com/maritime/")
}

请注意绝对 URL 的使用方式,因为收集器之间的上下文不同,因此克隆的收集器无法导航相对 URL 链接。

关于为什么要打印两次的第二个问题,这是因为div.row给定页面上有 2 个元素。我已经尝试了各种不同的 CSS 选择方法来仅将事件处理程序应用于第一个div.row,但只需添加一个检查字符串长度是否大于 0 会更容易。

于 2022-01-08T03:10:43.287 回答