Beautiful Soup是一個(gè)Python的HTML解析框架,我們可以利用它方便的處理HTML和XML文檔。Beautiful Soup有3和4兩個(gè)版本,目前3已經(jīng)停止開發(fā)。所以我們當(dāng)然還是學(xué)習(xí)最新的Beautiful Soup 4.如果需要詳細(xì)文檔的話可以參考Beautiful Soup中文文檔,這是難得的不是機(jī)翻的文檔。
解析文檔
獲取文檔
Beautiful Soup只是一個(gè)HTML解析庫(kù),所以我們?nèi)绻虢馕鼍W(wǎng)上的內(nèi)容,第一件事情就是把它下載下來(lái)。對(duì)于不同的網(wǎng)站,可能會(huì)對(duì)請(qǐng)求進(jìn)行過(guò)濾。糗事百科的網(wǎng)站就對(duì)沒(méi)有UA的請(qǐng)求直接拒絕掉。所以如果我們要爬這樣的網(wǎng)站,首先需要把請(qǐng)求偽裝成瀏覽器的樣子。具體網(wǎng)站具體分析,經(jīng)過(guò)我測(cè)試,糗事百科只要設(shè)置了UA就可以爬到內(nèi)容,對(duì)于其他網(wǎng)站,你需要測(cè)試一下才能確定什么設(shè)置能管用。
有了Request對(duì)象還不行,還需要實(shí)際發(fā)起請(qǐng)求才行。下面代碼的最后一句就使用了Python3的urllib庫(kù)發(fā)起了一個(gè)請(qǐng)求。urlopen(req)方法返回的是Reponse對(duì)象,我們調(diào)用它的read()函數(shù)獲取整個(gè)結(jié)果字符串。最后調(diào)用decode('utf-8')方法將它解碼為最終結(jié)果,如果不調(diào)用這一步,漢字等非ASCII字符就會(huì)變成\xXXX這樣的轉(zhuǎn)義字符。
import urllib.request as request
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
headers = {'User-Agent': user_agent}
req = request.Request('http://www.qiushibaike.com/', headers=headers)
page = request.urlopen(req).read().decode('utf-8')
查詢和遍歷方法
有了文檔字符串,我們就可以開始解析文檔了。第一步是建立BeautifulSoup對(duì)象,這個(gè)對(duì)象在bs4模塊中。注意在建立對(duì)象的時(shí)候可以額外指定一個(gè)參數(shù),作為實(shí)際的HTML解析器。解析器的值可以指定html.parser,這是內(nèi)置的HTML解析器。更好的選擇是使用下面的lxml解析器,不過(guò)它需要額外安裝一下,我們使用pip install lxml就可以安裝。
import bs4
soup = bs4.BeautifulSoup(page, "lxml")
有了BeautifulSoup對(duì)象,我們就可以開始解析了。首先先來(lái)介紹一下BeautifulSoup的對(duì)象種類,常用的有標(biāo)簽(bs4.element.Tag)以及文本(bs4.element.NavigableString)。還有注釋等對(duì)象,不過(guò)不太常用,所以就不介紹了。在標(biāo)簽對(duì)象上,我們可以調(diào)用一些查找方法例如find_all等等,還有一些屬性返回標(biāo)簽的父節(jié)點(diǎn)、兄弟節(jié)點(diǎn)、直接子節(jié)點(diǎn)、所有子節(jié)點(diǎn)等。在文本對(duì)象上,我們可以調(diào)用.string屬性獲取具體文本。
然后來(lái)說(shuō)說(shuō)BeautifulSoup的遍歷方法?;舅胁僮鞫夹枰ㄟ^(guò)BeautifulSoup對(duì)象來(lái)使用。使用方式主要有兩種:
- 一是直接引用屬性,就是
soup.title這樣的,會(huì)返回第一個(gè)符合條件的節(jié)點(diǎn); - 二是通過(guò)查找方法例如
find_all這樣的,傳入查詢條件來(lái)查找結(jié)果。
再來(lái)說(shuō)說(shuō)查詢條件。查詢條件可以是:
- 字符串,會(huì)返回對(duì)應(yīng)名稱的節(jié)點(diǎn);
- 正則表達(dá)式,按照正則表達(dá)式匹配;
- 列表,會(huì)返回所有匹配列表元素的節(jié)點(diǎn);
- 真值
True,會(huì)返回所有標(biāo)簽節(jié)點(diǎn),不會(huì)返回字符節(jié)點(diǎn); - 方法,我們可以編寫一個(gè)方法,按照自己的規(guī)則過(guò)濾,然后將該方法作為查詢條件。
實(shí)際例子
爬取糗事百科段子
首先打開糗事百科網(wǎng)站,按F12打開開發(fā)人員工具,然后在旁邊點(diǎn)擊分離按鈕把它變成獨(dú)立窗口,然后切到元素標(biāo)簽并最大化窗口。然后點(diǎn)擊那個(gè)鼠標(biāo)按鈕,再返回糗事百科頁(yè)面,并點(diǎn)擊一個(gè)段子,這樣就可以查看段子在HTML文檔的什么位置了。

首先分析一下HTML代碼,然后我們就可以查找所需的內(nèi)容了。這里需要說(shuō)明一下,查詢方法返回的是結(jié)果集,對(duì)結(jié)果集遍歷可以得到標(biāo)簽或者文本對(duì)象。如果調(diào)用標(biāo)簽對(duì)象的.contents,會(huì)返回一個(gè)列表,列表內(nèi)是標(biāo)簽、文本或注釋對(duì)象。動(dòng)態(tài)語(yǔ)言的優(yōu)勢(shì)就是使用靈活,缺點(diǎn)就是沒(méi)有代碼提示。雖然總共代碼沒(méi)幾行,但是還是花了我一番功夫。
divs = soup.find_all('div', class_='article block untagged mb15')
for div in divs:
links = div.find_all('a', href=re.compile(r'/article/\d*'), class_='contentHerf')
for link in links:
contents = link.span.contents
contents = [i for i in contents if not isinstance(i, bs4.element.Tag)]
print(contents)
上面的代碼會(huì)輸出首頁(yè)的所有段子。這樣我們便實(shí)現(xiàn)了半個(gè)爬蟲。為什么是半個(gè)呢?因?yàn)橐粋€(gè)完整的爬蟲可以爬取多個(gè)頁(yè)面,為了簡(jiǎn)便這里只爬首頁(yè),所以只能算半個(gè)爬蟲。不過(guò)如果你想爬取多個(gè)頁(yè)面,代碼稍加修改即可實(shí)現(xiàn)。