爬蟲接觸了也有段時間,跟着網上的一些教程,不僅做出了一些實用的小工具,而且對於使用Python爬蟲的整個流程有了大致的了解,也知道了爬蟲是怎么回事。以前做的一些小的試驗,陸續也都會寫成博客,今天記錄的, 是我在慕課網上(http://www.imooc.com/learn/563)學到的一個爬蟲框架,結構清晰合理,很值得學習,這里實現的只是爬蟲最簡單的功能,不涉及用戶的登陸和Cookie驗證,當然,這些都是可以擴充到這個框架里的。
首先,先講一講整個爬蟲框架的思路。
先確定任務:就是以百度百科Python詞條為根,陸續爬下相關的1000個網頁,這個閾值自然是可以調整的。都知道,在百度百科頁面,很多關鍵詞都是有鏈接的,鏈接到另一個網頁上,可以搜索到更多的鏈接,就這樣循環爬取,直到1000個為止。
代碼大概用了150多行,寫成了五個對象,SpiderMain、UrlManger、HtmlDownloader、HtmlParser和HtmlOutputer。SpiderMain是所有其余對象的入口,UrlManger負責管理url,用了兩個set(),一新一舊,因為已經使用過的鏈接就沒有必要再爬一次了,就放在old里,從網頁中解析出來的新的鏈接放在new里。HtmlDownloder負責將網站的數據下載到本地,HtmlParser負責把下載到本地的網頁數據里的相關數據解析出來,這里解析的,一個是鏈接,另一個是詞條的summary,HtmlOupter就負責最后結果的輸出。整個框架對應的是一個線性的流程,結構清晰。
下面看代碼。
| # 實例:爬取python相關一千個網址
# 先導入有關的庫,這一步很重要 import urllib2 from bs4 import BeautifulSoup # 很好用的解析器 import re # 很容易遺漏,即使漏掉程序也不報錯,很隱蔽
# 程序的入口類 class SpiderMain(object): # 初始化對象 def __init__(self): self.urls = UrlManger() self.downloader = HtmlDownloader() self.parser = HtmlParser() self.outputer = HtmlOutputer()
# 最核心的方法 def craw(self, root_url): count = 1 # 方便計數 self.urls.add_new_url(root_url) while self.urls.has_new_url(): try: # 取出url new_url = self.urls.get_new_url() print 'craw %d : %s' % (count, new_url) # 根據url下載網頁內容 html_cont = self.downloader.download(new_url) # 將網頁內容解析為需要的數據 new_urls, new_data = self.parser.parse(new_url, html_cont) # 將數據各自存放 self.urls.add_new_urls(new_urls) self.outputer.collect_data(new_data)
if count == 10: break
count += 1
except: print 'craw failed'
# 將數據輸出為html文件 self.outputer.output_html()
class UrlManger(object):
# 初始化,兩個set(),因為set()里的數據不會重復 def __init__(self): self.new_urls = set() self.old_urls = set()
# 添加一個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)
# 添加多個url,循環使用add_new_url,很巧妙 def add_new_urls(self, urls): if urls is None or len(urls) == 0: return for url in urls: self.add_new_url(url)
# 判斷是否還有沒有爬取的url,返回bool值 def has_new_url(self): return len(self.new_urls) != 0
# 獲取一個url def get_new_url(self): new_url = self.new_urls.pop() # pop用的極好,不僅取得了數據,而且將元素從set()中刪除 self.old_urls.add(new_url) # 使用過的url要放在old_urls里,避免重復爬取 return new_url
# 下載器 class HtmlDownloader(object):
def download(self, url): if url is None: return None
# Python自帶的urllib2下載模塊 response = urllib2.urlopen(url)
# 可以用getcode()方法判斷是否下載成功 if response.getcode() != 200: return None
return response.read()
# Html解析器,核心,倒着看 class HtmlParser(object):
def get_new_urls(self, soup):
# 解析出來的url也放在一個set()里 new_urls = set() # 正則表達式是根據網頁源代碼分析出來的 links = soup.find_all('a', href=re.compile(r'/view/\d+\.htm')) for link in links: new_url = link['href'] # 解析出來的鏈接是個相對url,要合成成一個絕對url page_url = 'http://baike.baidu.com' new_full_url = page_url + new_url new_urls.add(new_full_url) return new_urls
def get_new_data(self, page_url, soup):
# 先構造一個字典 res_data = {}
# url res_data['url'] = page_url
# <dd class="lemmaWgt-lemmaTitle-title"><h1>Python</h1> title_node = soup.find('dd', class_='lemmaWgt-lemmaTitle-title').find('h1') res_data['title'] = title_node.get_text()
# <div class="lemma-summary" label-module="lemmaSummary"> summary_node = soup.find('div', class_='lemma-summary') res_data['summary'] = summary_node.get_text()
return res_data
def parse(self, page_url, html_cont):
if page_url is None or html_cont is None: return
# BeautifulSoup固定用法,先構造一個BeautifulSoup對象 soup = BeautifulSoup(html_cont, 'html.parser', from_encoding='utf-8')
# 方法里面嵌套方法,值得學習 new_urls = self.get_new_urls(soup) new_data = self.get_new_data(page_url, soup) return new_urls, new_data
# 輸出器 class HtmlOutputer(object):
def __init__(self): self.datas = []
def collect_data(self, data): if data is None: return self.datas.append(data)
# 輸出為html文件 def output_html(self): # 以寫的形式打開文件 fout = open('D:/Learn/Code/python/pachong/output.html', 'w')
# 這一句必須加上,否則中文字符會顯示亂碼 fout.write('<meta charset=utf-8" />') fout.write('<html>') fout.write('<body>') fout.write('<table>') fout.write('<th>URL</th>') fout.write('<th>Title</th>') fout.write('<th>Summary</th>')
for data in self.datas: fout.write('<tr>') fout.write('<td>%s</td>' % data['url']) # 注意中文字符的編碼 fout.write('<td>%s</td>' % data['title'].encode('utf-8')) fout.write('<td>%s</td>' % data['summary'].encode('utf-8')) fout.write('</tr>')
fout.write('</table>') fout.write('</body>') fout.write('</html>')
# 不要忘記關閉文件 fout.close()
# 初始化程序 root_url = 'http://baike.baidu.com/view/21087.htm' obj_spider = SpiderMain() obj_spider.craw(root_url) |
結果如下:

打開output.html

沒什么問題!
總結:雖然名為一個簡易的爬蟲框架,其實已經不簡單,難得的是思路如此清晰,結構很嚴謹,很多細節都很值得玩味。就憑這一個項目,也能看得出自己與高手之間的差距。從這些代碼中,我領悟到,在寫代碼之前,一定要先理清思路;其次,這里用到了面向對象(oop)的方式,功能各自獨立,又互相補充,這不就是高內聚、低耦合嗎?要學的還有很多!
源代碼地址:https://github.com/Lucifer25/Learn-Python/blob/master/Web-Crawler/SimpleFramework.py
參考資料:http://www.imooc.com/learn/563
