擊上方「Python開發」,選擇「置頂公眾號」
關鍵時刻,第一時間送達!
Beautiful Soup是一個可以從HTML或XML文件中提取數據的Python庫,簡單來說,它能將HTML的標籤文件解析成樹形結構,然後方便地獲取到指定標籤的對應屬性。
通過Beautiful Soup庫,我們可以將指定的class或id值作為參數,來直接獲取到對應標籤的相關數據,這樣的處理方式簡潔明了。
當前最新的 Beautiful Soup 版本為4.4.0,Beautiful Soup 3 當前已停止維護。
Beautiful Soup 4 可用於 Python2.7 和 Python3.0,本文示例使用的Python版本為2.7。
博主使用的是Mac系統,直接通過命令安裝庫:
sudo easy_install beautifulsoup4
安裝完成後,嘗試包含庫運行:
from bs4 import BeautifulSoup
若沒有報錯,則說明庫已正常安裝完成。
開始本文會通過這個網頁http://reeoo.com來進行示例講解,如下圖所示
將一段文檔傳入 BeautifulSoup 的構造方法,就能得到一個文檔對象。如下代碼所示,文檔通過請求url獲取:
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 請求沒有做異常處理,這裡暫時先忽略。BeautifulSoup 構造方法的第二個參數為文檔解析器,若不傳入該參數,BeautifulSoup會自行選擇最合適的解析器來解析文檔,不過會有警告提示。
也可以通過文件句柄來初始化,可先將HTML的源碼保存到本地同級目錄 reo.html,然後將文件名作為參數:
soup = BeautifulSoup(open('reo.html'))
可以列印 soup,輸出內容和HTML文本無二致,此時它為一個複雜的樹形結構,每個節點都是Python對象。
Ps. 接下來示例代碼中所用到的 soup 都為該soup。
TagTag對象與HTML原生文檔中的標籤相同,可以直接通過對應名字獲取
tag = soup.title
print tag
列印結果:
<title>Reeoo - web design inspiration and website gallery</title>
通過Tag對象的name屬性,可以獲取到標籤的名稱
print tag.name
一個tag可能包含很多屬性,如id、class等,操作tag屬性的方式與字典相同。
例如網頁中包含縮略圖區域的標籤 article
...
<article class="box">
<div id="main">
<ul id="list">
<li id="sponsor"><div class="sponsor_tips"></div>
<script async type="text/javascript" src="//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
也可以直接通過 .attrs 獲取所有的屬性
tag = soup.article
attrs = tag.attrs
print attrs
ps. 因為class屬於多值屬性,所以它的值為數組。
tag中的字符串通過 string 方法獲取標籤中包含的字符串
tag = soup.title
s = tag.string
print s
一個Tag可能包含多個字符串或其它的Tag,這些都是這個Tag的子節點。Beautiful Soup提供了許多操作和遍歷子節點的屬性。
子節點通過Tag的 name 可以獲取到對應標籤,多次調用這個方法,可以獲取到子節點中對應的標籤。
如下圖:
我們希望獲取到 article 標籤中的 li
tag = soup.article.div.ul.li
print tag
列印結果:
<li id="sponsor"><div class="sponsor_tips"></div>
<script async="" id="_carbonads_js" src="//cdn.carbonads.com/carbon.js?zoneid=1696&serve=CVYD42T&placement=reeoocom" type="text/javascript"></script>
</li>
也可以把中間的一些節點省略,結果也一致
tag = soup.article.li
通過 . 屬性只能獲取到第一個tag,若想獲取到所有的 li 標籤,可以通過 find_all() 方法
ls = soup.article.div.ul.find_all('li')
獲取到的是包含所有li標籤的列表。
tag的 .contents 屬性可以將tag的子節點以列表的方式輸出:
tag = soup.article.div.ul
contents = tag.contents
列印 contents 可以看到列表中不僅包含了 li 標籤內容,還包括了換行符 ' '
過tag的 .children 生成器,可以對tag的子節點進行循環
tag = soup.article.div.ul
children = tag.children
print children
for child in children:
print child
可以看到 children 的類型為
.contents 和 .children 屬性僅包含tag的直接子節點,若要遍歷子節點的子節點,可以通過 .descendants 屬性,方法與前兩者類似,這裡不列出來了。
通過 .parent 屬性來獲取某個元素的父節點,article 的 父節點為 body。
tag = soup.article
print tag.parent.name
或者通過 .parents 屬性遍歷所有的父輩節點。
tag = soup.article
for p in tag.parents:
print p.name
.next_sibling 和 .previous_sibling 屬性用來插敘兄弟節點,使用方式與其他的節點類似。
文檔樹的搜索對樹形結構的文檔進行特定的搜索是爬蟲抓取過程中最常用的操作。
find_all()find_all(name , attrs , recursive , string , ** kwargs)
name 參數查找所有名字為 name 的tag
soup.find_all('title')
# [<title>Reeoo - web design inspiration and website gallery</title>]
soup.find_all('footer')
# [<footer id="footer"><div class="box"><p> ... </div></footer>]
如果指定參數的名字不是內置的參數名(name , attrs , recursive , string),則將該參數當成tag的屬性進行搜索,不指定tag的話則默認為對所有tag進行搜索。
如,搜索所有 id 值為 footer 的標籤
soup.find_all(id='footer')
# [<footer id="footer"><div class="box"><p> ... </div></footer>]
加上標籤的參數
soup.find_all('footer', id='footer')
# [<footer id="footer"><div class="box">n<p> ... </div>n</footer>]
# 沒有id值為'footer'的div標籤,所以結果返回為空
soup.find_all('div', id='footer')
# []
獲取所有縮略圖的 div 標籤,縮略圖用 class 為 thumb 標記
soup.find_all('div', class_='thumb')
這裡需要注意一點,因為 class 為Python的保留關鍵字,所以作為參數時加上了下劃線,為「class_」。
指定名字的屬性參數值可以包括:字符串、正則表達式、列表、True/False。
True/False是否存在指定的屬性。
搜索所有帶有 target 屬性的標籤
soup.find_all(target=True)
搜索所有不帶 target 屬性的標籤(仔細觀察會發現,搜索結果還是會有帶 target 的標籤,那是不帶 target 標籤的子標籤,這裡需要注意一下。)
soup.find_all(target=False)
可以指定多個參數作為過濾條件,例如頁面縮略圖部分的標籤如下所示:
...
<li>
<div class="thumb">
<a href="http://reeoo.com/aim-creative-studios"></a>
</div>
<div class="title">
<a href="http://reeoo.com/aim-creative-studios">AIM Creative Studios</a>
</div>
</li>
...
搜索 src 屬性中包含 reeoo 字符串,並且 class 為 lazy 的標籤:
soup.find_all(src=re.compile("reeoo.com"), class_='lazy')
搜索結果即為所有的縮略圖 img 標籤。
有些屬性不能作為參數使用,如 data-**** 屬性。在上面的例子中,data-original 不能作為參數使用,運行起來會報錯,SyntaxError: keyword can't be an expression*。
attrs 參數定義一個字典參數來搜索對應屬性的tag,一定程度上能解決上面提到的不能將某些屬性作為參數的問題。
例如,搜索包含 data-original 屬性的標籤
print soup.find_all(attrs={'data-original': True})
搜索 data-original 屬性中包含 reeoo.com 字符串的標籤
soup.find_all(attrs={'data-original': re.compile("reeoo.com")})
搜索 data-original 屬性為指定值的標籤
soup.find_all(attrs={'data-original': 'http://media.reeoo.com/Bersi Serlini Franciacorta.png!page'})
和 name 參數類似,針對文檔中的字符串內容。
搜索包含 Reeoo 字符串的標籤:
soup.find_all(string=re.compile("Reeoo"))
列印搜索結果可看到包含3個元素,分別是對應標籤裡的內容,具體見下圖所示
find_all() 返回的是整個文檔的搜索結果,如果文檔內容較多則搜索過程耗時過長,加上 limit 限制,當結果到達 limit 值時停止搜索並返回結果。
搜索 class 為 thumb 的 div 標籤,只搜索3個
soup.find_all('div', class_='thumb', limit=3)
列印結果為一個包含3個元素的列表,實際滿足結果的標籤在文檔裡不止3個。
recursive 參數find_all() 會檢索當前tag的所有子孫節點,如果只想搜索tag的直接子節點,可以使用參數 recursive=False。
find()find(name , attrs , recursive , string , ** kwargs)
find() 方法和 find_all() 方法的參數使用基本一致,只是 find() 的搜索方法只會返回第一個滿足要求的結果,等價於 find_all() 方法並將limit設置為1。
soup.find_all('div', class_='thumb', limit=1)
soup.find('div', class_='thumb')
搜索結果一致,唯一的區別是 find_all() 返回的是一個數組,find() 返回的是一個元素。
當沒有搜索到滿足條件的標籤時,find() 返回 None, 而 find_all() 返回一個空的列表。
CSS選擇器Tag 或 BeautifulSoup 對象通過 select() 方法中傳入字符串參數, 即可使用CSS選擇器的語法找到tag。
語義和CSS一致,搜索 article 標籤下的 ul 標籤中的 li 標籤
print soup.select('article ul li')
通過類名查找,兩行代碼的結果一致,搜索 class 為 thumb 的標籤
soup.select('.thumb')
soup.select('[class~=thumb]')
通過id查找,搜索 id 為 sponsor 的標籤
soup.select('#sponsor')
通過是否存在某個屬性來查找,搜索具有 id 屬性的 li 標籤
soup.select('li[id]')
通過屬性的值來查找查找,搜索 id 為 sponsor 的 li 標籤
soup.select('li[id="sponsor"]')
其他的搜索方法還有:
find_parents() 和 find_parent()
find_next_siblings() 和 find_next_sibling()
find_previous_siblings() 和 find_previous_sibling()
…
參數的作用和 find_all()、find() 差別不大,這裡就不再列舉使用方式了。這兩個方法基本已經能滿足絕大部分的查詢需求。
還有一些方法涉及文檔樹的修改。對於爬蟲來說大部分工作只是檢索頁面的信息,很少需要對頁面源碼做改動,所以這部分的內容也不再列舉。
【點擊成為Java大神】