登录
首页 >  Golang >  Go教程

浅析go语言实现并发网络爬虫的方法

来源:亿速云

时间:2023-04-11 16:54:25 405浏览 收藏

怎么入门Golang编程?需要学习哪些知识点?这是新手们刚接触编程时常见的问题;下面golang学习网就来给大家整理分享一些知识点,希望能够给初学者一些帮助。本篇文章就来介绍《浅析go语言实现并发网络爬虫的方法》,涉及到go语言,有需要的可以收藏一下

本篇内容主要讲解“浅析go语言实现并发网络爬虫的方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“浅析go语言实现并发网络爬虫的方法”吧!

首先我的思路是看一下爬虫的串行实现,然后通过两个并发实现:一个使用锁,另一个使用通道

这里不涉及从页面中提取URL的逻辑(请查看Go框架colly的内容)。网络抓取只是作为一个例子来考察Go的并发性。

我们想从我们的起始页中提取所有的URL,将这些URL保存到一个列表中,然后对列表中的每个URL做同样的处理。页面的图很可能是循环的,所以我们需要记住哪些页面已经经历了这个过程(或者在使用并发时,处于这个过程的中间)。

浅析go语言实现并发网络爬虫的方法

串行爬虫首先检查我们是否已经在获取地图中获取了该页面。如果我们没有,那么它就在页面上找到的每个URL上调用自己。注意:map 在Go中是引用类型,所以每次调用都会得到相同的 map。

func Serial(url string, fetcher Fetcher, fetched map[string]bool) {
    if fetched[url] {
        return
    }
    fetched[url] = true
    urls, err := fetcher.Fetch(url)
    if err != nil {
        return
    }
    for _, u := range urls {
        Serial(u, fetcher, fetched)
    }
    return
}
func main() {
    Serial(, fetcher, make(map[string]bool))
}

fetcher将包含提取URLs到列表中的逻辑(也可以对页面的内容做一些处理)。这个实现不是本讲的重点。

由于网络速度很慢,我们可以使用并发性来加快这个速度。为了实现这一点,我们需要使用锁(在读/写时锁定已经获取的页面地图)和 waitgroup(等待所有的goroutine完成)。

已经获取的页面的 map 只能由持有锁的线程访问,因为我们不希望多个线程开始处理同一个URL。如果在一个线程的读和写之间,另一个线程在第一个线程更新之前从 map 上得到了相同的读数,这就可能发生。

我们定义了fetchState结构,将 map 和锁组合在一起,并定义了一个方法来初始化它。

爬虫程序的开始是一样的,检查我们是否已经获取了URL,但这次使用sync.Mutex来锁定 map,如前所述。然后,对于页面上发现的每个URL,我们在一个新的goroutine中启动相同的函数。在启动之前,我们将WaitGroup的计数器增加1,done.Wait()在退出之前等待所有的抓取工作完成。

func ConcurrentMutex(url string, fetcher Fetcher, f *fetchState) {
    f.mu.Lock()
    already := f.fetched[url]
    f.fetched[url] = true
    f.mu.Unlock()
    if already {
        return
    }
    urls, err := fetcher.Fetch(url)
    if err != nil {
        return
    }
    var done sync.WaitGroup
    for _, u := range urls {
        done.Add(1)
        go func(u string) {
            defer done.Done()
            ConcurrentMutex(u, fetcher, f)
        }(u)
    }
    done.Wait()
    return
}
type fetchState struct {
    mu      sync.Mutex
    fetched map[string]bool
}
func makeState() *fetchState {
    f := &fetchState{}
    f.fetched = make(map[string]bool)
    return f
}
func main() {
    ConcurrentMutex(, fetcher, makeState())
}

注意:

[1] done.Done()的调用被推迟了,以防我们在其中一个调用中出现错误,在这种情况下,我们仍然要递减WaitGroup的计数器。

[2] 这段代码的一个问题是,我们没有限制线程的数量。但值得一提的是,goroutines比其他语言的线程更轻量级,并且由Go运行时管理,系统调用更少。

[3] 我们把字符串u传给立即函数,以便制作一个URL的副本,然后才把它送到goroutine,因为变量u在外层for循环中发生了变化。要理解这样做的必要性,一个更简单的例子是,在没有WaitGroup的情况下。

func checkThisOut() {
  s := "abc"
  sec := time.Second
  go func() {time.Sleep(sec); fmt.Printf("s = %vn", s)}()
  go func(u string) {time.Sleep(sec); fmt.Printf("u = %vn", u)}(s)
  s = "def"
  time.Sleep(2 * sec)
}
// this prints out: u = abc, s = def

[4] 我们可以运行内置的数据竞赛检测器,通过运行go run -race .来帮助检测竞赛条件。它在这个例子中非常有效。

下一个并发版本在线程之间完全不共享内存!嗯,这并不准确。我们只是不会自己同步访问共享数据。相反,我们使用一个通道在goroutine之间进行通信。

在这个最后的版本中,我们有一个主函数在主线程上运行。只有这个函数能看到 map 并从通道中读取。channel ,像 map 一样,也是引用类型。所以这里只有一个通道。

在启动时,我们将第一个URL写到通道上。这是在一个goroutine中完成的,因为向一个没有缓冲的通道的写入会导致goroutine暂停,直到该值被另一个goroutine读取。

我们在一个for循环中从通道中读取URL的列表(从一个没有缓冲的通道中读取也会阻塞)。然后,我们以与之前的实现类似的方式浏览该列表。通过使用一个计数器,一旦没有更多的工作者,这个循环就会中断。

工作者获取URL的列表,将它们传递给通道。如果出现错误,会传递一个空列表,这样从通道读取的for循环最终会退出(计数器的设置方式是,我们等待从每个goroutine读取一个值)。

func ConcurrentChannel(url string, fetcher Fetcher) {
    ch := make(chan []string)
    go func() {
        ch 

到此,相信大家对“浅析go语言实现并发网络爬虫的方法”有了更深的了解,不妨来实际操作一番吧!这里是golang学习网,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

今天关于《浅析go语言实现并发网络爬虫的方法》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于golang的内容请关注golang学习网公众号!

声明:本文转载于:亿速云 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>