具有分页处理功能的 Golang 网络蜘蛛

Golang web spider with pagination processing

我正在开发一个 golang 网络爬虫,它应该解析某些特定搜索引擎上的搜索结果。主要困难 - 并发解析,或者更确切地说,在处理分页时,例如 ← Previous 1 2 3 4 5 ... 34 Next →。除了分页结果的递归爬行外,所有事情都工作正常。看我的代码:

package main

import (
    "bufio"
    "errors"
    "fmt"
    "net"
    "strings"

    "github.com/antchfx/htmlquery"
    "golang.org/x/net/html"
)

type Spider struct {
    HandledUrls []string
}

func NewSpider(url string) *Spider {
    // ...
}

func requestProvider(request string) string {
    // Everything is good here
}

func connectProvider(url string) net.Conn {
    // Also works
}

// getContents makes request to search engine and gets response body
func getContents(request string) *html.Node {
    // ...
}

// CheckResult controls empty search results
func checkResult(node *html.Node) bool {
    // ...
}

func (s *Spider) checkVisited(url string) bool {
    // ...
}

// Here is the problems
func (s *Spider) Crawl(url string, channelDone chan bool, channelBody chan *html.Node) {
    body := getContents(url)

    defer func() {
        channelDone <- true
    }()

    if checkResult(body) == false {
        err := errors.New("Nothing found there")
        ErrFatal(err)
    }

    channelBody <- body
    s.HandledUrls = append(s.HandledUrls, url)
    fmt.Println("Handled ", url)

    newUrls := s.getPagination(body)

    for _, u := range newUrls {
        fmt.Println(u)
    }

    for i, newurl := range newUrls {
        if s.checkVisited(newurl) == false {
            fmt.Println(i)
            go s.Crawl(newurl, channelDone, channelBody)
        }
    }
}

func (s *Spider) getPagination(node *html.Node) []string {
    // ...
}

func main() {
    request := requestProvider(*requestFlag)
    channelBody := make(chan *html.Node, 120)
    channelDone := make(chan bool)

    var parsedHosts []*Host

    s := NewSpider(request)

    go s.Crawl(request, channelDone, channelBody)

    for {
        select {
        case recievedNode := <-channelBody:
             // ...

            for _, h := range newHosts {
                 parsedHosts = append(parsedHosts, h)
                 fmt.Println("added", h.HostUrl)
            }

        case <-channelDone:
            fmt.Println("Jobs finished")
        }

        break
   }
}

它总是returns 只有第一页,没有分页。同样的 GetPagination(...) 效果很好。请告诉我,我的错误在哪里。 希望 Google 翻译正确。

问题可能是 main 在所有 goroutine 完成之前就退出了。

首先,在 select 语句之后有一个 break,它在第一次读取通道后运行异常。这确保了 main func returns 在你第一次通过 channelBody.

发送内容后

其次,这里使用channelDone不是正确的方法。最惯用的方法是使用 sync.WaitGroup。在启动每个 goroutine 之前,使用 WG.Add(1) 并将 defer 替换为 defer WG.Done();在 main 中,使用 WG.Wait()。请注意,您应该使用指针来引用 WaitGroup。您可以阅读更多 here.