更新時間:2021-06-21 來源:黑馬程序員 瀏覽量:
使用lxml庫時需要編寫和測試XPath語句,顯然降低了開發(fā)效率。除了lxml庫之外,還可以使用Beautiful
Soup來提取HTML/XML數(shù)據(jù)。雖然這兩個庫的功能相似,但是Beautiful Soup使用起來更加簡潔方便,受到開發(fā)人員的推崇。
截止到目前,BeautifulSoup(3.2.1版本)已經(jīng)停止開發(fā),官網(wǎng)推薦現(xiàn)在的項目使用beautifulsoup4( Beautiful Soup4版本,簡稱bs4)開發(fā)。
bs4是一個HTML/XML的解析器,其主要功能是解析和提取HTML/XML數(shù)據(jù)。它不僅支持CSS選擇器,而且支持Python標(biāo)準(zhǔn)庫中的HTML解析器,以及l(fā)xml的XML解析器。通過使用這些轉(zhuǎn)化器,實現(xiàn)了慣用的文檔導(dǎo)航和查找方式,節(jié)省了大量的工作時間,提高了開發(fā)項目的效率。
bs4庫會將復(fù)雜的HTML文檔換成樹結(jié)構(gòu)(HITML DoM),這個結(jié)構(gòu)中的每個節(jié)點都是一個Pyhon對象。這些對象可以歸納為如下4種:
(1) bs4.element.Tag類:表示HTML中的標(biāo)簽,是最基本的信息組織單元,它有兩個非常重要的屬性,分別是表示標(biāo)簽名字的name屬性和表示標(biāo)簽屬性的attrs屬性。
(2) bs4.element.NavigableString類:表示HTML中標(biāo)簽的文本(非屬性字符串)。
(3) bs4.BeautifulSoup類:表示HTML DOM中的全部內(nèi)容,支持遍歷文檔樹和搜索文檔樹的大部分方法。
(4) bs4.element.Comment類:表示標(biāo)簽內(nèi)字符串的注釋部分,是一種特殊的Navigable String對象。
使用bs4的一般流程如下:
(1)創(chuàng)建一個BeautifulSoup類型的對象。
根據(jù)HTML或者文件創(chuàng)建BeautifulSoup 對象。
(2)通過BeautifulSoup對象的操作方法進(jìn)行解讀搜索。
根據(jù)DOM樹進(jìn)行各種節(jié)點的搜索( 例如,find_all()方法可以搜索出所有滿足要求的節(jié)點,find()方法只會搜索出第一個滿足要求的節(jié)點),只要獲得了一個節(jié)點,就可以訪問節(jié)點的名稱、屬性和文本。
(3)利用DOM樹結(jié)構(gòu)標(biāo)簽的特性,進(jìn)行更為詳細(xì)的節(jié)點信息提取。
在搜索節(jié)點時,也可以按照節(jié)點的名稱、節(jié)點的屬性或者節(jié)點的文字進(jìn)行搜索。上述流程如下圖所示。
通過一個字符串或者類文件對象(存儲在本地的文件句柄或Web網(wǎng)頁句柄)可以創(chuàng)建BauifulSoup類的對象。 BeautifulSoup類中構(gòu)造方法的語法如下:
def_init_(self, markup="", features=None, builder=None, parse_only=None, from_encoding=None, exclude_encodings=None, **kwargs)
上述方法的一些參數(shù)含義如下:
(1) markup:表示要解析的文檔字符串或文件對象。
(2) features:表示解析器的名稱。
(3) builder:表示指定的解析器。
(4) from_encoding:表示指定的編碼格式。
(5) exclude _encodings:表示排除的編碼格式。 例如,根據(jù)字符串html_doc創(chuàng)建一個BeautifulSoup對象:
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc, 'lxml')
上述示例中,在創(chuàng)建BeautifulSoup實例時共傳入了兩個參數(shù)。其中,第一個參數(shù)表示包含被解析HTML文檔的字符串;第二個參數(shù)表示使用lxml解析器進(jìn)行解析。
目前,bs4 支持的解析器包括Python標(biāo)準(zhǔn)庫、lxml和html5lib。為了讓用戶更好地選擇合適的解析器,下面列舉它們的使用方法和優(yōu)缺點,如表所示。
解析器 | 使用方法 | 優(yōu)勢 | 劣勢 |
lxml HTML解析器 | BeautifulSoup(markup,"lxml") | (1)速度快; (2)文檔容錯能力強(qiáng) | 需要安裝C語言庫 |
Python標(biāo)準(zhǔn)庫 | BeautifulSoup(markup, "html.parser") | (1) Python的內(nèi)置標(biāo)準(zhǔn)庫; (2)執(zhí)行速度適中; (3)文檔容錯能力強(qiáng) | Python 2.7.3或3.2.2之前的版本中文檔容錯能力差 |
lxml XML解析器 | BeautifulSoup(markup, [<<lxml-xml>>]) BeautifulSoup(markup, "xml") | (1)速度快; (2)唯一支持XML的解析器 | 需要安裝C語言庫 |
html5lib | BeautifulSoup(markup, "html5lib") | (1)最好的容錯性; (2)以瀏覽器的方式解析文檔 (3)生成HTML5格式的文檔 | (1)速度慢; (2)不依賴外部擴(kuò)展 |
在創(chuàng)建BeautifulSoup對象時,如果沒有明確地指定解析器,那么BeautifulSoup對象會根據(jù)當(dāng)前系統(tǒng)安裝的庫自動選擇解析器。解析器的選擇順序為:lxml、html5lib、Python標(biāo)準(zhǔn)庫。在下面兩種情況下,選擇解析器的優(yōu)先順序會發(fā)生變化:
(1)要解析的文檔是什么類型,目前支持html、xml和html5。
(2)指定使用哪種解析器。
如果明確指定的解析器沒有安裝,那么BeautifulSoup對象會自動選擇其他方案。但是,目前只有l(wèi)xml解析器支持解析XML文檔,一且沒有安裝lxml庫,就無法得到解析后的對象。
使用print()函數(shù)輸出剛創(chuàng)建的BeantifulSoup對象soup,代碼如下:
print(soup.prettify())
上述示例中調(diào)用了petif()方法進(jìn)行打印,既可以為HTML標(biāo)簽和內(nèi)容增加換行符,又可以對標(biāo)簽做相關(guān)的處理,以便于更加友好地顯示HTML內(nèi)容。為了直觀地比較這兩種情況,下面分別列出直接打印和調(diào)用prettify()方法后打印的結(jié)果。直接使用print()函數(shù)進(jìn)行輸出,示例結(jié)果如下:
<html><head><title>The Dormouse's story</title></head> <body> </body></html>
調(diào)用prettify()方法后進(jìn)行輸出,示例結(jié)果如下:
<html> <head> <title> The Dormouse's story </title> </head> <body> </body> </html>
實際上,網(wǎng)頁中有用的信息都存在于網(wǎng)頁中的文本或者各種不同標(biāo)簽的屬性值,為了能獲得這些有用的網(wǎng)頁信息,可以通過一些查找方法獲取文本或者標(biāo)簽屬性。因此,bs4庫內(nèi)置了一些查找方法,其中常用的兩個方法功能如下:
(1) find()方法:用于查找符合查詢條件的第一 個標(biāo)簽節(jié)點。
(2) find_all()方法:查找所有符合查詢條件的標(biāo)簽節(jié)點,并返回一個列表。
這兩個方法用到的參數(shù)是一樣的,這里以find_all()方法為例,介紹在這個方法中這些參數(shù)的應(yīng)用。find_all()方法的定義如下:
find_all(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)
上述方法中一些重要參數(shù)所表示的含義如下:
1.name參數(shù)
在找所有名字為name的標(biāo)簽,但字符串會被自動忽略。下面是 name參數(shù)的幾種情況:
(1) 傳人字符串:在搜索的方法中傳入一個字符串,BeautifuSoup對象會查找與字符事無全匹配的內(nèi)容。例如:
soup.find_all('b')
上述示例用于查找文檔中所有的<b>標(biāo)簽。
(2)傳人正則表達(dá)式:如果傳入一個正則表達(dá)式,那么BautifulSoup對象會通過re模塊的match()函數(shù)進(jìn)行匹配。下面的示例中,使用正則表達(dá)式"^b"匹配所有以字母b開頭的標(biāo)簽。
import re for tag in soup.find_all(re.compile("^b")) : print(tag.name) #輸出結(jié)果如下 body
(3)傳人列表:如果傳入一個列表,那么BeautifulSoup對象會將與列表中任一元索匹配的內(nèi)容返回。在下面的示例中,找到了文檔中所有的<a>標(biāo)簽和<b>標(biāo)簽。
soup.find_all(["a", "b"]) # 部分輸出結(jié)果如下: [<b>The Dormouse's story</b>, <a classm"sister" href="http://example.com/elsie" 1d="link1">E1sle</a>,
2.attrs參數(shù)
如果某個指定名字的參數(shù)不是搜索方法中內(nèi)置的參數(shù)名,那么在進(jìn)行搜索時,會把該參數(shù)當(dāng)作指定名稱的標(biāo)簽中的屬性來搜索。在下面的示例中,在find_all()方法中傳人名稱為id的參數(shù),這時BeautiflSoup對象會搜索每個標(biāo)簽的id屬性。
soup.find_all(id='link2') # 輸出的結(jié)果可能是: [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
若傳入多個指定名字的參數(shù),則可以同時過濾出標(biāo)簽中的多個屬性。在下面的示例中,既可以搜索每個標(biāo)簽的id屬性,同時又可以搜索href屬性。
import re soup.find_all(href=re.compile("elsie"), id='link1') # 輸出的結(jié)果可能是: [<a class="sister" href="http://example.com/elsie" id="linkl">Elsie</a>]
如果要搜索的標(biāo)簽名稱為class,由于class屬于Python的關(guān)鍵字,所以可在class的后面加上一個下畫線。例如:
soup.find_all("a", class_="sister") # 部分輸出結(jié)果如下: # [<a href="http: //example.com/elsie" id="link1">Elsie</a>,
但是,有些標(biāo)簽的屬性名稱是不能使用的,例如HTML5中的“data-”屬性,在程序中使用時,會出現(xiàn)SyntaxError異常信息。這時,可以通過find_all()方法的attrs參數(shù)傳入一個字典來搜索包含特殊屬性的標(biāo)簽。例如:
data_soup=BeautifulSoup('<div data-foo="value">foo!</div>', 'lxml') data_soup.find_all(data-foo="value") # 程序輸出如下報錯信息: # SyntaxError: keyword can't be an expression data_soup.find_all(attrs={"data-foo": "value"}) # 程序可匹配的結(jié)果 # [<div data-foo="value">foo!</div>]
3.text參數(shù)
通過在find_all()方法中傳人text參數(shù),可以搜索文檔中的字符串內(nèi)容。與name參數(shù)的可選值一樣,text參數(shù)也可以接受字符串、正則表達(dá)式和列表等。例如:
soup.find_all(text="Elsie") # [u'Elsie'] soup.find_all(text=["Tillie", "Elsie", "Lacie"]) # [u'Elsie', u'Lacie', u'Tillie']
limit參數(shù) 在使用find_all()方法返回匹配的結(jié)果時,倘若DOM樹非常大,那么搜索的速度會相當(dāng)慢。這時,如果不需要獲得全部的結(jié)果,就可以使用limit參數(shù)限制返回結(jié)果的數(shù)量,其效果與SQL語句中的limit關(guān)鍵字所產(chǎn)生的效果類似。一旦搜索到結(jié)果的數(shù)量達(dá)到了limit的限制,就會停止搜索。例如:
soup.find_all("a", limit=2)
上述示例會搜索到最多兩個符合搜索條件的標(biāo)簽。
recursive參數(shù) 在調(diào)用find_all()方法時,Beutifuloup對象會檢索當(dāng)前節(jié)點的所有子節(jié)點。這時,如果只想搜索當(dāng)前節(jié)點的直接子節(jié)點,就可以使用參數(shù)recursive=False。例如:
soup.html.find_all("title") # [<title>The Dormouse's story</title>] soup.html.find_all("titile", recursive=False) # []
除了上述兩個常用的方法以外,bs4庫中還提供了一些通過 節(jié)點間的關(guān)系進(jìn)行查我的方法。由于這些方法的參數(shù)和用法跟fnd, alll 方法類似,這里就不再另行介紹。
除了bs4庫提供的操作方法以外,還可以使用CSS選擇器進(jìn)行查找。什么是CSS呢? CSS (Cascading Style Sheets,層疊樣式表)是一種用來表現(xiàn)HTML或XML等文件樣式的計算機(jī)語言,它不僅可以靜態(tài)地修飾網(wǎng)頁,而且可以配合各種腳本語言動態(tài)地對網(wǎng)頁各元索進(jìn)行格式化。
要想使用Css對HTML頁面中的元素實現(xiàn)一對一、一對多或多對一的控制,需要用到CSS選擇器。 每一條CSS樣式定義均由兩部分組成,形式如下:
[code]選擇器{樣式}[/code]
其中,在{} 之前的部分就是“選擇器”。選擇器指明了}中樣式的作用對象,也就是“樣式”作用于網(wǎng)頁中的哪些元素。
為了使用CSS選擇器達(dá)到篩選節(jié)點的目的,在bs4庫的BeautifulSoup類中提供了一個select()方法,該方法會將搜索到的結(jié)果放到列表中。 CSS選擇器的查找方式可分為如下幾種:
1.通過標(biāo)簽查找
在編寫CSS時,標(biāo)簽的名稱不用加任何修飾。調(diào)用select0方法時,可以傳人包含某個標(biāo)簽的字符串。使用CSS選擇器查找標(biāo)簽的示例如下:
soup.select("title") # 查找的結(jié)果可能為 # [<title>The Dormouse's story</title>]
2.通過類名查找
在編寫CSS時,需要在類名的前面加上“.” 。例如,查找類名為sister的標(biāo)簽,示例如下:
soup.select('.sister') # 并查找的結(jié)果可能為 # [<a href="http://example.com/elsie" id="linkl"><!-- Elsie --></a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a> ]
3.通過id名查找
在編寫CSS時,需要在id名稱的前面加上“#”。例如,查找id名為link1的標(biāo)簽,具體示例如下;
soup.select("#link1") # 查找的結(jié)果可能為 # [<a href-"http://example.com/elsie" id="link1">Elsie</a>]
4.通過組合的形式查找
組合查找與編寫CLASS文件時標(biāo)簽名、類名、id
名的組合原理一樣,二者需要用空格分開。例如,在標(biāo)簽p中,查找id值等于link1的內(nèi)容。
soup.select('p #link1') # 手查找的結(jié)果可能為 # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
可以使用“>”將標(biāo)簽與子標(biāo)簽分隔,從而找到某個標(biāo)簽下的直接子標(biāo)簽。例如:
soup.select("head > title") # 查找的結(jié)果可能為 # [<title>The Dormouse's story</title>]
5.通過屬性查找
可以通過屬性元素進(jìn)行查找,屬性需要用中括號括起來。但是,屬性和標(biāo)簽屬于同一個結(jié)點,它們中間不能加空格,否則將無法匹配到。例如:
soup.select('a[href="http://example.com/elsie"]') # 查找的結(jié)果可能為 # [<a href="http: //example. com/elsie" id="link1">Elsie</a>]
同樣,屬性仍然可以與上述查找方式組合,即不在同一節(jié)點的屬性使用空格隔開,同一節(jié)點的屬性之間不加空格。例如:
soup.select('P a[href="http://example.com/elsie"]') # 查找的結(jié)果可能為 # [<a href="http://example.com/elsie" id="link1">Elsie</a>]
上述這些查找方式都會返回一個列表。遍歷這個列表,可以調(diào)用get _text() 方法來獲取節(jié)點 的內(nèi)容。例如:
<br class="Apple-interchange-newline"><div></div> soup=BeautifulSoup(html_doc, 'lxml') for element in soup.select('a'): print(element.get_text()) # 獲取節(jié)點的內(nèi)容 # 獲取到節(jié)點的內(nèi)容可能為 Elsie Lacie Tillie