
Beautiful Soup 是一個(gè)可以從HTML或XML文件中提取數(shù)據(jù)的Python庫(kù),簡(jiǎn)單來(lái)說(shuō),它能將HTML的標(biāo)簽文件解析成樹(shù)形結(jié)構(gòu),然后方便地獲取到指定標(biāo)簽的對(duì)應(yīng)屬性。
如在上一篇文章通過(guò)爬蟲(chóng)爬取漫畫(huà)圖片,獲取信息純粹用正則表達(dá)式進(jìn)行處理,這種方式即復(fù)雜,代碼的可閱讀性也低。通過(guò)Beautiful Soup庫(kù),我們可以將指定的class或id值作為參數(shù),來(lái)直接獲取到對(duì)應(yīng)標(biāo)簽的相關(guān)數(shù)據(jù),這樣的處理方式簡(jiǎn)潔明了。
當(dāng)前最新的 Beautiful Soup 版本為4.4.0,Beautiful Soup 3 當(dāng)前已停止維護(hù)。
Beautiful Soup 4 可用于 Python2.7 和 Python3.0,本文示例使用的Python版本為2.7。
博主使用的是Mac系統(tǒng),直接通過(guò)命令安裝庫(kù):
sudo easy_install beautifulsoup4
安裝完成后,嘗試包含庫(kù)運(yùn)行:
from bs4 import BeautifulSoup
若沒(méi)有報(bào)錯(cuò),則說(shuō)明庫(kù)已正常安裝完成。
<h3>開(kāi)始</h3>
本文會(huì)通過(guò)這個(gè)網(wǎng)頁(yè)http://reeoo.com來(lái)進(jìn)行示例講解,如下圖所示

<h4>BeautifulSoup 對(duì)象初始化</h4>
將一段文檔傳入 BeautifulSoup 的構(gòu)造方法,就能得到一個(gè)文檔對(duì)象。如下代碼所示,文檔通過(guò)請(qǐng)求url獲?。?/p>
#coding:utf-8
from bs4 import BeautifulSoup
import urllib2
url = 'http://reeoo.com'
request = urllib2.Request(url)
response = urllib2.urlopen(request, timeout=20)
content = response.read()
soup = BeautifulSoup(content, 'html.parser')
request 請(qǐng)求沒(méi)有做異常處理,這里暫時(shí)先忽略。BeautifulSoup 構(gòu)造方法的第二個(gè)參數(shù)為文檔解析器,若不傳入該參數(shù),BeautifulSoup會(huì)自行選擇最合適的解析器來(lái)解析文檔,不過(guò)會(huì)有警告提示。
也可以通過(guò)文件句柄來(lái)初始化,可先將HTML的源碼保存到本地同級(jí)目錄 reo.html,然后將文件名作為參數(shù):
soup = BeautifulSoup(open('reo.html'))
可以打印 soup,輸出內(nèi)容和HTML文本無(wú)二致,此時(shí)它為一個(gè)復(fù)雜的樹(shù)形結(jié)構(gòu),每個(gè)節(jié)點(diǎn)都是Python對(duì)象。
Ps. 接下來(lái)示例代碼中所用到的 soup 都為該soup。
<h4>Tag</h4>
Tag對(duì)象與HTML原生文檔中的標(biāo)簽相同,可以直接通過(guò)對(duì)應(yīng)名字獲取
tag = soup.title
print tag
打印結(jié)果:
<title>Reeoo - web design inspiration and website gallery</title>
<h4>Name</h4>
通過(guò)Tag對(duì)象的name屬性,可以獲取到標(biāo)簽的名稱
print tag.name
# title
<h4>Attributes</h4>
一個(gè)tag可能包含很多屬性,如id、class等,操作tag屬性的方式與字典相同。
例如網(wǎng)頁(yè)中包含縮略圖區(qū)域的標(biāo)簽 article
...
<article class="box">
<div id="main">
<ul id="list">
<li id="sponsor"><div class="sponsor_tips"></div>
<script async type="text/javascript" src="http://cdn.carbonads.com/carbon.js?zoneid=1696&serve=CVYD42T&placement=reeoocom" id="_carbonads_js"></script>
</li>
...
獲取它 class 屬性的值
tag = soup.article
c = tag['class']
print c
# [u'box']
也可以直接通過(guò) .attrs 獲取所有的屬性
tag = soup.article
attrs = tag.attrs
print attrs
# {u'class': [u'box']}
ps. 因?yàn)閏lass屬于多值屬性,所以它的值為數(shù)組。
<h4>tag中的字符串</h4>
通過(guò) string 方法獲取標(biāo)簽中包含的字符串
tag = soup.title
s = tag.string
print s
# Reeoo - web design inspiration and website gallery
<h3>文檔樹(shù)的遍歷</h3>
一個(gè)Tag可能包含多個(gè)字符串或其它的Tag,這些都是這個(gè)Tag的子節(jié)點(diǎn)。Beautiful Soup提供了許多操作和遍歷子節(jié)點(diǎn)的屬性。
<h4>子節(jié)點(diǎn)</h4>
通過(guò)Tag的 name 可以獲取到對(duì)應(yīng)標(biāo)簽,多次調(diào)用這個(gè)方法,可以獲取到子節(jié)點(diǎn)中對(duì)應(yīng)的標(biāo)簽。
如下圖:

