基礎爬蟲分5個模塊,使用多個文件相互配合,實現一個相對完善的數據爬取方案,便於之後更完善的爬蟲作準備。html
這裏目的是爬取200條百度百科信息,並生成一個html文件,存儲爬取的站點,詞條,解釋。python
本文思路來源書籍。其代碼部分來源書籍。https://book.douban.com/subject/27061630/app
主文件:爬蟲調度器,經過調用其餘文件中的方法,完成最終功能實現。ide
其餘文件:URL管理器,HTML下載器,HTML解析器,數據存儲器。函數
定義SpiderMan類做爲爬蟲調度器。輸入根URL開始爬取數據而後爬取結束。源碼分析
在爬取過程當中,須要獲取網頁,和解析網頁。測試
解析網頁須要HTML解析器,獲取網頁須要HTML下載器。url
解析網頁須要解析的數據有:URL,TITLE,CONTEXT等。則須要URL管理器和數據存儲器。設計
主文件添加根URL,而後提取該URL,下載該URL內容。調試
根據內容,調用解析器:
解析出該URL中的新URL,存入URL管理器;
解析出該URL中的標題,文本等信息,存入數據存儲器。
完成後開始下一次。這時URL管理器多出了新的URL,提取出新的URL,下載,解析,不斷重複便可。
重複結束以提取出的URL數量超過200則結束。
代碼以下:
from BaseSpider.DataOutput import DataOutput from BaseSpider.HtmlDownloader import HtmlDownloader from BaseSpider.HtmlParser import HtmlParser from BaseSpider.UrlManager import UrlManager class SpiderMan(): def __init__(self): self.manager=UrlManager() self.downloader=HtmlDownloader() self.parser=HtmlParser() self.output=DataOutput() def crawl(self,root_url): self.manager.add_new_url(root_url) while(self.manager.has_new_url() and self.manager.old_url_size()<200): new_url=self.manager.get_new_url() text=self.downloader.download(new_url) if text is None: print('None text') break new_urls,data=self.parser.parser(new_url,text) self.manager.add_new_urls(new_urls) self.output.store_data(data) print(self.manager.old_url_size()) 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?fr=aladdin") print('finish')
做爲最初的設計,應該容許異常拋出,便於查看程序終止的緣由,而後排查錯誤。
下載網頁,返回文本。便可。
import requests import chardet class HtmlDownloader(object): def download(self,url): if url is None: return None user_agent='Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0' headers={'User-Agent':user_agent} r=requests.get(url,headers=headers) if r.status_code is 200: r.encoding=chardet.detect(r.content)['encoding'] return r.text return None
HTML解析器將下載的文本進行解析,須要解析出的數據有:頁面的新URL,頁面的新數據文本。
創建相應的解析器,須要打開源碼對比,而後進行使用源碼分析,使用BeautifulSoup獲取所需信息。
爲了便於主函數調用或者其餘緣由,將全部數據經過parser實現返回,其parser分別調用獲取URL和獲取數據文本的信息。
爲了處理一些不一樣網頁可能抓取的意外狀況致使程序終止,添加了一些判斷。
import re from urllib import parse from bs4 import BeautifulSoup class HtmlParser(object): def parser(self,page_url,html_cont): if page_url is None or html_cont is None: return soup=BeautifulSoup(html_cont,'lxml') new_urls=self.getNewUrls(page_url,soup) new_data=self.getNewData(page_url,soup) return new_urls,new_data def getNewUrls(self,page_url,soup): new_urls=set() links=soup.find_all('a',href=re.compile(r'/item/.*')) for link in links: new_url=link['href'] new_full_url=parse.urljoin(page_url,new_url) new_urls.add(new_full_url) return new_urls def getNewData(self,page_url,soup): data={} data['url']=page_url title=soup.find('dd',class_="basicInfo-item value") if title is not None: data['title']=title.string summary=soup.find('meta',attrs={"name":"description"}) data['summary']=summary['content'] return data else: title=soup.find('meta',attrs={"name":"keywords"}) if title is not None: data['title']=title['content'] summary=soup.find('meta',attrs={"name":"description"}) data['summary']=summary['content'] return data else: data['title']="ERROR!" data['summary']="Please check the url for more information" data['url']=page_url return data
爲了不重複的URL,使用python的set,創建集合初始化。參閱:https://www.runoob.com/python3/python3-set.html
使用old_urls存儲已經訪問過的網址,使用new_urls存入將要提取的網址。
而後寫好has_new_url等方法,輔助主程序調用。當獲得新的URL們時,主程序調用函數將他們存入。
而主程序須要的其餘URL管理方案,如提取,數量斷定等,也在這裏實現。
class UrlManager(): def __init__(self): self.old_urls=set() self.new_urls=set() pass def has_new_url(self): return self.new_url_size()!=0 def new_url_size(self): return len(self.new_urls) def old_url_size(self): return len(self.old_urls) def get_new_url(self): new_url=self.new_urls.pop() self.old_urls.add(new_url) return new_url def add_new_url(self,url): if url is None: return if url not in self.new_urls and url not in self.old_urls: self.new_urls.add(url) pass def add_new_urls(self,urls): if urls is None or len(urls) == 0: return for url in urls: self.add_new_url(url) pass
經過HTML解析器獲取的數據,經過數據存儲器進行存儲。
而最終將數據從內存寫入到本地磁盤,也在該文件實現。
爲了調試美觀,建議是先爬取一兩個數據作好測試,寫好table的寬度設定,加入style='word-break:break-all;word-wrap:break-word;'參數。參閱:https://zhidao.baidu.com/question/1385859725784504260.html
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("<head><meta charset='urf-8'></head>") fout.write("<body>") fout.write("<table border='1' width=1800 style='word-break:break-all;word-wrap:break-word;'>") fout.write("<tr>") fout.write("<td width='300'>URL</td>") fout.write("<td width='100'>標題</td>") fout.write("<td width='1200'>釋義</td>") fout.write("</tr>") for data in self.datas: fout.write("<tr>") fout.write("<td><a href=%s>%s</a></td>"%(data['url'],data['url'])) fout.write("<td>%s</td>"%data['title']) fout.write("<td>%s</td>"%data['summary']) fout.write("</tr>") fout.write("</table>") fout.write("</body>") fout.write("</html>") fout.close()
最終效果:
固然還有一些數據沒有處理好。
完