WSWP(用python寫爬蟲)筆記二:實現連結獲取和資料儲存爬蟲
前面已經構建了一個獲取連結的爬蟲模組,現在繼續完善這個爬蟲。
分析網頁
要對一個網頁中的資料進行提取,需要先對網頁的結構有一個大體的瞭解,通過在瀏覽器檢視網頁原始碼的方法就能實現。
在瞭解到網頁的結構後,接下來便是獲取自己想要的資料了,個人比較喜歡用Chrome瀏覽器的檢查元素的方式來定位資料在html原始碼中的位置(根據個人喜好來選擇,不過建議直接使用chrome自帶的,後面獲取解析內容的css selector或XPath比較方便)。例如,我想要爬取某篇blog的內容,直接在Chrome中開啟blog的url,並在感興趣的內容上依次點選右鍵 -> 檢查便能定位到資料在網頁原始碼中的位置。如圖所示:
結果如下:
如此便得到了想要爬取的內容存在於標籤< div class=”markdown_views”>之中。
接下來便是通過解析網頁獲取到目標內容並進行抓取了。
資料抓取
測試url: http://example.webscraping.com/places/default/view/China-47
抓取的資料: 國土面積
資料在網頁原始碼中的位置:
可以看出所想抓取的資料的位置在< td class=”w2p_fw”>中。
接下來介紹三種抓取資料的方式,首先是經典的正則表示式匹配法,然後是python中比較流行的Beautifulsoup模組,最後是強大的lxml模組。
正則表示式
對於正則表示式不清楚的,可以先閱讀 正則表示式。
通過正則表示式抓取面積資料時,首先嚐試匹配< td class=”w2p_fw”>中的內容,示例如下:
# re_test.py
from wswp_1_pro.downloader import download
import re
url = 'http://example.webscraping.com/places/default/view/China-47'
html = download(url, headers={'User-agent':'wswp'}, proxy=None, numRetries=2 )
for content in re.findall('<td class="w2p_fw">(.*?)</td>', str(html)):
print(content)
結果如下:
從上面的結果中可以看出,解析出了很多內容,返回到原始碼中可以看出多個屬性都使用了< td class=”w2p_fw”>標籤。如果想要分離出面積屬性,可以只選擇其中的第二個元素即可。
# re_test.py
from wswp_1_pro.downloader import download
import re
url = 'http://example.webscraping.com/places/default/view/China-47'
html = download(url, headers={'User-agent':'wswp'}, proxy=None, numRetries=2)
# for content in re.findall('<td class="w2p_fw">(.*?)</td>', str(html)):
# print(content)
area = re.findall('<td class="w2p_fw">(.*?)</td>', str(html))[1]
print("面積為:", area)
結果如下:
使用正則表示式的缺點是不夠靈活,如果網頁的將屬性值更改以後,則需要重新更改正則表示式。
BeautifulSoup模組
BeautifulSoup在網頁資料解析時非常強大,提供定位內容的便捷介面。
首先通過pip安全該模組。
pip install bs4
使用BeautifulSoup的第一步是將已經下載好的html內容解析為soup文件(對網頁內容進行正確解析並對未閉合的標籤進行閉合)。然後可以使用find()和findall()來定位所需要的元素了。
以下是使用BeautifuSoup提取示例國家面積資料的完整程式碼:
from bs4 import BeautifulSoup
from wswp_1_pro.downloader import download
url = 'http://example.webscraping.com/places/default/view/China-47'
html = download(url, headers={'User-agent':'wswp'}, proxy=None, numRetries=2)
soup = BeautifulSoup(html, "html.parser")
# 先定位面積的父標籤
tr = soup.find(attrs={'id':'places_area__row'})
# 通過父標籤再來定位子標籤
td = tr.find(attrs={'class':'w2p_fw'})
area = td.text
print("面積為:", area)
lxml模組
lxml官網點選這裡^_^。
lxml pdf文件下載。
lxml通過pip安裝。
pip install lxml
lxml模組跟BeautifulSoup一樣,將下載的html解析為統一格式(修復不閉合的標籤)。示例程式碼如下:
from wswp_1_pro.downloader import download
import lxml.html
url = 'http://example.webscraping.com/places/default/view/China-47'
html = download(url, headers={'User-agent':'wswp'}, proxy=None, numRetries=2)
tree = lxml.html.fromstring(str(html))
# 從面積的父標籤開始提取內容
td = tree.cssselect('tr#places_area__row > td.w2p_fw')[0]
area = td.text_content()
print("面積為:", area)
上述程式碼中使用cssselect(CSS選擇器)可通過pip直接安裝。
lxml有幾種不同的方法進行元素的選擇,比如XPath選擇器、CSS選擇器和類似於BeautifulSoup中的find方法。選用CSS選擇器,因為其更加簡潔,並且在能夠解析動態內容。(可通過Chrome中選擇檢查元素,copy –> Copy selector來直接從瀏覽器中獲取到CSS選擇器的引數,然後再根據實際情況進行精簡處理),CSS選擇器的參考教程請點這裡。
使用XPath選擇器的程式碼如下:
from wswp_1_pro.downloader import download
import lxml.html
url = 'http://example.webscraping.com/places/default/view/China-47'
html = download(url, headers={'User-agent':'wswp'}, proxy=None, numRetries=2)
tree = lxml.html.fromstring(str(html))
# 從面積的父標籤開始提取內容
td = tree.xpath('//tr[@id="places_area__row"]/td[@class="w2p_fw"]')[0]
area = td.text_content()
print("面積為:", area)
以上就是從網頁中獲取感興趣的資料的方法,接下來就是向爬蟲程式中新增針對爬取到的資料的處理方法了。
為連結爬蟲新增抓取回調
前面實現的連結爬蟲如果想要複用到抓取其他網站的資料,需要新增一個callback引數參與抓取行為。callback是一個函式,在發生某個特定事件之後會呼叫該函式(在本例中會在網頁下載完成後呼叫)。
實現的callback函式包含url和html兩個引數,並且可以反回一個待爬取的url列表。在linkCrawler.py中新增程式碼,如下所示:
# linkCrawler.py
def linkCrawler(..., scrapeCallBack=None):
...
links = []
if scrapeCallBack:
links.extend(scrapeCallBack(url, html) or [])
...
現在只需要對傳入的callback函式進行定製化處理,就能使用該爬蟲對其他網站進行爬取了。下面對使用lxml抓取國土面積的程式碼進行修改,使其能在callback中使用。
import re
import lxml.html
from wswp_1_pro.linkCrawler import linkCrawler
FIELDS = ['area', 'population', 'iso', 'capital', 'continent', 'tld', 'currency_code', 'currency_name', 'phone', 'postal_code_format', 'postal_code_regex', 'languages', 'neighbours']
def scrapeCallBack(url, html):
if re.search('/view/', url):
tree = lxml.html.fromstring(str(html))
row = [tree.cssselect('table > tr#places_%s__row > td.w2p_fw' % field)[0].text_content() for field in FIELDS]
for text, content in zip(FIELDS,row):
print(text + ' : ' + content)
if __name__ == "__main__":
linkCrawler('http://example.webscraping.com/', '.*?/(index|view)', maxDepth=2, scrapeCallBack=scrapeCallBack)
結果如下:
上一個callback函式會去抓取國家資料,然後顯示出來。通常情況下,在抓取網站時,更希望能夠複用這些資料,下面對callback的功能進行擴充套件,將得到的結果資料儲存到CSV表格中,程式碼如下:
# callBack_test.py
import csv
import re
import lxml.html
from wswp_1_pro.linkCrawler import linkCrawler
class ScrapeCallback:
def __init__(self):
self.writer = csv.writer(open('countries.csv', 'w'))
self.fields = ('area', 'population', 'iso', 'country', 'capital', 'continent', 'tld', 'currency_code', 'currency_name', 'phone', 'postal_code_format', 'postal_code_regex', 'languages', 'neighbours')
self.writer.writerow(self.fields)
def __call__(self, url, html):
if re.search('/view/', url):
tree = lxml.html.fromstring(html)
row = []
for field in self.fields:
row.append(tree.cssselect('table > tr#places_{}__row > td.w2p_fw'.format(field))[0].text_content())
self.writer.writerow(row)
if __name__ == '__main__':
linkCrawler('http://example.webscraping.com/', '/(index|view)', maxDepth=3, scrapeCallBack=ScrapeCallback())
結果如下:
上面的callback,使用了回撥類,而不再是回撥函式,以便保持csv中writer的屬性狀態。csv的writer屬性在構造方法中進行了例項化處理,然後在 __call__ 方法中執行多次寫操作。__call__()是一個特殊的方法,在物件作為函式被呼叫是會呼叫該方法。
成功了,完成了一個可以工作的資料獲取爬蟲了。