我們希望獲取到 article 標(biāo)簽中的 li
tag = soup.article.div.ul.li
print tag
打印結(jié)果:
<li id="sponsor"><div class="sponsor_tips"></div>
<script async="" id="_carbonads_js" src="http://cdn.carbonads.com/carbon.js?zoneid=1696&serve=CVYD42T&placement=reeoocom" type="text/javascript"></script>
</li>
也可以把中間的一些節(jié)點(diǎn)省略,結(jié)果也一致
tag = soup.article.li
通過(guò) . 屬性只能獲取到第一個(gè)tag,若想獲取到所有的 li 標(biāo)簽,可以通過(guò) find_all() 方法
ls = soup.article.div.ul.find_all('li')
獲取到的是包含所有li標(biāo)簽的列表。
tag的 .contents 屬性可以將tag的子節(jié)點(diǎn)以列表的方式輸出:
tag = soup.article.div.ul
contents = tag.contents
打印 contents 可以看到列表中不僅包含了 li 標(biāo)簽內(nèi)容,還包括了換行符 '\n'
過(guò)tag的 .children 生成器,可以對(duì)tag的子節(jié)點(diǎn)進(jìn)行循環(huán)
tag = soup.article.div.ul
children = tag.children
print children
for child in children:
print child
可以看到 children 的類型為 <listiterator object at 0x109cb1850>
.contents 和 .children 屬性僅包含tag的直接子節(jié)點(diǎn),若要遍歷子節(jié)點(diǎn)的子節(jié)點(diǎn),可以通過(guò) .descendants 屬性,方法與前兩者類似,這里不列出來(lái)了。
<h4>父節(jié)點(diǎn)</h4>
通過(guò) .parent 屬性來(lái)獲取某個(gè)元素的父節(jié)點(diǎn),article 的 父節(jié)點(diǎn)為 body。
tag = soup.article
print tag.parent.name
# body
或者通過(guò) .parents 屬性遍歷所有的父輩節(jié)點(diǎn)。
tag = soup.article
for p in tag.parents:
print p.name
<h4>兄弟節(jié)點(diǎn)</h4>
.next_sibling 和 .previous_sibling 屬性用來(lái)插敘兄弟節(jié)點(diǎn),使用方式與其他的節(jié)點(diǎn)類似。
<h3>文檔樹(shù)的搜索</h3>
對(duì)樹(shù)形結(jié)構(gòu)的文檔進(jìn)行特定的搜索是爬蟲(chóng)抓取過(guò)程中最常用的操作。
<h4>find_all()</h4>
find_all(name , attrs , recursive , string , ** kwargs)
<h5>name 參數(shù)</h5>
查找所有名字為 name 的tag
soup.find_all('title')
# [<title>Reeoo - web design inspiration and website gallery</title>]
soup.find_all('footer')
# [<footer id="footer">\n<div class="box">\n<p> ... </div>\n</footer>]
<h5>keyword 參數(shù)</h5>
如果指定參數(shù)的名字不是內(nèi)置的參數(shù)名(name , attrs , recursive , string),則將該參數(shù)當(dāng)成tag的屬性進(jìn)行搜索,不指定tag的話則默認(rèn)為對(duì)所有tag進(jìn)行搜索。
如,搜索所有 id 值為 footer 的標(biāo)簽
soup.find_all(id='footer')
# [<footer id="footer">\n<div class="box">\n<p> ... </div>\n</footer>]
加上標(biāo)簽的參數(shù)
soup.find_all('footer', id='footer')
# [<footer id="footer">\n<div class="box">\n<p> ... </div>\n</footer>]
# 沒(méi)有id值為'footer'的div標(biāo)簽,所以結(jié)果返回為空
soup.find_all('div', id='footer')
# []
獲取所有縮略圖的 div 標(biāo)簽,縮略圖用 class 為 thumb 標(biāo)記
soup.find_all('div', class_='thumb')
這里需要注意一點(diǎn),因?yàn)?class 為Python的保留關(guān)鍵字,所以作為參數(shù)時(shí)加上了下劃線,為“class_”。
指定名字的屬性參數(shù)值可以包括:字符串、正則表達(dá)式、列表、True/False。
<h5>True/False</h5>
是否存在指定的屬性。
搜索所有帶有 target 屬性的標(biāo)簽
soup.find_all(target=True)
搜索所有不帶 target 屬性的標(biāo)簽(仔細(xì)觀察會(huì)發(fā)現(xiàn),搜索結(jié)果還是會(huì)有帶 target 的標(biāo)簽,那是不帶 target 標(biāo)簽的子標(biāo)簽,這里需要注意一下。)
soup.find_all(target=False)
可以指定多個(gè)參數(shù)作為過(guò)濾條件,例如頁(yè)面縮略圖部分的標(biāo)簽如下所示:
...
<li>
<div class="thumb">
<a ></a>
</div>
<div class="title">
<a >AIM Creative Studios</a>
</div>
</li>
...
搜索 src 屬性中包含 reeoo 字符串,并且 class 為 lazy 的標(biāo)簽:
soup.find_all(src=re.compile("reeoo.com"), class_='lazy')
搜索結(jié)果即為所有的縮略圖 img 標(biāo)簽。
有些屬性不能作為參數(shù)使用,如 data-**** 屬性。在上面的例子中,data-original 不能作為參數(shù)使用,運(yùn)行起來(lái)會(huì)報(bào)錯(cuò),SyntaxError: keyword can't be an expression*。
<h5>attrs 參數(shù)</h5>
定義一個(gè)字典參數(shù)來(lái)搜索對(duì)應(yīng)屬性的tag,一定程度上能解決上面提到的不能將某些屬性作為參數(shù)的問(wèn)題。
例如,搜索包含 data-original 屬性的標(biāo)簽
print soup.find_all(attrs={'data-original': True})
搜索 data-original 屬性中包含 reeoo.com 字符串的標(biāo)簽
soup.find_all(attrs={'data-original': re.compile("reeoo.com")})
搜索 data-original 屬性為指定值的標(biāo)簽
soup.find_all(attrs={'data-original': 'http://media.reeoo.com/Bersi Serlini Franciacorta.png!page'})
<h5>string 參數(shù)</h5>
和 name 參數(shù)類似,針對(duì)文檔中的字符串內(nèi)容。
搜索包含 Reeoo 字符串的標(biāo)簽:
soup.find_all(string=re.compile("Reeoo"))
打印搜索結(jié)果可看到包含3個(gè)元素,分別是對(duì)應(yīng)標(biāo)簽里的內(nèi)容,具體見(jiàn)下圖所示



