【实战经验】用Golang构建一款分布式爬虫的完整实践流程
在如今信息爆炸的时代,海量的数据可以让人头疼,但是数据的质量和价值是我们不能忽视的,因此,网站爬虫成为了我们获取数据的重要手段之一。本文将介绍如何使用Golang构建一款分布式爬虫来获取想要的数据。
一、分析需求
在开始构建分布式爬虫前,我们需要充分了解需求,梳理好需要爬取的数据、网站的结构,以及要使用的技术栈等方面。在此,我们以爬取拉勾网招聘信息为例来讲解如何构建一个分布式爬虫。
我们需要爬取的信息包括职位名称、职位链接、公司名称、公司所在城市、薪资范围和工作经验等信息。我们可以通过浏览器访问拉勾的招聘页面,可以看到页面是由多个职位的信息组成的,而每个职位的信息又包括多个字段。通过Chrome浏览器的Inspect功能,我们可以看到页面的结构如下:

在分析页面结构后,我们需要选择一个合适的技术栈来实现爬虫的功能。本次我们选择使用Go语言,并借助其强大的并发、网络和解析等能力来完成爬虫的功能。
二、准备工作
在开始编写代码前,我们需要先安装Go语言并配置好环境变量。具体操作可以参考官方文档。
1. 安装Go
从Go官网下载适合自己操作系统的安装包,进行安装。安装完成后,可以打开终端输入以下命令查看版本号:
```
go version
```
2. 配置环境变量
在macOS和Linux系统下,需要将export PATH=/usr/local/go/bin:$PATH写入到~/.bashrc或~/.zshrc中,并执行source ~/.bashrc或source ~/.zshrc来使其生效。
在Windows系统下,需要将%GOPATH%\bin;%GOROOT%\bin;写入到系统的环境变量中,具体操作可以参考官方文档。
三、编写代码
在准备工作完成后,我们就可以开始编写代码了。我们可以将整个爬虫分成三个部分:URL管理器、HTML下载器和HTML解析器。其中,URL管理器用于管理所有待下载的URL,HTML下载器用于下载URL对应的HTML源码,HTML解析器用于解析HTML源码中我们需要的信息。
1. URL管理器
URL管理器主要负责管理待下载URL的集合和已下载URL的集合,以及对URL的去重和添加等操作。在本爬虫中,我们可以使用一个map[string]bool类型的变量来作为URL的集合,并使用sync.Mutex类型的锁来保证URL的并发安全。代码如下:
```
type urlManager struct {
urls map[string]bool
mu sync.Mutex
}
func newURLManager() *urlManager {
return &urlManager{
urls: map[string]bool{},
}
}
func (m *urlManager) addURL(url string) {
m.mu.Lock()
defer m.mu.Unlock()
if !m.urls[url] {
m.urls[url] = true
}
}
func (m *urlManager) addURLs(urls []string) {
m.mu.Lock()
defer m.mu.Unlock()
for _, url := range urls {
if !m.urls[url] {
m.urls[url] = true
}
}
}
func (m *urlManager) hasURL() bool {
m.mu.Lock()
defer m.mu.Unlock()
return len(m.urls) > 0
}
func (m *urlManager) getURL() string {
m.mu.Lock()
defer m.mu.Unlock()
for url := range m.urls {
delete(m.urls, url)
return url
}
return ""
}
```
2. HTML下载器
HTML下载器主要负责下载URL对应的HTML源码,并返回对应的HTTP响应状态码。在本爬虫中,我们可以使用Go的net/http包来实现HTTP的请求和响应。代码如下:
```
func download(url string) (string, int, error) {
client := &http.Client{}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", 0, err
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3")
resp, err := client.Do(req)
if err != nil {
return "", 0, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", 0, err
}
return string(body), resp.StatusCode, nil
}
```
3. HTML解析器
HTML解析器主要负责解析HTML源码,并提取我们需要的信息。在本爬虫中,我们可以使用Go的goquery包来实现HTML的解析和信息提取。代码如下:
```
func parse(body string) []job {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(body))
if err != nil {
return nil
}
var jobs []job
doc.Find(".job-list li").Each(func(i int, s *goquery.Selection) {
j := job{}
j.Title = s.Find(".position_link h3").Text()
j.Link, _ = s.Find(".position_link").Attr("href")
j.Company = s.Find(".company_name a").Text()
j.City = s.Find(".position .add em").Eq(0).Text()
j.Salary = s.Find(".money").Text()
j.Exp = s.Find(".position .add em").Eq(1).Text()
jobs = append(jobs, j)
})
return jobs
}
```
四、构建分布式爬虫
在完成单机版爬虫的编写后,我们就可以开始构建分布式爬虫了。在分布式爬虫中,我们需要将爬虫分成两个部分:任务调度器和工作节点。
1. 任务调度器
任务调度器主要负责管理所有待爬取URL的集合和已爬取URL的集合,对URL进行去重和添加等操作,并将待爬取URL分发到各个工作节点上执行。任务调度器还需要负责维护工作节点的数量。
在本分布式爬虫中,我们可以使用一个map[string]bool类型的变量来作为URL的集合,并使用sync.Mutex类型的锁来保证URL的并发安全。
为了使待爬取URL在各个工作节点上均匀分散,我们采用一致性哈希算法来确定需要爬取的URL的工作节点。具体实现可以参考一致性哈希算法。
代码如下:
```
type scheduler struct {
urlm *urlManager
nodes []string
circle *consisthash.Map
mu sync.Mutex
}
func newScheduler(nodes []string) *scheduler {
return &scheduler{
urlm: newURLManager(),
nodes: nodes,
circle: consisthash.New(),
}
}
func (s *scheduler) addURL(url string) {
s.urlm.addURL(url)
}
func (s *scheduler) addURLs(urls []string) {
s.urlm.addURLs(urls)
}
func (s *scheduler) hasURL() bool {
return s.urlm.hasURL()
}
func (s *scheduler) getURL() string {
url := s.urlm.getURL()
return url
}
func (s *scheduler) addNode(node string) {
s.mu.Lock()
defer s.mu.Unlock()
s.nodes = append(s.nodes, node)
s.circle.Add(node)
}
func (s *scheduler) removeNode(node string) {
s.mu.Lock()
defer s.mu.Unlock()
for i, n := range s.nodes {
if n == node {
s.nodes[i] = s.nodes[len(s.nodes)-1]
s.nodes = s.nodes[:len(s.nodes)-1]
break
}
}
s.circle.Remove(node)
}
func (s *scheduler) getNode(key string) string {
s.mu.Lock()
defer s.mu.Unlock()
if node, ok := s.circle.Get(key); ok {
return node
}
return ""
}
```
2. 工作节点
工作节点主要负责从任务调度器中获取待爬取URL并进行爬取,将结果保存到本地或者上传到云端等操作。在本分布式爬虫中,我们可以使用Go的goroutine来实现并发操作。
每个工作节点的代码结构和单机版爬虫完全一致,唯一的区别在于获取待爬取URL的方式。每个工作节点可以通过轮询的方式从任务调度器中获取待爬取URL。代码如下:
```
func run(node string, s *scheduler, result chan<- []job) {
for {
url := s.getNode(node)
if url == "" {
break
}
body, _, err := download(url)
if err != nil {
fmt.Println(err)
continue
}
jobs := parse(body)
result <- jobs
}
}
```
五、总结
综上所述,我们通过使用Golang构建了一款分布式爬虫,可以实现高效、快速地获取我们需要的数据。在代码的编写过程中,我们掌握了URL管理器、HTML下载器、HTML解析器、一致性哈希算法等一系列技术知识点。通过此次实践,我们不仅深入了解了Golang的并发、网络和解析能力,也提升了我们的技术水平和实践能力。