基本爬虫架构:实现豆瓣爬虫

一、架构原理及运行流程

1.1 架构图解

1.2 模块分析

  1. 爬虫调度器:爬虫调度器只要负责统筹其他四个模块的协调工作。
  2. URL 管理器:负责管理 URL 链接,维护已经爬取的 URL 集合和未爬取的 URL 集合,提供获取新 URL 链接接口。
  3. HTML 下载器:用于从 URL 管理器中获取未爬取的 URL 链接并下载 HTML 网页。
  4. HTML 解析器:用于从 HTML 下载器中获取已经下载的 HTML 网页,并从中解析出新的 URL 交给 URL 管理器,解析出有效数据交给数据存储器。
  5. 数据存储器:用于将 HTML 解析器解析出来的数据通过文件或者数据库形式存储起来。

1.3 运行流程

二、URL 管理器

2.1 实现原理

URL 管理器主要包括两个变量,一个是已爬取 URL 的集合,另一个是未爬取 URL 的集合。采用 Python 中的 set 类型,主要是使用 set 的去重复功能, 防止链接重复爬取,因为爬取链接重复时容易造成死循环。链接去重复在 Python 爬虫开发中是必备的功能,解决方案主要有三种:1)内存去重 2)关系数据库去重 3)缓存数据库去重。大型成熟的爬虫基本上采用缓存数据库去重的方案,尽可能避免内存大小的限制,又比关系型数据库去重性能高很多。由于基础爬虫的爬取数量较小,因此我们使用 Python 中 set 这个内存去重方式。

URL 管理器除了具有两个 URL 集合,还需要提供以下接口,由于配合其他模块使用,接口如下:

  1. 判断是否有待取的 URL, 方法定义为 has_new_url()。
  2. 添加新的 URL 到未爬取集合中, 方法定义为 add_new_url(url),add_new_urls(urls)。
  3. 获取一个未爬取的 URL,方法定义为 get_new_url()。
  4. 获取未爬取 URL 集合的大小,方法定义为 new_url_size()。
  5. 获取已经爬取的 URL 集合的大小,方法定义为 old_url_size()。

2.2 代码如下

 1 class UrlManager:
 2     def __init__(self):
 3         self.new_urls = set()   # 未爬取 url 集合
 4         self.old_urls = set()   # 已爬取 url 集合
 5 
 6     def has_new_url(self):
 7         """
 8         判断是否有未爬取的 url
 9         :return: bool
10         """
11         return self.new_urls_size() != 0
12 
13     def get_new_url(self):
14         """
15         返回一个未爬取的 url
16         :return: str
17         """
18         new_url = self.new_urls.pop()
19         self.old_urls.add(new_url)
20         return new_url
21 
22     def add_new_url(self, url):
23         """
24         添加一个新的 url
25         :param url: 单个 url
26         :return: None
27         """
28         if url is None:
29             return None
30         if (url not in self.new_urls) and (url not in self.old_urls):
31             self.new_urls.add(url)
32 
33     def add_new_urls(self, urls):
34         """
35         添加多个新的url
36         :param urls: 多个 url
37         :return: None
38         """
39         if urls is None:
40             return None
41         for url in urls:
42             self.add_new_url(url)
43 
44     def new_urls_size(self):
45         """
46         返回未爬过的 url 集合的大小
47         :return: int
48         """
49         return len(self.new_urls)
50 
51     def old_urls_size(self):
52         """
53         返回已爬过的 url 集合的大小
54         :return: int
55         """
56         return len(self.old_urls)

 三、HTML 下载器

3.1 实现原理

HTML 下载器用来下载网页,这时候需要注意网页的编码,以保证下载的网页没有乱码。下载器需要用到 Requests 模块,里面只需要实现一个接口即可:download(url)。

3.2 代码如下

 1 import requests
 2 
 3 
 4 class HtmlDownloader:
 5     def download(self, url):
 6         """
 7         下载 html 页面源码
 8         :param url: url
 9         :return: str / None
10         """
11         if not url:
12             return None
13 
14         headers = {
15             'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:63.0) Gecko/20100101 Firefox/63.0',
16         }
17         r = requests.get(url, headers=headers)
18         if r.status_code == 200:
19             r.encoding = 'utf-8'
20             return r.text
21         else:
22             return None

