BeautifulSoup的使用
我們學(xué)習(xí)了正則表達(dá)式的相關(guān)用法,但是一旦正則寫的有問題,可能得到的就不是我們想要的結(jié)果了,而且對于一個網(wǎng)頁來說,都有一定的特殊的結(jié)構(gòu)和層級關(guān)系,而且很多標(biāo)簽都有id或class來對作區(qū)分,所以我們借助于它們的結(jié)構(gòu)和屬性來提取不也是可以的嗎?
所以,這一節(jié)我們就介紹一個強(qiáng)大的解析工具,叫做BeautiSoup,它就是借助網(wǎng)頁的結(jié)構(gòu)和屬性等特性來解析網(wǎng)頁的工具,有了它我們不用再去寫一些復(fù)雜的正則,只需要簡單的幾條語句就可以完成網(wǎng)頁中某個元素的提取。
廢話不多說,接下來我們就來感受一下BeautifulSoup的強(qiáng)大之處吧。
BeautifulSoup簡介
簡單來說,BeautifulSoup就是Python的一個HTML或XML的解析庫,我們可以用它來方便地從網(wǎng)頁中提取數(shù)據(jù),官方的解釋如下:
BeautifulSoup提供一些簡單的、python式的函數(shù)用來處理導(dǎo)航、搜索、修改分析樹等功能。它是一個工具箱,通過解析文檔為用戶提供需要抓取的數(shù)據(jù),因?yàn)楹唵?,所以不需要多少代碼就可以寫出一個完整的應(yīng)用程序。BeautifulSoup自動將輸入文檔轉(zhuǎn)換為Unicode編碼,輸出文檔轉(zhuǎn)換為utf-8編碼。你不需要考慮編碼方式,除非文檔沒有指定一個編碼方式,這時你僅僅需要說明一下原始編碼方式就可以了。BeautifulSoup已成為和lxml、html6lib一樣出色的python解釋器,為用戶靈活地提供不同的解析策略或強(qiáng)勁的速度。
所以說,利用它我們可以省去很多繁瑣的提取工作,提高解析效率。
安裝
使用之前,我們當(dāng)然需要首先說明一下它的安裝方式。目前BeautifulSoup的最新版本是4.x版本,之前的版本已經(jīng)停止開發(fā)了,推薦使用pip來安裝,安裝命令如下:
pip3 install beautifulsoup4
當(dāng)然也可以從pypi下載whl文件安裝,鏈接如下:
https://pypi./pypi/beautifulsoup4
好,安裝完成之后可以驗(yàn)證一下,寫一段Python程序試驗(yàn)一下。
from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'html.parser')
print(soup.p.string)
運(yùn)行結(jié)果
Hello
如果沒有報錯,則證明安裝沒有問題,關(guān)于它的解析用法我們在后面會詳細(xì)介紹。
注意在這里我們雖然安裝的是beautifulsoup4這個包,但是在引入的時候是引入的bs4,這是因?yàn)檫@個包源代碼本身的庫文件夾名稱就是bs4,所以安裝完成之后,這個庫文件夾就被移入到我們本機(jī)Python3的lib庫里,所以識別到的庫文件名稱就叫做bs4,所以我們引入的時候就引入bs4這個包。因此,包本身的名稱和我們使用時導(dǎo)入的包的名稱并不一定是一致的。
解析器
BeautifulSoup在解析的時候?qū)嶋H上是依賴于解析器的,它除了支持Python標(biāo)準(zhǔn)庫中的HTML解析器,還支持一些第三方的解析器比如lxml,下面我們對BeautifulSoup支持的解析器及它們的一些優(yōu)缺點(diǎn)做一個簡單的對比。
解析器使用方法優(yōu)勢劣勢
Python標(biāo)準(zhǔn)庫BeautifulSoup(markup, 'html.parser')Python的內(nèi)置標(biāo)準(zhǔn)庫、執(zhí)行速度適中 、文檔容錯能力強(qiáng)Python 2.7.3 or 3.2.2)前的版本中文容錯能力差
lxml HTML 解析器BeautifulSoup(markup, 'lxml')速度快、文檔容錯能力強(qiáng)需要安裝C語言庫
lxml XML 解析器BeautifulSoup(markup, 'xml')速度快、唯一支持XML的解析器需要安裝C語言庫
html5libBeautifulSoup(markup, 'html5lib')最好的容錯性、以瀏覽器的方式解析文檔、生成HTML5格式的文檔速度慢、不依賴外部擴(kuò)展
所以通過以上對比可以看出,lxml這個解析器有解析HTML和XML的功能,而且速度快,容錯能力強(qiáng),所以推薦使用這個庫來進(jìn)行解析,但是這里的劣勢是必須安裝一個C語言庫,它叫做lxml,我們在這里依然使用pip安裝即可,命令如下:
pip3 install lxml
安裝完成之后,我們就可以使用lxml這個解析器來解析了,在初始化的時候我們可以把第二個參數(shù)改為lxml,如下:
from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'lxml')
print(soup.p.string)
運(yùn)行結(jié)果是完全一致的,后面BeautifulSoup的用法實(shí)例也統(tǒng)一用這個庫來演示。
基本使用
下面我們首先用一個實(shí)例來感受一下BeautifulSoup的基本使用:
html = '''
<html><head><title>The Dormouse's story</title></head>
<body>
<p class='title' name='dromouse'><b>The Dormouse's story</b></p>
<p class='story'>Once upon a time there were three little sisters; and their names were
<a href='http:///elsie' class='sister' id='link1'><!-- Elsie --></a>,
<a href='http:///lacie' class='sister' id='link2'>Lacie</a> and
<a href='http:///tillie' class='sister' id='link3'>Tillie</a>;
and they lived at the bottom of a well.</p>
<p class='story'>...</p>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.prettify())
print(soup.title.string)
運(yùn)行結(jié)果:
<html>
<head>
<title>
The Dormouse's story
</title>
</head>
<body>
<p class='title' name='dromouse'>
<b>
The Dormouse's story
</b>
</p>
<p class='story'>
Once upon a time there were three little sisters; and their names were
<a class='sister' href='http:///elsie' id='link1'>
<!-- Elsie -->
</a>
,
<a class='sister' href='http:///lacie' id='link2'>
Lacie
</a>
and
<a class='sister' href='http:///tillie' id='link3'>
Tillie
</a>
;
and they lived at the bottom of a well.
</p>
<p class='story'>
...
</p>
</body>
</html>
The Dormouse's story
首先我們聲明了一個變量html,它是一個HTML字符串,但是注意到,它并不是一個完整的HTML字符串,<body>
和<html>
標(biāo)簽都沒有閉合,但是我們將它當(dāng)作第一個參數(shù)傳給BeautifulSoup對象,第二個參數(shù)傳入的是解析器的類型,在這里我們使用lxml,這樣就完成了BeaufulSoup對象的初始化,將它賦值給soup這個變量。
那么接下來我們就可以通過調(diào)用soup的各個方法和屬性對這串HTML代碼解析了。
我們首先調(diào)用了prettify()方法,這個方法可以把要解析的字符串以標(biāo)準(zhǔn)的縮進(jìn)格式輸出,在這里注意到輸出結(jié)果里面包含了</body>
和</html>
標(biāo)簽,也就是說對于不標(biāo)準(zhǔn)的HTML字符串BeautifulSoup可以自動更正格式,這一步實(shí)際上不是由prettify()方法做的,這個更正實(shí)際上在初始化BeautifulSoup時就完成了。
然后我們調(diào)用了soup.title.string
,這個實(shí)際上是輸出了HTML中<title>
標(biāo)簽的文本內(nèi)容。所以soup.title
就可以選擇出HTML中的<title>
標(biāo)簽,再調(diào)用string屬性就可以得到里面的文本了,所以我們就可以通過簡單地調(diào)用幾個屬性就可以完成文本的提取了,是不是非常方便?
標(biāo)簽選擇器
剛才我們選擇元素的時候直接通過調(diào)用標(biāo)簽的名稱就可以選擇節(jié)點(diǎn)元素了,然后再調(diào)用string屬性就可以得到標(biāo)簽內(nèi)的文本了,這種選擇方式速度非??欤绻麊蝹€標(biāo)簽結(jié)構(gòu)話層次非常清晰,可以選用這種方式來解析。
選擇元素
下面我們再用一個例子詳細(xì)說明一下它的選擇方法。
html = '''
<html><head><title>The Dormouse's story</title></head>
<body>
<p class='title' name='dromouse'><b>The Dormouse's story</b></p>
<p class='story'>Once upon a time there were three little sisters; and their names were
<a href='http:///elsie' class='sister' id='link1'><!-- Elsie --></a>,
<a href='http:///lacie' class='sister' id='link2'>Lacie</a> and
<a href='http:///tillie' class='sister' id='link3'>Tillie</a>;
and they lived at the bottom of a well.</p>
<p class='story'>...</p>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.title)
print(type(soup.title))
print(soup.title.string)
print(soup.head)
print(soup.p)
運(yùn)行結(jié)果:
<title>The Dormouse's story</title>
<class 'bs4.element.Tag'>
The Dormouse's story
<head><title>The Dormouse's story</title></head>
<p class='title' name='dromouse'><b>The Dormouse's story</b></p>
在這里我們依然選用了剛才的HTML代碼,我們首先打印輸出了title標(biāo)簽的選擇結(jié)果,輸出結(jié)果正是title標(biāo)簽加里面的文字內(nèi)容。接下來輸出了它的類型,是bs4.element.Tag
類型,這是BeautifulSoup中的一個重要的數(shù)據(jù)結(jié)構(gòu),經(jīng)過選擇器選擇之后,選擇結(jié)果都是這種Tag類型,它具有一些屬性比如string屬性,調(diào)用Tag的string屬性,就可以得到節(jié)點(diǎn)的文本內(nèi)容了,所以接下來的輸出結(jié)果正是節(jié)點(diǎn)的文本內(nèi)容。
接下來我們又嘗試選擇了head標(biāo)簽,結(jié)果也是標(biāo)簽加其內(nèi)部的所有內(nèi)容,再接下來選擇了p標(biāo)簽,不過這次情況比較特殊,我們發(fā)現(xiàn)結(jié)果是第一個p標(biāo)簽的內(nèi)容,后面的幾個p標(biāo)簽并沒有選擇到,也就是說,當(dāng)有多個標(biāo)簽時,這種選擇方式只會選擇到第一個匹配的標(biāo)簽,其他的后面的標(biāo)簽都會忽略。
提取信息
在上面我們演示了調(diào)用string屬性來獲取文本的值,那我們要獲取標(biāo)簽屬性值怎么辦呢?獲取標(biāo)簽名怎么辦呢?下面我們來統(tǒng)一梳理一下信息的提取方式
獲取名稱
可以利用name屬性來獲取標(biāo)簽的名稱。還是以上面的文本為例,我們選取title標(biāo)簽,然后調(diào)用name屬性就可以得到標(biāo)簽名稱。
print(soup.title.name)
運(yùn)行結(jié)果:
title
獲取屬性
每個標(biāo)簽可能有多個屬性,比如id,class等等,我們選擇到這個節(jié)點(diǎn)元素之后,可以調(diào)用attrs獲取所有屬性。
print(soup.p.attrs)
print(soup.p.attrs['name'])
運(yùn)行結(jié)果:
{'class': ['title'], 'name': 'dromouse'}
dromouse
可以看到attrs的返回結(jié)果是字典形式,把選擇的標(biāo)簽的所有屬性和屬性值組合成一個字典,接下來如果要獲取name屬性,就相當(dāng)于從字典中獲取某個鍵值,只需要用中括號加屬性名稱就可以得到結(jié)果了,比如獲取name屬性就可以通過attrs['name']
得到相應(yīng)的屬性值。
其實(shí)這樣的寫法還有點(diǎn)繁瑣,還有一種更簡單的獲取方式,我們可以不用寫attrs,直接節(jié)點(diǎn)元素后面加中括號,傳入屬性名就可以達(dá)到屬性值了,樣例如下:
print(soup.p['name'])
print(soup.p['class'])
運(yùn)行結(jié)果:
dromouse
['title']
在這里注意到有的返回結(jié)果是字符串,有的返回結(jié)果是字符串組成的列表。比如name屬性的值是唯一的,返回的結(jié)果就是單個字符串,而對于class,一個節(jié)點(diǎn)元素可能由多個class,所以返回的是列表,所以在實(shí)際處理過程中要注意判斷類型。
獲取內(nèi)容
可以利用string屬性獲取節(jié)點(diǎn)元素包含的文本內(nèi)容,比如上面的文本我們獲取第一個p標(biāo)簽的文本:
print(soup.p.string)
運(yùn)行結(jié)果:
The Dormouse's story
再次注意一下這里選擇到的p標(biāo)簽是第一個p標(biāo)簽,獲取的文本也就是第一個p標(biāo)簽里面的文本。
嵌套選擇
在上面的例子中我們知道每一個返回結(jié)果都是bs4.element.Tag
類型,它同樣可以繼續(xù)調(diào)用標(biāo)簽進(jìn)行下一步的選擇,比如我們獲取了head節(jié)點(diǎn)元素,我們可以繼續(xù)調(diào)用head來選取其內(nèi)部的head節(jié)點(diǎn)元素。
html = '''
<html><head><title>The Dormouse's story</title></head>
<body>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.head.title)
print(type(soup.head.title))
print(soup.head.title.string)
運(yùn)行結(jié)果:
<title>The Dormouse's story</title>
<class 'bs4.element.Tag'>
The Dormouse's story
第一行結(jié)果是我們調(diào)用了head之后再次調(diào)用了title來選擇的title節(jié)點(diǎn)元素,然后我們緊接著打印輸出了它的類型,可以看到它仍然是bs4.element.Tag
類型,也就是說我們在Tag類型的基礎(chǔ)上再次選擇得到的依然還是Tag類型,每次返回的結(jié)果都相同,所以這樣我們就可以這樣做嵌套的選擇了。
最后輸出了一下它的string屬性,也就是標(biāo)簽里的文本內(nèi)容。
關(guān)聯(lián)選擇
我們在做選擇的時候有時候不能做到一步就可以選擇到想要的節(jié)點(diǎn)元素,有時候在選擇的時候需要先選中某一個節(jié)點(diǎn)元素,然后以它為基準(zhǔn)再選擇它的子節(jié)點(diǎn)、父節(jié)點(diǎn)、兄弟節(jié)點(diǎn)等等。所以在這里我們就介紹下如何來選擇這些節(jié)點(diǎn)元素。
子節(jié)點(diǎn)和子孫節(jié)點(diǎn)
選取到了一個節(jié)點(diǎn)元素之后,如果想要獲取它的直接子節(jié)點(diǎn)可以調(diào)用contents屬性,我們用一個實(shí)例來感受一下:
html = '''
<html>
<head>
<title>The Dormouse's story</title>
</head>
<body>
<p class='story'>
Once upon a time there were three little sisters; and their names were
<a href='http:///elsie' class='sister' id='link1'>
<span>Elsie</span>
</a>
<a href='http:///lacie' class='sister' id='link2'>Lacie</a>
and
<a href='http:///tillie' class='sister' id='link3'>Tillie</a>
and they lived at the bottom of a well.
</p>
<p class='story'>...</p>
'''
運(yùn)行結(jié)果:
['\n Once upon a time there were three little sisters; and their names were\n ', <a class='sister' href='http:///elsie' id='link1'>
<span>Elsie</span>
</a>, '\n', <a class='sister' href='http:///lacie' id='link2'>Lacie</a>, ' \n and\n ', <a class='sister' href='http:///tillie' id='link3'>Tillie</a>, '\n and they lived at the bottom of a well.\n ']
返回的結(jié)果是列表形式,p標(biāo)簽里面既包含文本,又包含標(biāo)簽,返回的結(jié)果會將他們以列表形式都統(tǒng)一返回。
注意得到的列表的每一個元素都是p標(biāo)簽的直接子節(jié)點(diǎn),比如第一個a標(biāo)簽里面包含了一層span標(biāo)簽,這個就相當(dāng)于孫子節(jié)點(diǎn)了,但是返回結(jié)果中并沒有單獨(dú)把span標(biāo)簽選出來作為結(jié)果的一部分,所以說contents屬性得到的結(jié)果是直接子節(jié)點(diǎn)的列表。
同樣地我們可以調(diào)用children屬性,得到相應(yīng)的結(jié)果。
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.children)
for i, child in enumerate(soup.p.children):
print(i, child)
運(yùn)行結(jié)果:
<list_iterator object at 0x1064f7dd8>
0
Once upon a time there were three little sisters; and their names were
1 <a class='sister' href='http:///elsie' id='link1'>
<span>Elsie</span>
</a>
2
3 <a class='sister' href='http:///lacie' id='link2'>Lacie</a>
4
and
5 <a class='sister' href='http:///tillie' id='link3'>Tillie</a>
6
and they lived at the bottom of a well.
還是同樣的HTML文本,在這里我們調(diào)用了children屬性來進(jìn)行選擇,返回結(jié)果可以看到是生成器類型,所以接下來我們用for循環(huán)輸出了一下相應(yīng)的內(nèi)容,內(nèi)容其實(shí)是一樣的,只不過children返回的是生成器類型,而contents返回的是列表類型。
如果我們要得到所有的子孫節(jié)點(diǎn)的話可以調(diào)用descendants屬性。
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.descendants)
for i, child in enumerate(soup.p.descendants):
print(i, child)
運(yùn)行結(jié)果:
<generator object descendants at 0x10650e678>
0
Once upon a time there were three little sisters; and their names were
1 <a class='sister' href='http:///elsie' id='link1'>
<span>Elsie</span>
</a>
2
3 <span>Elsie</span>
4 Elsie
5
6
7 <a class='sister' href='http:///lacie' id='link2'>Lacie</a>
8 Lacie
9
and
10 <a class='sister' href='http:///tillie' id='link3'>Tillie</a>
11 Tillie
12
and they lived at the bottom of a well.
返回結(jié)果還是生成器,遍歷輸出一下可以看到這次的輸出結(jié)果就包含了span標(biāo)簽,descendants會遞歸地查詢所有子節(jié)點(diǎn),得到的是所有的子孫節(jié)點(diǎn)。
父節(jié)點(diǎn)和祖先節(jié)點(diǎn)
如果要獲取某個節(jié)點(diǎn)元素的父節(jié)點(diǎn),可以調(diào)用parent屬性。
html = '''
<html>
<head>
<title>The Dormouse's story</title>
</head>
<body>
<p class='story'>
Once upon a time there were three little sisters; and their names were
<a href='http:///elsie' class='sister' id='link1'>
<span>Elsie</span>
</a>
</p>
<p class='story'>...</p>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.a.parent)
運(yùn)行結(jié)果:
<p class='story'>
Once upon a time there were three little sisters; and their names were
<a class='sister' href='http:///elsie' id='link1'>
<span>Elsie</span>
</a>
</p>
在這里我們選擇的是第一個a標(biāo)簽的父節(jié)點(diǎn)元素,很明顯它的父節(jié)點(diǎn)是p標(biāo)簽,輸出結(jié)果便是p標(biāo)簽及其內(nèi)部的內(nèi)容。
注意到這里輸出的僅僅是a標(biāo)簽的直接父節(jié)點(diǎn),而沒有再向外尋找父節(jié)點(diǎn)的祖先節(jié)點(diǎn),如果我們要想獲取所有的祖先節(jié)點(diǎn),可以調(diào)用parents屬性。
html = '''
<html>
<body>
<p class='story'>
<a href='http:///elsie' class='sister' id='link1'>
<span>Elsie</span>
</a>
</p>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(type(soup.a.parents))
print(list(enumerate(soup.a.parents)))
運(yùn)行結(jié)果:
<class 'generator'>
[(0, <p class='story'>
<a class='sister' href='http:///elsie' id='link1'>
<span>Elsie</span>
</a>
</p>), (1, <body>
<p class='story'>
<a class='sister' href='http:///elsie' id='link1'>
<span>Elsie</span>
</a>
</p>
</body>), (2, <html>
<body>
<p class='story'>
<a class='sister' href='http:///elsie' id='link1'>
<span>Elsie</span>
</a>
</p>
</body></html>), (3, <html>
<body>
<p class='story'>
<a class='sister' href='http:///elsie' id='link1'>
<span>Elsie</span>
</a>
</p>
</body></html>)]
返回結(jié)果是一個生成器類型,我們在這里用列表輸出了它的索引和內(nèi)容,可以發(fā)現(xiàn)列表中的元素就是a標(biāo)簽的祖先節(jié)點(diǎn)。
兄弟節(jié)點(diǎn)
上面說明了子節(jié)點(diǎn)和父節(jié)點(diǎn)的獲取方式,如果要獲取同級的節(jié)點(diǎn)也就是兄弟節(jié)點(diǎn)應(yīng)該怎么辦?我們先用一個實(shí)例來感受一下:
html = '''
<html>
<body>
<p class='story'>
Once upon a time there were three little sisters; and their names were
<a href='http:///elsie' class='sister' id='link1'>
<span>Elsie</span>
</a>
Hello
<a href='http:///lacie' class='sister' id='link2'>Lacie</a>
and
<a href='http:///tillie' class='sister' id='link3'>Tillie</a>
and they lived at the bottom of a well.
</p>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print('Next Sibling', soup.a.next_sibling)
print('Prev Sibling', soup.a.previous_sibling)
print('Next Siblings', list(enumerate(soup.a.next_siblings)))
print('Prev Siblings', list(enumerate(soup.a.previous_siblings)))
運(yùn)行結(jié)果:
Next Sibling
Hello
Prev Sibling
Once upon a time there were three little sisters; and their names were
Next Siblings [(0, '\n Hello\n '), (1, <a class='sister' href='http:///lacie' id='link2'>Lacie</a>), (2, ' \n and\n '), (3, <a class='sister' href='http:///tillie' id='link3'>Tillie</a>), (4, '\n and they lived at the bottom of a well.\n ')]
Prev Siblings [(0, '\n Once upon a time there were three little sisters; and their names were\n ')]
可以看到在這里我們調(diào)用了四個不同的屬性,next_sibling和previous_sibling分別可以獲取節(jié)點(diǎn)的下一個和上一個兄弟元素,next_siblings和previous_siblings則分別返回所有前面和后面的兄弟節(jié)點(diǎn)的生成器。
提取信息
在上面我們講解了關(guān)聯(lián)元素節(jié)點(diǎn)的選擇方法,如果我們想要獲取它們的一些信息,比如文本、屬性等等也是同樣的方法。
html = '''
<html>
<body>
<p class='story'>
Once upon a time there were three little sisters; and their names were
<a href='http:///elsie' class='sister' id='link1'>Bob</a><a href='http:///lacie' class='sister' id='link2'>Lacie</a>
</p>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print('Next Sibling:')
print(type(soup.a.next_sibling))
print(soup.a.next_sibling)
print(soup.a.next_sibling.string)
print('Parent:')
print(type(soup.a.parents))
print(list(soup.a.parents)[0])
print(list(soup.a.parents)[0].attrs['class'])
運(yùn)行結(jié)果:
Next Sibling:
<class 'bs4.element.Tag'>
<a class='sister' href='http:///lacie' id='link2'>Lacie</a>
Lacie
Parent:
<class 'generator'>
<p class='story'>
Once upon a time there were three little sisters; and their names were
<a class='sister' href='http:///elsie' id='link1'>Bob</a><a class='sister' href='http:///lacie' id='link2'>Lacie</a>
</p>
['story']
如果返回結(jié)果是單個節(jié)點(diǎn),那么可以直接調(diào)用string、attrs等屬性來獲得其文本和屬性,如果返回結(jié)果是多個節(jié)點(diǎn)的生成器,則可以轉(zhuǎn)為list后取出某個元素,然后再調(diào)用string、attrs等屬性來獲取其對應(yīng)節(jié)點(diǎn)等文本和屬性。
方法選擇器
前面我們所講的選擇方法都是用.
這種運(yùn)算符來選擇元素的,這種選擇方法非常快,但是如果要進(jìn)行比較復(fù)雜的選擇的話則會比較繁瑣,不夠靈活。所以BeautifulSoup還為我們提供了一些查詢的方法,比如find_all()、find()等方法,我們可以調(diào)用方法然后傳入相應(yīng)等參數(shù)就可以靈活地進(jìn)行查詢了。
最常用的查詢方法莫過于find_all()和find()了,下面我們對它們的用法進(jìn)行詳細(xì)的介紹。
find_all(name , attrs , recursive , text , **kwargs)
find_all,顧名思義,就是查詢所有符合條件的元素,可以給它傳入一些屬性或文本來得到符合條件的元素,功能十分強(qiáng)大。
name
我們可以根據(jù)標(biāo)簽名來查詢元素,下面我們用一個實(shí)例來感受一下:
html='''
<div class='panel'>
<div class='panel-heading'>
<h4>Hello</h4>
</div>
<div class='panel-body'>
<ul class='list' id='list-1'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
<li class='element'>Jay</li>
</ul>
<ul class='list list-small' id='list-2'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(name='ul'))
print(type(soup.find_all(name='ul')[0]))
運(yùn)行結(jié)果:
[<ul class='list' id='list-1'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
<li class='element'>Jay</li>
</ul>, <ul class='list list-small' id='list-2'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
</ul>]
<class 'bs4.element.Tag'>
在這里我們調(diào)用了find_all()方法,傳入了一個name參數(shù),參數(shù)值為ul,也就是說我們想要查詢所有ul標(biāo)簽,返回結(jié)果是list類型,長度為2,每個元素依然都是bs4.element.Tag
類型。
因?yàn)槎际荰ag類型,所以我們依然可以進(jìn)行嵌套查詢,還是同樣的文本,在這里我們查詢出所有ul標(biāo)簽后再繼續(xù)查詢其內(nèi)部的li標(biāo)簽。
for ul in soup.find_all(name='ul'):
print(ul.find_all(name='li'))
運(yùn)行結(jié)果:
[<li class='element'>Foo</li>, <li class='element'>Bar</li>, <li class='element'>Jay</li>]
[<li class='element'>Foo</li>, <li class='element'>Bar</li>]
返回結(jié)果是列表類型,列表中的每個元素依然還是Tag類型。
接下來我們就可以遍歷每個li獲取它的文本了。
for ul in soup.find_all(name='ul'):
print(ul.find_all(name='li'))
for li in ul.find_all(name='li'):
print(li.string)
運(yùn)行結(jié)果:
[<li class='element'>Foo</li>, <li class='element'>Bar</li>, <li class='element'>Jay</li>]
Foo
Bar
Jay
[<li class='element'>Foo</li>, <li class='element'>Bar</li>]
Foo
Bar
attrs
除了根據(jù)標(biāo)簽名查詢,我們也可以傳入一些屬性來進(jìn)行查詢,我們用一個實(shí)例感受一下:
html='''
<div class='panel'>
<div class='panel-heading'>
<h4>Hello</h4>
</div>
<div class='panel-body'>
<ul class='list' id='list-1' name='elements'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
<li class='element'>Jay</li>
</ul>
<ul class='list list-small' id='list-2'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(attrs={'id': 'list-1'}))
print(soup.find_all(attrs={'name': 'elements'}))
運(yùn)行結(jié)果:
[<ul class='list' id='list-1' name='elements'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
<li class='element'>Jay</li>
</ul>]
[<ul class='list' id='list-1' name='elements'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
<li class='element'>Jay</li>
</ul>]
在這里我們查詢的時候傳入的是attrs參數(shù),參數(shù)的類型是字典類型,比如我們要查詢id為list-1的節(jié)點(diǎn),那就可以傳入attrs={'id': 'list-1'}
的查詢條件,得到的結(jié)果是列表形式,包含的內(nèi)容就是符合id為list-1的所有節(jié)點(diǎn),上面的例子中符合條件的元素個數(shù)是1,所以結(jié)果是長度為1的列表。
對于一些常用的屬性比如id、class等,我們可以不用attrs來傳遞,比如我們要查詢id為list-1的節(jié)點(diǎn),我們可以直接傳入id這個參數(shù),還是上面的文本,我們換一種方式來查詢。
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(id='list-1'))
print(soup.find_all(class_='element'))
運(yùn)行結(jié)果:
[<ul class='list' id='list-1'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
<li class='element'>Jay</li>
</ul>]
[<li class='element'>Foo</li>, <li class='element'>Bar</li>, <li class='element'>Jay</li>, <li class='element'>Foo</li>, <li class='element'>Bar</li>]
在這里我們直接傳入id='list-1'
就可以查詢id為list-1的節(jié)點(diǎn)元素了。而對于class來說,由于class在python里是一個關(guān)鍵字,所以在這里后面需要加一個下劃線,class_='element'
,返回的結(jié)果依然還是Tag組成的列表。
text
text參數(shù)可以用來匹配節(jié)點(diǎn)的文本,傳入的形式可以是字符串,可以是正則表達(dá)式對象,我們用一個實(shí)例來感受一下:
import re
html='''
<div class='panel'>
<div class='panel-body'>
<a>Hello, this is a link</a>
<a>Hello, this is a link, too</a>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(text=re.compile('link')))
運(yùn)行結(jié)果:
['Hello, this is a link', 'Hello, this is a link, too']
在這里有兩個a節(jié)點(diǎn),其內(nèi)部包含有文本信息,在這里我們調(diào)用find_all()方法傳入text參數(shù),參數(shù)為正則表達(dá)式對象,結(jié)果會返回所有匹配正則表達(dá)式的節(jié)點(diǎn)文本組成的列表。
find(name , attrs , recursive , text , **kwargs)
除了find_all()方法,還有find()方法,只不過find()方法返回的是單個元素,也就是第一個匹配的元素,而find_all()返回的是所有匹配的元素組成的列表。
html='''
<div class='panel'>
<div class='panel-heading'>
<h4>Hello</h4>
</div>
<div class='panel-body'>
<ul class='list' id='list-1'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
<li class='element'>Jay</li>
</ul>
<ul class='list list-small' id='list-2'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find(name='ul'))
print(type(soup.find(name='ul')))
print(soup.find(class_='list'))
運(yùn)行結(jié)果:
<ul class='list' id='list-1'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
<li class='element'>Jay</li>
</ul>
<class 'bs4.element.Tag'>
<ul class='list' id='list-1'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
<li class='element'>Jay</li>
</ul>
返回結(jié)果不再是列表形式,而是第一個匹配的節(jié)點(diǎn)元素,類型依然是Tag類型。
另外還有許多的查詢方法,用法與前面介紹的find_all()、find()方法完全相同,只不過查詢范圍不同,在此做一下簡單的說明。
find_parents() find_parent()
find_parents()返回所有祖先節(jié)點(diǎn),find_parent()返回直接父節(jié)點(diǎn)。
find_next_siblings() find_next_sibling()
find_next_siblings()返回后面所有兄弟節(jié)點(diǎn),find_next_sibling()返回后面第一個兄弟節(jié)點(diǎn)。
find_previous_siblings() find_previous_sibling()
find_previous_siblings()返回前面所有兄弟節(jié)點(diǎn),find_previous_sibling()返回前面第一個兄弟節(jié)點(diǎn)。
find_all_next() find_next()
find_all_next()返回節(jié)點(diǎn)后所有符合條件的節(jié)點(diǎn), find_next()返回第一個符合條件的節(jié)點(diǎn)。
find_all_previous() 和 find_previous()
find_all_previous()返回節(jié)點(diǎn)后所有符合條件的節(jié)點(diǎn), find_previous()返回第一個符合條件的節(jié)點(diǎn)
CSS選擇器
BeautifulSoup還提供了另外一種選擇器,那就是CSS選擇器,如果對web開發(fā)熟悉對話,CSS選擇器肯定也不陌生,如果不熟悉的話,可以看一下CSS選擇器參考手冊。
使用CSS選擇器,只需要調(diào)用select()方法,傳入相應(yīng)的CSS選擇器即可,我們用一個實(shí)例來感受一下:
html='''
<div class='panel'>
<div class='panel-heading'>
<h4>Hello</h4>
</div>
<div class='panel-body'>
<ul class='list' id='list-1'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
<li class='element'>Jay</li>
</ul>
<ul class='list list-small' id='list-2'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.select('.panel .panel-heading'))
print(soup.select('ul li'))
print(soup.select('#list-2 .element'))
print(type(soup.select('ul')[0]))
運(yùn)行結(jié)果:
[<div class='panel-heading'>
<h4>Hello</h4>
</div>]
[<li class='element'>Foo</li>, <li class='element'>Bar</li>, <li class='element'>Jay</li>, <li class='element'>Foo</li>, <li class='element'>Bar</li>]
[<li class='element'>Foo</li>, <li class='element'>Bar</li>]
<class 'bs4.element.Tag'>
在這里我們用了三次CSS選擇器,返回的結(jié)果均是符合CSS選擇器的節(jié)點(diǎn)組成的列表。例如select('ul li')
則是選擇所有ul節(jié)點(diǎn)下面的所有l(wèi)i節(jié)點(diǎn),結(jié)果便是所有的li節(jié)點(diǎn)組成的列表。
最后一句我們打印輸出了列表中元素的類型,可以看到類型依然是Tag類型。
嵌套選擇
select()方法同樣支持嵌套選擇,例如我們先選擇所有ul節(jié)點(diǎn),再遍歷每個ul節(jié)點(diǎn)選擇其li節(jié)點(diǎn),樣例如下:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for ul in soup.select('ul'):
print(ul.select('li'))
運(yùn)行結(jié)果:
[<li class='element'>Foo</li>, <li class='element'>Bar</li>, <li class='element'>Jay</li>]
[<li class='element'>Foo</li>, <li class='element'>Bar</li>]
可以看到正常輸出了遍歷每個ul節(jié)點(diǎn)之后,其下的所有l(wèi)i節(jié)點(diǎn)組成的列表。
獲取屬性
我們知道節(jié)點(diǎn)類型是Tag類型,所以獲取屬性還是可以用原來的方法獲取,仍然是上面的HTML文本,我們在這里嘗試獲取每個ul節(jié)點(diǎn)的id屬性。
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for ul in soup.select('ul'):
print(ul['id'])
print(ul.attrs['id'])
運(yùn)行結(jié)果:
list-1
list-1
list-2
list-2
可以看到直接傳入中括號和屬性名和通過attrs屬性獲取屬性值都是可以成功的。
獲取文本
那么獲取文本當(dāng)然也可以用前面所講的string屬性,還有一個方法那就是get_text(),同樣可以獲取文本值。
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for li in soup.select('li'):
print('Get Text:', li.get_text())
print('String:', li.string)
運(yùn)行結(jié)果:
Get Text: Foo
String: Foo
Get Text: Bar
String: Bar
Get Text: Jay
String: Jay
Get Text: Foo
String: Foo
Get Text: Bar
String: Bar
二者的效果是完全一致的,都可以獲取到節(jié)點(diǎn)的文本值。
綜述
到此BeautifulSoup的使用介紹基本就結(jié)束了,最后做一下簡單的總結(jié):
推薦使用lxml解析庫,必要時使用html.parser
標(biāo)簽選擇篩選功能弱但是速度快
建議使用find()、find_all() 查詢匹配單個結(jié)果或者多個結(jié)果
如果對CSS選擇器熟悉的話可以使用select()選擇法
Python3網(wǎng)絡(luò)爬蟲精華實(shí)戰(zhàn)視頻教程,大數(shù)據(jù)時代必備技能,重磅推薦!