<h5>limit 參數(shù)</h5>
find_all() 返回的是整個(gè)文檔的搜索結(jié)果,如果文檔內(nèi)容較多則搜索過(guò)程耗時(shí)過(guò)長(zhǎng),加上 limit 限制,當(dāng)結(jié)果到達(dá) limit 值時(shí)停止搜索并返回結(jié)果。
搜索 class 為 thumb 的 div 標(biāo)簽,只搜索3個(gè)
soup.find_all('div', class_='thumb', limit=3)
打印結(jié)果為一個(gè)包含3個(gè)元素的列表,實(shí)際滿足結(jié)果的標(biāo)簽在文檔里不止3個(gè)。
<h5>recursive 參數(shù)</h5>
find_all() 會(huì)檢索當(dāng)前tag的所有子孫節(jié)點(diǎn),如果只想搜索tag的直接子節(jié)點(diǎn),可以使用參數(shù) recursive=False。
<h4>find()</h4>
find(name , attrs , recursive , string , ** kwargs)
find() 方法和 find_all() 方法的參數(shù)使用基本一致,只是 find() 的搜索方法只會(huì)返回第一個(gè)滿足要求的結(jié)果,等價(jià)于 find_all() 方法并將limit設(shè)置為1。
soup.find_all('div', class_='thumb', limit=1)
soup.find('div', class_='thumb')
搜索結(jié)果一致,唯一的區(qū)別是 find_all() 返回的是一個(gè)數(shù)組,find() 返回的是一個(gè)元素。
當(dāng)沒(méi)有搜索到滿足條件的標(biāo)簽時(shí),find() 返回 None, 而 find_all() 返回一個(gè)空的列表。
<h4>CSS選擇器</h4>
Tag 或 BeautifulSoup 對(duì)象通過(guò) select() 方法中傳入字符串參數(shù), 即可使用CSS選擇器的語(yǔ)法找到tag。
語(yǔ)義和CSS一致,搜索 article 標(biāo)簽下的 ul 標(biāo)簽中的 li 標(biāo)簽
print soup.select('article ul li')
通過(guò)類名查找,兩行代碼的結(jié)果一致,搜索 class 為 thumb 的標(biāo)簽
soup.select('.thumb')
soup.select('[class~=thumb]')
通過(guò)id查找,搜索 id 為 sponsor 的標(biāo)簽
soup.select('#sponsor')
通過(guò)是否存在某個(gè)屬性來(lái)查找,搜索具有 id 屬性的 li 標(biāo)簽
soup.select('li[id]')
通過(guò)屬性的值來(lái)查找查找,搜索 id 為 sponsor 的 li 標(biāo)簽
soup.select('li[id="sponsor"]')
<h3>其他</h3>
其他的搜索方法還有:
find_parents() 和 find_parent()
find_next_siblings() 和 find_next_sibling()
find_previous_siblings() 和 find_previous_sibling()
...
參數(shù)的作用和 find_all()、find() 差別不大,這里就不再列舉使用方式了。這兩個(gè)方法基本已經(jīng)能滿足絕大部分的查詢需求。
還有一些方法涉及文檔樹(shù)的修改。對(duì)于爬蟲(chóng)來(lái)說(shuō)大部分工作只是檢索頁(yè)面的信息,很少需要對(duì)頁(yè)面源碼做改動(dòng),所以這部分的內(nèi)容也不再列舉。
具體詳細(xì)信息可直接參考Beautiful Soup庫(kù)的官方說(shuō)明文檔
【完】。 :)