070-并发爬虫
Posted --Allen--
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了070-并发爬虫相关的知识,希望对你有一定的参考价值。
关于爬虫(Spider/Crawler),相信你多多少少接触过。比如你老板要你从某网站获取一批企业的黄页信息,这时候爬虫就派上用场了。而本文,我们的任务是编写一个网页抓取程序,提取网页中的 url. 我们的程序可以继续访问抓取到的 url,深度抓取更多的 url.
1. 链接抓取器
关于这个程序,我就不多废口舌了,你也不必过多的去研究它,掌握用法即可,我们的目的是学习并发。这个程序位于 gopl/goroutine/link 目录下。
package link
import (
"io/ioutil"
"net/http"
"regexp"
"strings"
)
// 从指定的种子地址提取 url
func ExtractLinks(seed string) ([]string, error)
var urls []string
pattern := regexp.MustCompile(`<a\\b[^>]+\\bhref="([^"]*)"[^>]*>[\\s\\S]*?</a>`)
resp, err := http.Get(seed)
if err != nil
return nil, err
defer resp.Body.Close()
buf, err := ioutil.ReadAll(resp.Body)
if err != nil
return nil, err
res := pattern.FindAllStringSubmatch(string(buf), -1)
for _, e := range res
if len(e) != 2
continue
if strings.HasPrefix(e[1], "http")
urls = append(urls, e[1])
return urls, nil
下面是一个简单的测试示例:
// +build ignore
package main
import (
"fmt"
"gopl/goroutine/link"
"log"
"os"
)
func main()
if len(os.Args) < 2
fmt.Println("Usage:\\n\\tgo run main.go <url>")
os.Exit(1)
urls, err := link.ExtractLinks(os.Args[1])
if err != nil
log.Print(err)
return
for _, url := range urls
fmt.Printf("%v\\n", url)
接下来运行 go run main.go 'http://www.baidu.com/s?wd=goroutine'
,你的屏幕会打印类似这样的结果:
图1 链接抓取器(只展示部分结果)
2. 深度链接抓取器
看到图 1 的结果了吧,上面是我们抓取的 http://www.baidu.com/s?wd=goroutine 这个网页的链接。但是作为一只“优秀”的爬虫,这点结果根本不能满足,我们希望继续访问抓取到的链接,然后获取更多的链接。思路非常简单:
- 每个 goroutine 从一个种子 url 提取所有的链接,保存到任务列表
- 主协程从任务列表提取已经抓取的 url,并启动新的协程继续抓取
下面是这个深度链接抓取器的代码,在路径 gopl/goroutine/concurrence 下面:
// crawler01.go
package main
import (
"fmt"
"gopl/goroutine/link"
"log"
"os"
)
// url 抓取,返回抓取到的所有 url
func crawl(url string) []string
fmt.Println(url)
urls, err := link.ExtractLinks(url)
if err != nil
log.Print(fmt.Sprintf("\\x1b[31m%v\\x1b[0m", err))
return urls
func main()
if len(os.Args) < 2
fmt.Println("Usage:\\n\\tgo run crawl.go <url>")
os.Exit(1)
// 任务队列
workList := make(chan []string)
// 记录该 url 是否已经访问过,防止无限循环
seen := make(map[string]bool)
go func()
workList <- os.Args[1:]
()
for list := range workList
for _, url := range list
if seen[url]
continue
seen[url] = true
go func(url string)
workList <- crawl(url)
(url)
接下来,运行一下看看,我们把结果重定向到 links.go 文件中,错误会输出到屏幕上。
$ go run crawler01.go 'http://www.baidu.com/s?wd=goroutine' > links.txt
要不了一会儿,你的屏幕应该就会出现:
图2 输出错误
这个程序几乎永远不会停止,使用 CTRL C 终止它吧。另外我们的 links.txt 文件也抓取了不少 url 了。
2.1 为什么出现 too many open files
这是文件描述符被耗尽了。一般你的系统都有同时打开的最大文件描述符个数的限制。
图3 文件描述符限制
在我的系统中,这个限制是 7168。而每访问一个 url 都要建立一个 tcp 链接,意味着消耗掉一个描述符,因此这个程序理论上最大可以同时打开 7168 个 url。
但是我们的程序太 TM 并行了,开启的 goroutine 远远超出了这个限制。如何解决?什么?修改系统最大描述符限制的上限?别开开玩笑了,goroutine 是可以轻松开到上万的,这样会把你的系统搞死。
无穷无尽的并发并不是什么好事。是的,我们需要对并发度做控制。换句话说,我们希望把同时开启的 goroutine 上限控制在一个合理的数字上,比如允许最大 20 并发。
为了防止你学习疲劳,今天到此为止。下回我们讨论并发度控制。
3. 总结
- 练习链接抓取器
- 练习并发的深度链接抓取器
- 为什么程序会出现 too many open files 的错误
以上是关于070-并发爬虫的主要内容,如果未能解决你的问题,请参考以下文章