python爬蟲實戰:基礎爬蟲(使用BeautifulSoup4等)
以前學習寫爬蟲程式時候,我沒有系統地學習爬蟲最基本的模組框架,只是實現自己的目標而寫出來的,最近學習基礎的爬蟲,但含有完整的結構,大型爬蟲含有的基礎模組,此專案也有,“麻雀雖小,五臟俱全”,只是沒有考慮優化和穩健性問題。
爬蟲框架
爬蟲框架包括這五大模組,簡單介紹作用:1.爬蟲排程器:協調其他四大模組工作;2.URL管理器:就是管理提供爬取的連結,分為已爬取URL集合和未爬取URL集合;3.html下載器:下載URL的整個html網頁;4.html解析器:將下載的網頁進行解析,獲得有效資料;5.資料儲存器:儲存解析後的資料,以檔案或資料庫形式儲存。
專案準備爬取百度百科的一些名詞解釋和連結,這是小的專案,許多方法簡化處理,做起來簡潔有效。下面是具體步驟:
1、URL管理器
根據作用可知,它包括2個集合,已爬取和未爬取URL連結,所以使用python的set()型別進行去重,防止重複死迴圈。這裡在實踐時候我存在疑問,後面再討論。去重方案主要有3種:1)記憶體去重,2)關係資料庫去重,3)快取資料庫去重;明顯地,大型成熟爬蟲會選擇後兩種,避免記憶體大小限制,而現在尚未成熟的小專案,就使用第1種。
class UrlManager(): # URL管理器 def __init__(self): self.new_urls = set() # 未爬取集合(去重) self.old_urls = set() def has_new_url(self): return self.new_url_size() != 0 # 判斷是否有未爬取 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): # 將新的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) def add_new_urls(self, urls): if urls is None or len(urls) == 0: return for url in urls: self.add_new_url(url) def new_url_size(self): # print(len(self.new_urls)) return len(self.new_urls) def old_url_size(self): return len(self.old_urls)
幾個函式作用很清晰,它會將新的連結加入未爬取集合,爬取過的URL就存入old_urls集合中。
2、HTML下載器
這裡本來用requests包操作,但是我在後面執行程式時出現錯誤,所以後來我用了urllib包代替,而requests是它的高階封裝,按往常也是用這個,具體是函式返回值出問題還是其他原因還沒找出來,給出2個方法,需要大家指正。
import urllib.request class HtmlDownloader(object): def download(self, url): if url is None: return None user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)' headers = {'User-Agent': user_agent} html_content = urllib.request.Request(url,headers=headers)#urlopen(url) response=urllib.request.urlopen(html_content) if response.getcode() == 200: # print(response.read()) return response.read()#.decode('utf-8') return None
這個是成功的方法,使用urllib開啟URL連結,新增請求頭可以更好的模擬正常訪問,根據狀態碼返回內容。而存在問題的requests如下:
import requests#使用requests包爬取,結果提示頁面不存在 class HtmlDownloader(object): # HTML下載器 def download(self, url): if url is None: return None user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)' headers = {'User-Agent': user_agent} respondse = requests.get(url, headers=headers) # print(url) if respondse.status_code != 200: return None # respondse.encoding = 'utf-8' # print(respondse.content) return respondse.content
測試時候可看出urllib的respondse.read()與requests的content都是bytes型別,從程式碼閱讀上大體一樣,經過幾次嘗試,以為是編解碼問題,最後還是沒能找出問題,所以用了urllib進行。
3、HTML下載器
這裡使用BeautifulSoup4進行解析,bs4進行解析可以很簡潔的幾行程式碼完成,功能強大常使用。先分析網頁結構格式,寫出匹配表示式獲取資料。
分析之後標題title = soup.find('dd', class_='lemmaWgt-lemmaTitle-title').find('h1')。獲取tag中包含的所有文字內容,包括子孫tag中的內容,並將結果作為Unicode字串返回,詞條解釋可寫出式子匹配summary = soup.find('div', class_='lemma-summary')。
import re from urllib.parse import urljoin from bs4 import BeautifulSoup class HtmlParser(object): 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() # print(summary_node.get_text()) return data def _get_new_urls(self, page_url, soup): new_urls = set() # print(new_urls) links = soup.find_all('a', href=re.compile('/item/\w+')) for link in links: new_url = link['href'] new_full_url = urljoin(page_url, new_url) new_urls.add(new_full_url) # print(new_full_url) return new_urls def parser(self, page_url, html_cont): if page_url is None or html_cont is None: return soup = BeautifulSoup(html_cont, 'html.parser') new_urls = self._get_new_urls(page_url, soup) new_data = self._get_new_data(page_url, soup) return new_urls, new_data
這裡使用了正則表示式來匹配網頁中的詞條連結:links = soup.find_all('a', href=re.compile('/item/\w+'))
4、資料儲存器
包括兩個方法,store_data:將目標資料存到列表中;out_html:資料輸出外存,這裡存為html格式,也可以根據需要存為csv、txt形式。
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='utf-8'/></head>") fout.write("<body>") fout.write("<table>") for data in self.datas: print(data["title"]) 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()
爬取資料量少的情況下可用以上方法,否則要使用分批儲存,避免發生異常,資料丟失。
5、爬蟲排程器
終於到最後步驟了,也是關鍵的一步,協調上面所有“器”,爬蟲開始!
from craw_pratice.adataOutput import DataOutput from craw_pratice.ahtmlDownloader import HtmlDownloader from craw_pratice.ahtmlParser import HtmlParser from craw_pratice.aurlManeger import UrlManager class spiderman(object): def __init__(self): self.manage = UrlManager() self.downloader = HtmlDownloader() self.parser = HtmlParser() self.output = DataOutput() def crawl(self, root_url): self.manage.add_new_url(root_url) # 判斷URL管理器中是否有新的URL,同時判斷抓去了多少個URL while (self.manage.has_new_url() and self.manage.old_url_size() < 100): try: new_url = self.manage.get_new_url() # 從管理器獲取新的URL print("已經抓取%s個連結: %s" % (self.manage.old_url_size(), new_url)) html = self.downloader.download(new_url) # 下載網頁 # print(html) new_urls, data = self.parser.parser(new_url, html) # 解析器抽取網頁資料 print(data) self.manage.add_new_urls(new_urls) self.output.store_data(data) # 儲存 except Exception: print("crawl failed") self.output.output_html() if __name__ == "__main__": root_url = 'https://baike.baidu.com/item/Python/407313' spider_man = spiderman() spider_man.crawl(root_url)
至此,整個爬蟲專案完成了,效果如圖:
這是我成功後的小總結,而過程並不是如此順利,而是遇到小問題,對程式程式碼不斷debug,比如:
上面說到的requests問題,導致爬取的連結不存在,一直提示頁面不存在。後來採取urllib解決。還有第3中urljoin的呼叫,整個小爬蟲專案我用到的是python3.6,已經把urlparse模組封裝到urllib裡面,所以不採用import parser。
這個專案實踐讓我學習到爬蟲最基本的框架,各個功能都實現模組化,清晰簡潔,為之後實現大型成熟的爬蟲專案做了鋪墊,分享學習心得,希望能學得更好,要繼續努