Python基礎爬蟲
搭建環境:
win10,Python3.6,pycharm,未設虛擬環境
之前寫的爬蟲並沒有架構的思想,且不具備面向對象的特征,現在寫一個基礎爬蟲架構,爬取百度百科,首先介紹一下基礎爬蟲框架的五大模塊功能,包括爬蟲調度器,URL管理器,HTML下載器,HTML解析器,數據存儲器,功能分析如下:
>>爬蟲調度器主要負責統籌其他四個模塊的協調工作
>>URL管理器負責管理URL鏈接,維護已經爬取的URL集合和未爬取的URL集合,提供獲取新URL鏈接的接口
>>HTML下載器用於從URL管理器中獲取未爬取的URL鏈接並下載HTML網頁
>>HTML解析器用於從HTML下載器中獲取已經下載的HTML網頁,並從中解析出新的URL鏈接交給URL管理器,解析出有效數據交給數據存儲器
>>數據存儲器用於將HTML解析器解析出來的數據通過文件或者數據庫的形式存儲起來
URL管理器:
URL管理器主要包括兩個變量,一個是已爬取的URL集合,另一個是未爬取的URL集合;鏈接去重很重要,因為爬取鏈接重復時容易造成死循環,防止鏈接重復方法主要有三種,一是內存去重,二是關系數據庫去重,三是緩存數據庫去重;大型成熟的爬蟲基本上采用緩存數據庫的去重方案,盡可能避免內存大小的限制,又比關系型數據庫去重性能高得多(每爬一個鏈接之前都要在數據庫中查詢一遍);由於基礎爬蟲的爬取數量較小,因此我們使用Python中set這個內存去重方式
在pycharm中新建一個python項目,然後新建一個URLManager.py文件,敲入以下代碼:
class UrlManager(object): def __init__(self): self.new_urls = set()#未爬取URL集合 self.old_urls = set()#已爬取URL集合 def has_new_url(self): ‘‘‘ 判斷是否有未爬取的url :return ‘‘‘ return self.new_url_size()!= 0 def get_new_url(self): ‘‘‘ 獲取一個未爬取的url :return:‘‘‘ new_url = self.new_urls.pop() self.old_urls.add(new_url) return new_url def add_new_url(self,url): ‘‘‘ 將新的url添加到未爬取的URL集合中 :return: ‘‘‘ if url is None: return if url not in self.new_urls and url not in self.old_urls: self.new_urls.add(url) def add_new_urls(self,urls): ‘‘‘ 將新的URL添加到未爬取的URL集合中 ‘‘‘ if urls is None or len(urls)==0: return for url in urls: self.add_new_url(url) def new_url_size(self): ‘‘‘ 獲取未爬取URL集合的大小 ‘‘‘ return len(self.new_urls) def old_url_size(self): ‘‘‘ 獲取已經爬取URL集合的大小 ‘‘‘ return len(self.old_urls)
HTML下載器
HTML下載器用來下載網頁,這時候需要註意網頁的編碼,以保證下載的網頁沒有亂碼,同樣新建一個HtmlDownloader.py
import requests class HtmlDownloader(object): def download(self,url): if url is None: return None user_agent = ‘Your user_agent‘ 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
HTML解析器
在這裏HTML解析器使用BeautifulSoup來解析網頁源碼,其他解析方式還有CSS選擇器,xpath,pyquery(大殺器),正則等等,我們需要提取正文標題,摘要以及網頁中存在的URL鏈接,
同樣新建一個HtmlParser.py文件
看下網頁源碼:
定位到了標題位置,div > h1
所以可以這麽寫:
title = soup.find(‘dd‘,class_=‘lemmaWgt-lemmaTitle-title‘).find(‘h1‘).get_text()
再看摘要位置:
所以可以這麽寫:
summary = soup.find(‘div‘,class_=‘lemma-summary‘).get_text().strip()
再看網頁中的URL鏈接:
大多數是這種格式:<a target="_blank" href="/item/%E4%B8%87%E7%BB%B4%E7%BD%91">萬維網</a>,以及其他格式,因此寫一個如下的提取(其實並不能提取以91結尾的URL,正則太久沒寫忘記了。。):
links = soup.find_all(‘a‘,href=re.compile(r‘/item/[\w\W]*?91‘))
具體代碼:
import re import urllib from bs4 import BeautifulSoup import requests class HtmlParser(object): def parser(self,page_url,html_cont): ‘‘‘ 用於解析網頁內容,抽取URL和數據 ‘‘‘ if page_url is None or html_cont is None: return soup = BeautifulSoup(html_cont,‘html5lib‘) new_urls = self._get_new_urls(page_url,soup) new_data = self._get_new_data(page_url,soup) return new_urls,new_data def _get_new_urls(self,page_url,soup): ‘‘‘ 抽取新的URL集合 ‘‘‘ new_urls = set() links = soup.find_all(‘a‘,href=re.compile(r‘/item/[\w\W]*?91‘)) for link in links: new_url = link[‘href‘] new_full_url= urllib.parse.urljoin(page_url,new_url) new_urls.add(new_full_url) return new_urls def _get_new_data(self,page_url,soup): ‘‘‘ 抽取有效數據 ‘‘‘ 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‘) data[‘summary‘]=summary.get_text().strip() return data ‘‘‘
以下代碼是我用來單獨測試這個模塊的
def download(self,page_url): if page_url is None: return None user_agent = ‘Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36‘ headers = {‘User-Agent‘: user_agent} r = requests.get(page_url,headers=headers) if r.status_code==200: r.encoding=‘utf-8‘ return r.text return None parser = HtmlParser() page_url = ‘https://baike.baidu.com/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/5162711‘ html_cont = parser.download(page_url) new_urls,new_data = parser.parser(page_url,html_cont) print(new_urls,new_data) ‘‘‘
數據存儲器
包括兩個方法,store_data用來將HTML解析模塊解析出來的數據存儲到內存中(list),out_html用來將存儲的數據輸出為HTML格式(利於展示),同樣新建一個DataOutput.py文件
代碼如下:
import codecs class DataOutput(object): def __init__(self): self.datas = [] def store_data(self,data): if data is None: return self.datas.append(data) def output_html(self): fout = codecs.open(‘baike.html‘,‘w‘,encoding=‘utf-8‘) fout.write("<html>") fout.write("<body>") fout.write("<table>") for data in self.datas: fout.write("<tr>") fout.write("<td>%s</td>"%data[‘url‘]) fout.write("<td>%s</td>"%data[‘title‘]) fout.write("<td>%s</td>"%data[‘summary‘]) fout.write("</tr>") self.datas.remove(data) fout.write("</table>") fout.write("</body>") fout.write("</html>") fout.close()
爬蟲調度器
爬蟲調度器要做的工作就是初始化各個模塊,然後通過一個方法傳入入口URL,按照流程運行各個模塊,同樣新建一個SpiderMan.py文件
代碼如下:
from DataOutput import DataOutput from HtmlDownloader import HtmlDownloader from HtmlParser import HtmlParser from UrlManager import UrlManager class SpiderMan(object): def __init__(self): self.manager = UrlManager() self.downloader = HtmlDownloader() self.parser = HtmlParser() self.output = DataOutput() def crawl(self,root_url): #添加入口url self.manager.add_new_url(root_url) #判斷url管理器中是否有新的url,同時判斷抓取了多少個url while(self.manager.has_new_url() and self.manager.old_url_size() < 100): try: #從URL管理器獲取新的url new_url = self.manager.get_new_url() #HTML下載器下載網頁 html = self.downloader.download(new_url) #print(html) # #HTML解析器抽取網頁數據 new_urls,data = self.parser.parser(new_url,html) #print(new_urls,data) # #將抽取的url添加到URL管理器中 self.manager.add_new_urls(new_urls) # #數據存儲器存儲文件 self.output.store_data(data) print("已經抓取%s個鏈接"%self.manager.old_url_size()) except Exception as e: print("crawl failed") self.output.output_html() if __name__ == "__main__": spider_man = SpiderMan() spider_man.crawl("https://baike.baidu.com/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/5162711")
最後輸出的HTML文件如下:
並不是很利於展示。。。再接再厲
Python基礎爬蟲