一、架构原理及运行流程

1.1 架构图解

爬虫架构的基本组成部分_python

1.2 模块分析

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

1.3 运行流程

爬虫架构的基本组成部分_python_02

二、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 解析,需要解析的部分主要有书名、评分和评分人数。

爬虫架构的基本组成部分_爬虫架构的基本组成部分_03

 

爬虫架构的基本组成部分_爬虫架构的基本组成部分_04

爬虫架构的基本组成部分_爬虫架构的基本组成部分_05

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')