前兩講分別講了分布式爬蟲的結(jié)構(gòu)和masterSpider的實現(xiàn):
簡單分布式爬蟲——第一彈:了解分布式爬蟲結(jié)構(gòu)
簡單分布式爬蟲——第二彈:masterSpider的實現(xiàn)
接下來完成分布式爬蟲的最后一部分:節(jié)點爬蟲的實現(xiàn)。
節(jié)點爬蟲顧名思義就是負責爬取任務(wù),具體來講主要實現(xiàn):
1、從網(wǎng)絡(luò)上獲取masterSpider放在task_queue隊列中的任務(wù)(url)
2、爬取獲取的url的內(nèi)容,并解析出新的url和所要提取的數(shù)據(jù)內(nèi)容
3、將解析好的數(shù)據(jù)以一定格式(字典)返還給masterSpider(放入result_queue隊列)
接下來來完成各部分的功能實現(xiàn):
根據(jù)模塊化思想,我們將nodeSpider分為三部分:
MainSpider: 負責nodeSpider調(diào)度
HtmlDownloader: 下載網(wǎng)頁
HtmlParser: 解析網(wǎng)頁
## MainSpider.py
# -*- coding: utf-8 -*-
from multiprocessing.managers import BaseManager
from HtmlDownLoader import HtmlDownloader
from HtmlParser import HtmlParser
class SpiderWork(object):
'''
爬蟲節(jié)點調(diào)度器:
初始化:
從主節(jié)點注冊任務(wù)
初始化下載器和解析器
開始爬取:
'''
def __init__(self):
#初始化分布式進程中的工作節(jié)點的連接工作
# 實現(xiàn)第一步:使用BaseManager注冊獲取Queue的方法名稱
BaseManager.register('get_task_queue')
BaseManager.register('get_result_queue')
# 實現(xiàn)第二步:連接到服務(wù)器:
server_addr = '127.0.0.1'
print('Connect to server %s...' % server_addr)
# 端口和驗證口令注意保持與服務(wù)進程設(shè)置的完全一致:
self.m = BaseManager(address=(server_addr, 10001), authkey='python'.encode('utf-8'))
try:
self.m.connect()
# 實現(xiàn)第三步:獲取Queue的對象:
self.task = self.m.get_task_queue()
self.result = self.m.get_result_queue()
#初始化網(wǎng)頁下載器和解析器
self.downloader = HtmlDownloader()
self.parser = HtmlParser()
print('*********Init finished!*********')
except ConnectionRefusedError as e:
print('?。≈鞴?jié)點未響應(yīng)')
exit(-1)
def crawl(self, counter):
while True:
try:
if not self.task.empty():
url = self.task.get()
if url == 'end':
print('控制節(jié)點通知爬蟲節(jié)點停止工作...')
#接著通知其它節(jié)點停止工作
self.result.put({'new_urls': 'end', 'data': 'end'})
return
print('%s節(jié)點正在解析:%s'%(counter,url.encode('utf-8')))
counter += 1
content = self.downloader.download(url)
new_urls, data = self.parser.parser(url, content)
self.result.put({"new_urls": new_urls, "data": data})
except EOFError as e:
print("連接工作節(jié)點失敗")
return
except Exception as e:
print(e)
print('!!Crawled fail')
if __name__ == "__main__":
spider = SpiderWork()
spider.crawl()
## HtmlDownloader.py
# -*- coding: utf-8 -*-
import requests
class HtmlDownloader(object):
'''
HTML下載器:
一個接口:
網(wǎng)頁下載接口: downloader(url)
'''
def download(self,url):
if url is None:
return None
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers={'User-Agent': user_agent}
r = requests.get(url,headers=headers)
if r.status_code == 200:
r.encoding = 'utf-8'
return r.text
return None
## HtmlParser.py
# -*- utf-8 -*-
import re
from urllib.parse import urlparse, urljoin
from bs4 import BeautifulSoup
class HtmlParser(object):
'''
HTML內(nèi)容解析器:
一個對外接口:
內(nèi)容解析器: parser(url,content)
'''
def parser(self, url, content):
'''
用于解析網(wǎng)頁內(nèi)容抽取URL和數(shù)據(jù)
:param url: 下載頁面的URL
:param content: 下載的網(wǎng)頁內(nèi)容
:return:返回URL和數(shù)據(jù)
'''
if url is None or content is None:
return
soup = BeautifulSoup(content, 'html.parser', from_encoding='utf-8')
new_urls = self._get_new_urls(url, soup)
new_data = self._get_new_data(url, soup)
return new_urls, new_data
def _get_new_urls(self,page_url,soup):
'''
抽取新的URL集合
:param page_url: 下載頁面的URL
:param soup:soup
:return: 返回新的URL集合
'''
new_urls = set()
#抽取符合要求的a標簽: 示例
links = soup.find_all('a',href=re.compile(r'/item/.*'))
for link in links:
#提取href屬性
new_url = link['href']
#拼接成完整網(wǎng)址
new_full_url = urljoin(page_url,new_url)
new_urls.add(new_full_url)
return new_urls
def _get_new_data(self,page_url,soup):
'''
抽取有效數(shù)據(jù): 示例
:param page_url:下載頁面的URL
:param soup:
:return:返回有效數(shù)據(jù)
'''
data={}
data['url']=page_url
title = soup.find('dd',class_='lemmaWgt-lemmaTitle-title').find('h1')
data['title']=title.get_text()
summary = soup.find('div',class_='lemma-summary')
#獲取到tag中包含的所有文版內(nèi)容包括子孫tag中的內(nèi)容,并將結(jié)果作為Unicode字符串返回
data['summary']=summary.get_text()
return data
nodeSpider的結(jié)構(gòu)很清晰,根據(jù)自己需要修改Html解析類即可。
至此,我們完成了整個打造簡單分布式爬蟲的任務(wù),這其中重在理清爬蟲框架結(jié)構(gòu),更重要的是要自己在理解了框架的基礎(chǔ)上動手實踐。
參考資料:《Python爬蟲開發(fā)與項目實戰(zhàn)》