go语言实现并发网络爬虫的示例代码
作者:q56731523 发布时间:2024-01-31 07:45:35
go语言做爬虫也是很少尝试,首先我的思路是看一下爬虫的串行实现,然后通过两个并发实现:一个使用锁,另一个使用通道
这里不涉及从页面中提取URL的逻辑(请查看Go框架colly的内容)。网络抓取只是作为一个例子来考察Go的并发性。
我们想从我们的起始页中提取所有的URL,将这些URL保存到一个列表中,然后对列表中的每个URL做同样的处理。页面的图很可能是循环的,所以我们需要记住哪些页面已经经历了这个过程(或者在使用并发时,处于这个过程的中间)。
串行爬虫首先检查我们是否已经在获取地图中获取了该页面。如果我们没有,那么它就在页面上找到的每个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(<page>, 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(<page>, 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 = %v\n", s)}()
go func(u string) {time.Sleep(sec); fmt.Printf("u = %v\n", 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 <- []string{url}
}()
master(ch, fetcher)
}
func master(ch chan []string, fetcher Fetcher) {
n := 1
fetched := make(map[string]bool)
for urls := range ch {
for _, u := range urls {
if fetched[u] == false {
fetched[u] = true
n += 1
go worker(u, ch, fetcher)
}
}
n -= 1
if n == 0 {
break
}
}
}
func worker(url string, ch chan []string, fetcher Fetcher) {
urls, err := fetcher.Fetch(url)
if err != nil {
ch <- []string{}
} else {
ch <- urls
}
}
来源:https://blog.csdn.net/weixin_44617651/article/details/129744675


猜你喜欢
- 本文实例讲述了Python简单I/O操作。分享给大家供大家参考,具体如下:文件:poem = '''hellowor
- 新公司是内网环境,无法使用pip安装第三方资源库,在网上搜下,可以直接使用pip打包本机所安装的第三方资源库,打包成whl文件一 进入cmd
- 学了几天终于大概明白pytorch怎么用了这个是直接搬运的官方文档的代码之后会自己试着实现其他nlp的任务# Author: Robert
- [项目]数据库中users表,包含u_name,u_pwd两个字段,其中u_name存在重复项,现在要实现把重复的项删除![分析]1、生成一
- 以下测试用于去除任何字符串中连线的分隔符 --去除字符串中连续的分隔符 declare @str nvarchar(200) declare
- Anaconda 实际上是一个软件发行版,它附带了conda、Python和150多个科学包及其依赖项。其中,conda是一个开源的软件包管
- Python中单类继承Python是一门面向对象的编程语言,支持类继承。新的类称为子类(Subclass),被继承的类称为父类、基类或者超类
- 这篇文章主要介绍了python异常处理try except过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价
- Installing mysql (2.8.1) with native extensions /usr/local/lib/ruby/si
- 首先说说什么是内存泄露,在一个进程中,如果某一块内存无法访问,且直到进程结束为止也无法释放,那么就发生了内存泄露。通常这种情况发生在C++之
- 各种请求方式pip3 install requests>>> import requests>>> r
- 实现思路很多网站都有拼图验证码1.首先要了解拼图验证码的生成原理2.制定破解计划,考虑其可能性和成功率。3.编写脚本很多网站的拼图验证码都是
- PyQt5+requests实现一个车票查询工具,供大家参考,具体内容如下结构图效果图思路1、search(QPushButton)点击信号
- 前言:本系列文章主要参考了《Ultimate ASP.NET Core 3 Web API》一书,对原文进行了翻译,同时适当删减、修改了一部
- iterator循环器(iterator)是对象的容器,包含有多个对象。通过调用循环器的next()方法 (next()方法,在Python
- 本文实例讲述了php实现的支持断点续传的文件下载类及其用法,是非常实用的技巧。分享给大家供大家参考。具体方法如下:通常来说,php支持断点续
- 方法一在 Pillow 中,resize() 方法会强制将图片缩放到指定的大小,可能会导致图片变形或失真;而 thumbnail() 方法则
- 今天在修改 淘宝 宝贝详情页面的时候,发现页面在 Firefox 下遇到这样一个问题:链接用图片做背景,text-indent:-9999p
- 本文实例讲述了Python文件的读写操作。分享给大家供大家参考,具体如下:读写文件读取文件f = open('my_path/my_
- 建议先看vue瀑布流组件上拉加载更多再来食用本文,如果直接想看源码文末就是~文末新增组件优化,之所以没有删优化前的代码是想让以后自己还能看到