基本爬蟲架構:實現豆瓣爬蟲


一、架構原理及運行流程

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM