已經(jīng)確定目標(biāo)內(nèi)容后,應(yīng)該怎么做?
· 尋找“打印此頁”的鏈接,或者看看網(wǎng)站有沒有 HTML 樣式更友好的移動(dòng)版。
· 尋找隱藏在 JavaScript 文件里的信息。
· 所需信息是否存在于其它網(wǎng)站?網(wǎng)站上顯示的數(shù)據(jù)是不是從其它網(wǎng)站上抓取的?
再來一碗 BeautifulSoup
通過屬性查找標(biāo)簽
CSS 可以讓 HTML 元素呈現(xiàn)出差異化,使那些具有相同修飾元素呈現(xiàn)出不同的樣式。
比如一個(gè)頁面中,小說任務(wù)對(duì)話都是紅色的,任務(wù)名稱都是綠色的。源代碼里的 span 標(biāo)簽,引用了對(duì)應(yīng)的 CSS 屬性,如下所示:
"<span class="red">Heavens! what a virulent attack!</span>" replied <span class="green">the prince</span>, not in the least disconcerted by this reception.
我們可以抓出整個(gè)頁面,然后創(chuàng)建一個(gè) BeautifulSoup 對(duì)象:
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/pages/warandpeace.html")
bsObj = BeautifulSoup(html)
通過 BeautifulSoup 對(duì)象,我們可以用 find_all 函數(shù)抽取只包含在 <span class="green"></span> 標(biāo)簽里的文字,這樣就會(huì)得到一個(gè)人物名稱的 Python 列表:
name_list = bsObj.find_all('span', {'class':'green'})
for name in name_list:
print(name.get_text())
代碼執(zhí)行后就會(huì)按照《戰(zhàn)爭與和平》中的人物出場順序顯示所有的人名。這是怎么實(shí)現(xiàn)的呢?
之前,我們調(diào)用 bsObj.tagName 只能獲取頁面中的第一個(gè)指定的標(biāo)簽?,F(xiàn)在,調(diào)用 bsObj.find_all(tagName, tagAttributes) 可以獲取頁面中所有指定的標(biāo)簽,不再只是第一個(gè)。
獲取人名列表之后,程序遍歷列表中所有的名字,然后打印 name.get_text(),就可以把標(biāo)簽中的內(nèi)容分開顯示了。
find() 和 find_all()
這兩個(gè)函數(shù)非常類似,BeautifulSoup 文檔里兩者的定義是這樣的:
find_all(tag, attrs, recursive, text, limit, keywords)
find(tag, attrs, recursive, text, keywords)
標(biāo)簽參數(shù) tag 前面已經(jīng)介紹過——你可以傳一個(gè)標(biāo)簽的名稱或者多個(gè)標(biāo)簽名稱組成的列表做標(biāo)簽參數(shù)。
屬性參數(shù) attributes 是用一個(gè)Python字典封裝一個(gè)標(biāo)簽的若干屬性和對(duì)應(yīng)的屬性值。例如,下面這個(gè)函數(shù)會(huì)返回 HTML 文檔里紅色與綠色兩種顏色的 span 標(biāo)簽:
.find_all('span', {'class':{'green', 'red'}})
遞歸參數(shù) recursive 是一個(gè)布爾變量。你想抓取 HTML 文檔標(biāo)簽結(jié)構(gòu)里多少層的信息?如果設(shè)置為 True,find_all() 就會(huì)查找標(biāo)簽參數(shù)的所有子標(biāo)簽,以及子標(biāo)簽的子標(biāo)簽。如果設(shè)置為 False,find_all() 就只查找文檔的一級(jí)標(biāo)簽。(默認(rèn)值為 True)
文本參數(shù) text 有點(diǎn)不同,它是用標(biāo)簽的文本內(nèi)容去匹配,而不是用標(biāo)簽的屬性。假如我們想查找前面網(wǎng)頁中包含“the prince”內(nèi)容的標(biāo)簽數(shù)量,我們可以把之前的 find_all() 方法換成下面的代碼:
name_list = bsObj.find_all(text='the prince')
print(len(name_list))
輸出結(jié)果為“7”。
范圍限制參數(shù) limit。find 其實(shí)等價(jià)于 find_all 的limit 等于 1 時(shí)的情形。如果你只對(duì)網(wǎng)頁中獲取的前 n 項(xiàng)結(jié)果感興趣,就可以設(shè)置它。但是前幾項(xiàng)的結(jié)果是按照網(wǎng)頁上的順序排序的。
還有一個(gè)關(guān)鍵詞參數(shù) keyword,可以讓你選擇哪些具有指定屬性的標(biāo)簽。但是,任何關(guān)鍵詞參數(shù)能夠完成的任務(wù),同樣可以用 attrs 解決。
通過標(biāo)簽參數(shù)列表傳到 .find_all() 里獲取一列標(biāo)簽,其實(shí)就是一個(gè)“或”關(guān)系的過濾器。而關(guān)鍵詞參數(shù)可以讓你增加一個(gè)“與”關(guān)系的過濾器來簡化工作。
導(dǎo)航樹
獲取屬性
在網(wǎng)絡(luò)數(shù)據(jù)采集時(shí)你經(jīng)常不需要查找標(biāo)簽的內(nèi)容,而是需要查找標(biāo)簽屬性。比如標(biāo)簽 <a> 指向的URL鏈接包含在 href 屬性中,或者 <img> 標(biāo)簽的圖片文件包含 src 屬性中,這時(shí)獲取標(biāo)簽屬性就變得非常有用了。
對(duì)于一個(gè)標(biāo)簽對(duì)象,可以用下面的代碼獲取它的全部屬性:
myTag.attrs
這行代碼返回的是一個(gè) Python 字典對(duì)象,可以獲取和操作這些屬性。比如要獲取圖片的資源位置 src,可以用下面的代碼:
myTag.attrs['src']
lambda 表達(dá)式
BeautifulSoup 允許我們把特定函數(shù)類型當(dāng)作 find_all() 函數(shù)的參數(shù)。唯一的限制條件是這些函數(shù)必須把一個(gè)標(biāo)簽作為參數(shù)且返回結(jié)果是布爾類型。 BeautifulSoup 用這個(gè)函數(shù)來評(píng)估它遇到的每個(gè)標(biāo)簽對(duì)象,最后把評(píng)估結(jié)果為“真”的標(biāo)簽保留,把其他標(biāo)簽剔除。
例如,下面的代碼就是獲取有兩個(gè)屬性的標(biāo)簽:
soup.find_all(lambda tag: len(tag.attrs) == 2)