zoukankan      html  css  js  c++  java
  • 基本爬虫架构:实现豆瓣爬虫

    一、架构原理及运行流程

    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')
  • 相关阅读:
    Python处理海量手机号码
    javascript面向对象
    NET实现的DDD、CQRS与微服务架构
    增加打赏功能
    开发测试时给 Kafka 发消息的 UI 发送器――Mikasa
    Computational Network Toolkit (CNTK) 是微软出品的开源深度学习工具包
    线程同步和多线程优先级
    linux sed命令详解(转)
    linux sort,uniq,cut,wc命令详解 (转)
    linux awk命令详解(转)
  • 原文地址:https://www.cnblogs.com/mxsf/p/10143201.html
Copyright © 2011-2022 走看看