四、HTML 解析器

4.1 实现原理

HTML 解析器使用 Xpath 规则进行 HTML 解析,需要解析的部分主要有书名、评分和评分人数。

 

4.2 代码如下

 1 from lxml.html import etree
 2 import re
 3 
 4 class HtmlParser:
 5     def parser(self, page_url, html_text):
 6         """
 7         解析页面新的 url 链接和数据
 8         :param page_url: url
 9         :param html_text: 页面内容
10         :return: tuple / None
11         """
12         if not page_url and not html_text:
13             return None
14         new_urls = self._get_new_urls(page_url, html_text)
15         new_data = self._get_new_data(html_text)
16 
17         return new_urls, new_data
18 
19     def _get_new_urls(self, page_url, html_text):
20         """
21         返回解析后的 url 集合
22         :param page_url: url
23         :param html_text: 页面内容
24         :return: set
25         """
26         new_urls = set()
27         links = re.compile(r'\?start=\d+').findall(html_text)
28         for link in links:
29             new_urls.add(page_url.split('?')[0] + link)
30         return new_urls
31 
32     def _get_new_data(self, html_text):
33         """
34         返回解析后的数据列表
35         :param html_text: 页面内容
36         :return: list
37         """
38         datas = []
39         for html in etree.HTML(html_text).xpath('//ol[@class="grid_view"]/li'):
40             name = html.xpath('./div/div[@class="info"]/div[@class="hd"]/a/span[1]/text()')[0]
41             score = html.xpath('./div/div[@class="info"]/div[@class="bd"]/div[@class="star"]/span[2]/text()')[0]
42             person_num = html.xpath('./div/div[@class="info"]/div[@class="bd"]/div[@class="star"]/span[4]/text()')[0].strip('人评价')
43             datas.append([name, score, person_num])
44         return datas

五、数据存储器

5.1 实现原理

数据存储器主要包括两个方法:store_data(data)用于将解析出来的数据存储到内存中,output_csv()用于将存储的数据输出为指定的文件格式,我们使用的是将数据输出为 csv 格式。

5.2 代码如下

 1 import csv
 2 
 3 class DataOutput:
 4     def __init__(self):
 5         self.file = open('数据.csv', 'w')
 6         self.csv_file = csv.writer(self.file)
 7         self.csv_file.writerow(['书名', '评分', '评分人数'])
 8 
 9     def output_csv(self, data):
10         """
11         将数据写入 csv 文件
12         :param data: 数据
13         :return: None
14         """
15         self.csv_file.writerow(data)
16 
17     def close_file(self):
18         """
19         关闭文件链接
20         :return: None
21         """
22         self.file.close()

六、爬虫调度器

6.1 实现原理

爬虫调度器首先要做的是初始化各个模块,然后通过 crawl(start_url) 方法传入入口 URL,方法内部实现按照运行流程控制各个模块的工作。

6.2 代码如下

 1 from UrlManager import UrlManager
 2 from HtmlDownloader import HtmlDownloader
 3 from HtmlParser import HtmlParser
 4 from DataOutput import DataOutput
 5 
 6 
 7 class SpiderManager:
 8     def __init__(self):
 9         self.manager = UrlManager()
10         self.downloader = HtmlDownloader()
11         self.parser = HtmlParser()
12         self.output = DataOutput()
13 
14     def crawl(self, start_url):
15         """
16         负责调度其他爬虫模块
17         :param start_url: 起始 url
18         :return: None
19         """
20         self.manager.add_new_url(start_url)
21         while self.manager.has_new_url():
22             try:
23                 new_url = self.manager.get_new_url()
24                 html = self.downloader.download(new_url)
25                 new_urls, new_datas = self.parser.parser(start_url, html)
26                 self.manager.add_new_urls(new_urls)
27                 for data in new_datas:
28                     self.output.output_csv(data)
29             except Exception:
30                 print('爬取失败')
31         self.output.close_file()
32 
33 
34 if __name__ == '__main__':
35     sm = SpiderManager()
36     sm.crawl('https://movie.douban.com/top250?start=0')
posted @ 2018-12-19 14:50  梦醒时分c  阅读(1284)  评论(0编辑  收藏  举报