繼續上一篇文章的內容,上一篇文章中已經將url管理器和下載器寫好了。接下來就是url解析器,總的來說這個模塊是幾個模塊中比較難的。因為通過下載器下載完頁面之後,我們雖然得到了頁面,但是這並不是我們想要的結果。而且由於頁面的代碼很多,我們很難去裡面找到自己想要的數據。所幸,我們下載的是html頁面,它 ...
繼續上一篇文章的內容,上一篇文章中已經將url管理器和下載器寫好了。接下來就是url解析器,總的來說這個模塊是幾個模塊中比較難的。因為通過下載器下載完頁面之後,我們雖然得到了頁面,但是這並不是我們想要的結果。而且由於頁面的代碼很多,我們很難去裡面找到自己想要的數據。所幸,我們下載的是html頁面,它是一種由多個多層次的節點組成的樹型結構的文本文件。所以,相較於txt文件,我們更加容易定位到我們要找的數據塊。現在我們要做的就是去原頁面去分析一下,我們想要的數據到底在哪。
打開百度百科pyton詞條的頁面,然後按F12調出開發者工具。通過使用工具,我們就能定位到頁面的內容:
這樣我們就找到了我們想要的信息處在哪個標簽里了。
1 import bs4 2 import re 3 from urllib.parse import urljoin 4 class HtmlParser(object): 5 """docstring for HtmlParser""" 6 def _get_new_urls(self, url, soup): 7 new_urls = set() 8 links = soup.find_all('a', href = re.compile(r'/item/.')) 9 for link in links: 10 new_url = re.sub(r'(/item/)(.*)', r'\1%s' % link.getText(), link['href']) 11 new_full_url = urljoin(url, new_url) 12 new_urls.add(new_full_url) 13 return new_urls 14 15 def _get_new_data(self, url, soup): 16 res_data = {} 17 #url 18 res_data['url'] = url 19 #<dd class="lemmaWgt-lemmaTitle-title"> 20 title_node = soup.find('dd', class_ = "lemmaWgt-lemmaTitle-title").find('h1') 21 res_data['title'] = title_node.getText() 22 #<div class="lemma-summary" label-module="lemmaSummary"> 23 summary_node = soup.find('div', class_ = "lemma-summary") 24 res_data['summary'] = summary_node.getText() 25 return res_data 26 27 def parse(self, url, html_cont): 28 if url is None or html_cont is None: 29 return 30 soup = bs4.BeautifulSoup(html_cont, 'lxml') 31 new_urls = self._get_new_urls(url, soup) 32 new_data = self._get_new_data(url, soup) 33 return new_urls, new_data
解析器只有一個外部方法就是parse方法,
a.首先它會接受url, html_cont兩個參數,然後進行判斷頁面內容是否為空
b.調用bs4模塊的方法來解析網頁內容,'lxml'為文檔解析器,預設的為html.parser,bs官方推薦我們用lxml,那就聽它的吧,誰讓人家是官方呢。
c.接下來就是調用兩個內部函數來獲取新的url列表和數據
d.最後將url列表和數據返回
這裡有一些註意點
1.bs的方法調用還有一個參數,from_encoding 這個和我在下載器那裡的重覆了,所以我就取消了,兩個的功能是一樣的。
2.獲取url列表的內部方法,需要用到正則表達式,這裡我也是摸著石頭過河,不是很會,中間也調試過許多次。
3.數據是放在字典中的,這樣可以通過key來增改刪除數據。
最好,就直接數據輸出了,這個比較簡單,直接上代碼。
1 class HtmlOutputer(object): 2 """docstring for HtmlOutputer""" 3 def __init__(self): 4 self.datas = [] 5 def collect_data(self, new_data): 6 if new_data is None: 7 return 8 self.datas.append(new_data) 9 def output_html(self): 10 fout = open('output1.html', 'w', encoding = 'utf-8') 11 fout.write('<html>') 12 fout.write('<head><meta charset="utf-8"></head>') 13 fout.write('<body>') 14 fout.write('<table>') 15 for data in self.datas: 16 fout.write('<tr>') 17 fout.write('<td>%s</td>' % data['url']) 18 fout.write('<td>%s</td>' % data['title']) 19 fout.write('<td>%s</td>' % data['summary']) 20 fout.write('</tr>') 21 fout.write('</table>') 22 fout.write('</body>') 23 fout.write('</html>') 24 fout.close()
這裡也有兩個註意點
1.fout = open('output1.html', 'w', encoding = 'utf-8'),這裡的encoding參數一定要加,不然會報錯,在windows平臺,它預設是使用gbk編碼來寫文件的。
2.fout.write('<head><meta charset="utf-8"></head>'),這裡的meta標簽也要加上,因為要告訴瀏覽器使用什麼編碼來渲染頁面,這裡我一開始沒加弄了很久,我打開頁面的內容,發現裡面是中文的,結果瀏覽器展示的就是亂碼。總的來說,因為整個頁面採集過程結果好幾個模塊,所以編碼問題要非常小心,不然少不留神就會出錯。
最後總結,這段程式還有許多方面可以深入探討:
1.頁面的數據量過小,我嘗試了10000個頁面的爬取。一旦數據量劇增之後,就會帶來一下問題,第一是待爬取url和已爬取url就不能放在set集合中了,要麼放到radi緩存伺服器里,要麼放到mysql資料庫中
2.第二,數據也是同樣的,字典也滿足不了了,需要專門的資料庫來存放
3.第三量上去之後,對爬取效率就有要求了,那麼多線程就要加進來
4.第四,一旦佈置好任務,單台伺服器的壓力會過大,而且一旦宕機,風險很大,所以分散式的高可用架構也要跟上來
5.一方面是頁面的內容過於簡單,都是靜態頁面,不涉及登錄,也不涉及ajax動態獲取
6.這隻是數據採集,後續還有建模,分析…………
綜上所述,路還遠的很呢,加油!