企業(yè)網(wǎng)站設(shè)計(jì)中應(yīng)注意產(chǎn)品發(fā)布功能優(yōu)化/種子搜索引擎 磁力天堂
?爬蟲專欄:http://t.csdnimg.cn/WfCSx
使用 Beautiful Soup
前面介紹了正則表達(dá)式的相關(guān)用法,但是一旦正則表達(dá)式寫的有問題,得到的可能就不是我們想要的結(jié)果了。而且對于一個(gè)網(wǎng)頁來說,都有一定的特殊結(jié)構(gòu)和層級關(guān)系,而且很多節(jié)點(diǎn)都有 id 或 class 來作區(qū)分,所以借助它們的結(jié)構(gòu)和屬性來提取不也可以嗎?
這一節(jié)中,我們就來介紹一個(gè)強(qiáng)大的解析工具 Beautiful Soup,它借助網(wǎng)頁的結(jié)構(gòu)和屬性等特性來解析網(wǎng)頁。有了它,我們不用再去寫一些復(fù)雜的正則表達(dá)式,只需要簡單的幾條語句,就可以完成網(wǎng)頁中某個(gè)元素的提取。
廢話不多說,接下來就來感受一下 Beautiful Soup 的強(qiáng)大之處吧。
1. Beautiful Soup 簡介
簡單來說,BeautifulSoup 就是 Python 的一個(gè) HTML 或 XML 的解析庫,我們可以用它來方便地從網(wǎng)頁中提取數(shù)據(jù),官方的解釋如下:
BeautifulSoup 提供一些簡單的、Python 式的函數(shù)用來處理導(dǎo)航、搜索、修改分析樹等功能。它是一個(gè)工具箱,通過解析文檔為用戶提供需要抓取的數(shù)據(jù),因?yàn)楹唵?#xff0c;所以不需要多少代碼就可以寫出一個(gè)完整的應(yīng)用程序。 BeautifulSoup 自動(dòng)將輸入文檔轉(zhuǎn)換為 Unicode 編碼,輸出文檔轉(zhuǎn)換為 utf-8 編碼。你不需要考慮編碼方式,除非文檔沒有指定一個(gè)編碼方式,這時(shí)你僅僅需要說明一下原始編碼方式就可以了。 BeautifulSoup 已成為和 lxml、html5lib 一樣出色的 Python 解釋器,為用戶靈活地提供不同的解析策略或強(qiáng)勁的速度。
所以說,利用它可以省去很多煩瑣的提取工作,提高了解析效率。
2. 準(zhǔn)備工作
在開始之前,請確保已經(jīng)正確安裝好了 Beautiful Soup 和 lxml,如果沒有安裝,可以參考第 1 章的內(nèi)容。
3. 解析器
Beautiful Soup 在解析時(shí)實(shí)際上依賴解析器,它除了支持 Python 標(biāo)準(zhǔn)庫中的 HTML 解析器外,還支持一些第三方解析器(比如 lxml)。列出了 Beautiful Soup 支持的解析器。
Beautiful Soup 支持的解析器
解析器 | 使用方法 | 優(yōu)勢 | 劣勢 |
---|---|---|---|
Python 標(biāo)準(zhǔn)庫 | BeautifulSoup(markup, "html.parser") | Python 的內(nèi)置標(biāo)準(zhǔn)庫、執(zhí)行速度適中 、文檔容錯(cuò)能力強(qiáng) | Python 2.7.3 or 3.2.2) 前的版本中文容錯(cuò)能力差 |
LXML HTML 解析器 | BeautifulSoup(markup, "lxml") | 速度快、文檔容錯(cuò)能力強(qiáng) | 需要安裝 C 語言庫 |
LXML XML 解析器 | BeautifulSoup(markup, "xml") | 速度快、唯一支持 XML 的解析器 | 需要安裝 C 語言庫 |
html5lib | BeautifulSoup(markup, "html5lib") | 最好的容錯(cuò)性、以瀏覽器的方式解析文檔、生成 HTML5 格式的文檔 | 速度慢、不依賴外部擴(kuò)展 |
通過以上對比可以看出,lxml 解析器有解析 HTML 和 XML 的功能,而且速度快,容錯(cuò)能力強(qiáng),所以推薦使用它。
如果使用 lxml,那么在初始化 Beautiful Soup 時(shí),可以把第二個(gè)參數(shù)改為 lxml 即可:
from bs4 import BeautifulSoup soup = BeautifulSoup('<p>Hello</p>', 'lxml') print(soup.p.string)
在后面,Beautiful Soup 的用法實(shí)例也統(tǒng)一用這個(gè)解析器來演示。
4. 基本使用
下面首先用實(shí)例來看看 Beautiful Soup 的基本用法:
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://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/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://example.com/elsie" id="link1"><!-- Elsie --></a>,<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>and<a class="sister" href="http://example.com/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,它是一個(gè) HTML 字符串。但是需要注意的是,它并不是一個(gè)完整的 HTML 字符串,因?yàn)?body 和 html 節(jié)點(diǎn)都沒有閉合。接著,我們將它當(dāng)作第一個(gè)參數(shù)傳給 BeautifulSoup 對象,該對象的第二個(gè)參數(shù)為解析器的類型(這里使用 lxml),此時(shí)就完成了 BeaufulSoup 對象的初始化。然后,將這個(gè)對象賦值給 soup 變量。
接下來,就可以調(diào)用 soup 的各個(gè)方法和屬性解析這串 HTML 代碼了。
首先,調(diào)用 prettify() 方法。這個(gè)方法可以把要解析的字符串以標(biāo)準(zhǔn)的縮進(jìn)格式輸出。這里需要注意的是,輸出結(jié)果里面包含 body 和 html 節(jié)點(diǎn),也就是說對于不標(biāo)準(zhǔn)的 HTML 字符串 BeautifulSoup,可以自動(dòng)更正格式。這一步不是由 prettify() 方法做的,而是在初始化 BeautifulSoup 時(shí)就完成了。
然后調(diào)用 soup.title.string,這實(shí)際上是輸出 HTML 中 title 節(jié)點(diǎn)的文本內(nèi)容。所以,soup.title 可以選出 HTML 中的 title 節(jié)點(diǎn),再調(diào)用 string 屬性就可以得到里面的文本了,所以我們可以通過簡單調(diào)用幾個(gè)屬性完成文本提取,這是不是非常方便?
5. 節(jié)點(diǎn)選擇器
直接調(diào)用節(jié)點(diǎn)的名稱就可以選擇節(jié)點(diǎn)元素,再調(diào)用 string 屬性就可以得到節(jié)點(diǎn)內(nèi)的文本了,這種選擇方式速度非??臁H绻麊蝹€(gè)節(jié)點(diǎn)結(jié)構(gòu)層次非常清晰,可以選用這種方式來解析。
選擇元素
下面再用一個(gè)例子詳細(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://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/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 節(jié)點(diǎn)的選擇結(jié)果,輸出結(jié)果正是 title 節(jié)點(diǎn)加里面的文字內(nèi)容。接下來,輸出它的類型,是 bs4.element.Tag 類型,這是 Beautiful Soup 中一個(gè)重要的數(shù)據(jù)結(jié)構(gòu)。經(jīng)過選擇器選擇后,選擇結(jié)果都是這種 Tag 類型。Tag 具有一些屬性,比如 string 屬性,調(diào)用該屬性,可以得到節(jié)點(diǎn)的文本內(nèi)容,所以接下來的輸出結(jié)果正是節(jié)點(diǎn)的文本內(nèi)容。
接下來,我們又嘗試選擇了 head 節(jié)點(diǎn),結(jié)果也是節(jié)點(diǎn)加其內(nèi)部的所有內(nèi)容。最后,選擇了 p 節(jié)點(diǎn)。不過這次情況比較特殊,我們發(fā)現(xiàn)結(jié)果是第一個(gè) p 節(jié)點(diǎn)的內(nèi)容,后面的幾個(gè) p 節(jié)點(diǎn)并沒有選到。也就是說,當(dāng)有多個(gè)節(jié)點(diǎn)時(shí),這種選擇方式只會(huì)選擇到第一個(gè)匹配的節(jié)點(diǎn),其他的后面節(jié)點(diǎn)都會(huì)忽略。
提取信息
上面演示了調(diào)用 string 屬性來獲取文本的值,那么如何獲取節(jié)點(diǎn)屬性的值呢?如何獲取節(jié)點(diǎn)名呢?下面我們來統(tǒng)一梳理一下信息的提取方式。
獲取名稱
可以利用 name 屬性獲取節(jié)點(diǎn)的名稱。這里還是以上面的文本為例,選取 title 節(jié)點(diǎn),然后調(diào)用 name 屬性就可以得到節(jié)點(diǎn)名稱:
print(soup.title.name)
運(yùn)行結(jié)果:
title
獲取屬性
每個(gè)節(jié)點(diǎn)可能有多個(gè)屬性,比如 id 和 class 等,選擇這個(gè)節(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é)果是字典形式,它把選擇的節(jié)點(diǎn)的所有屬性和屬性值組合成一個(gè)字典。接下來,如果要獲取 name 屬性,就相當(dāng)于從字典中獲取某個(gè)鍵值,只需要用中括號加屬性名就可以了。比如,要獲取 name 屬性,就可以通過 attrs['name'] 來得到。
其實(shí)這樣有點(diǎn)煩瑣,還有一種更簡單的獲取方式:可以不用寫 attrs,直接在節(jié)點(diǎn)元素后面加中括號,傳入屬性名就可以獲取屬性值了。樣例如下:
print(soup.p['name']) print(soup.p['class'])
運(yùn)行結(jié)果如下:
dromouse ['title']
這里需要注意的是,有的返回結(jié)果是字符串,有的返回結(jié)果是字符串組成的列表。比如,name 屬性的值是唯一的,返回的結(jié)果就是單個(gè)字符串。而對于 class,一個(gè)節(jié)點(diǎn)元素可能有多個(gè) class,所以返回的是列表。在實(shí)際處理過程中,我們要注意判斷類型。
獲取內(nèi)容
可以利用 string 屬性獲取節(jié)點(diǎn)元素包含的文本內(nèi)容,比如要獲取第一個(gè) p 節(jié)點(diǎn)的文本:
print(soup.p.string)
運(yùn)行結(jié)果如下:
The Dormouse's story
再次注意一下,這里選擇到的 p 節(jié)點(diǎn)是第一個(gè) p 節(jié)點(diǎn),獲取的文本也是第一個(gè) p 節(jié)點(diǎn)里面的文本。
嵌套選擇
在上面的例子中,我們知道每一個(gè)返回結(jié)果都是 bs4.element.Tag 類型,它同樣可以繼續(xù)調(diào)用節(jié)點(diǎn)進(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 屬性,也就是節(jié)點(diǎn)里的文本內(nèi)容。
關(guān)聯(lián)選擇
在做選擇的時(shí)候,有時(shí)候不能做到一步就選到想要的節(jié)點(diǎn)元素,需要先選中某一個(gè)節(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 屬性,示例如下:
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://example.com/elsie" class="sister" id="link1"><span>Elsie</span></a><a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and<a href="http://example.com/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.p.contents)
運(yùn)行結(jié)果如下:
['\n Once upon a time there were three little sisters; and their names were\n ', <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a>, '\n', <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, ' \n and\n ', <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>, '\n and they lived at the bottom of a well.\n ']
可以看到,返回結(jié)果是列表形式。p 節(jié)點(diǎn)里既包含文本,又包含節(jié)點(diǎn),最后會(huì)將它們以列表形式統(tǒng)一返回。
需要注意的是,列表中的每個(gè)元素都是 p 節(jié)點(diǎn)的直接子節(jié)點(diǎn)。比如第一個(gè) a 節(jié)點(diǎn)里面包含一層 span 節(jié)點(diǎn),這相當(dāng)于孫子節(jié)點(diǎn)了,但是返回結(jié)果并沒有單獨(dú)把 span 節(jié)點(diǎn)選出來。所以說,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 were1 <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a> 2 3 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> 4 and5 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a> 6 and they lived at the bottom of a well.
還是同樣的 HTML 文本,這里調(diào)用了 children 屬性來選擇,返回結(jié)果是生成器類型。接下來,我們用 for 循環(huán)輸出相應(yīng)的內(nèi)容。
如果要得到所有的子孫節(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 were1 <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a> 2 3 <span>Elsie</span> 4 Elsie 5 6 7 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> 8 Lacie 9 and10 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a> 11 Tillie 12 and they lived at the bottom of a well.
此時(shí)返回結(jié)果還是生成器。遍歷輸出一下可以看到,這次的輸出結(jié)果就包含了 span 節(jié)點(diǎn)。descendants 會(huì)遞歸查詢所有子節(jié)點(diǎn),得到所有的子孫節(jié)點(diǎn)。
父節(jié)點(diǎn)和祖先節(jié)點(diǎn)
如果要獲取某個(gè)節(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://example.com/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://example.com/elsie" id="link1"> <span>Elsie</span> </a> </p>
這里我們選擇的是第一個(gè) a 節(jié)點(diǎn)的父節(jié)點(diǎn)元素。很明顯,它的父節(jié)點(diǎn)是 p 節(jié)點(diǎn),輸出結(jié)果便是 p 節(jié)點(diǎn)及其內(nèi)部的內(nèi)容。
需要注意的是,這里輸出的僅僅是 a 節(jié)點(diǎn)的直接父節(jié)點(diǎn),而沒有再向外尋找父節(jié)點(diǎn)的祖先節(jié)點(diǎn)。如果想獲取所有的祖先節(jié)點(diǎn),可以調(diào)用 parents 屬性:
html = """ <html><body><p class="story"><a href="http://example.com/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://example.com/elsie" id="link1"> <span>Elsie</span> </a> </p>), (1, <body> <p class="story"> <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a> </p> </body>), (2, <html> <body> <p class="story"> <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a> </p> </body></html>), (3, <html> <body> <p class="story"> <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a> </p> </body></html>)]
可以發(fā)現(xiàn),返回結(jié)果是生成器類型。這里用列表輸出了它的索引和內(nèi)容,而列表中的元素就是 a 節(jié)點(diǎn)的祖先節(jié)點(diǎn)。
兄弟節(jié)點(diǎn)
上面說明了子節(jié)點(diǎn)和父節(jié)點(diǎn)的獲取方式,如果要獲取同級的節(jié)點(diǎn)(也就是兄弟節(jié)點(diǎn)),應(yīng)該怎么辦呢?示例如下:
html = """ <html><body><p class="story">Once upon a time there were three little sisters; and their names were<a href="http://example.com/elsie" class="sister" id="link1"><span>Elsie</span></a>Hello<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and<a href="http://example.com/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 HelloPrev Sibling Once upon a time there were three little sisters; and their names wereNext Siblings [(0, '\n Hello\n '), (1, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>), (2, ' \n and\n '), (3, <a class="sister" href="http://example.com/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)用了 4 個(gè)屬性,其中 next_sibling 和 previous_sibling 分別獲取節(jié)點(diǎn)的下一個(gè)和上一個(gè)兄弟元素,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://example.com/elsie" class="sister" id="link1">Bob</a><a href="http://example.com/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://example.com/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://example.com/elsie" id="link1">Bob</a><a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> </p> ['story']
如果返回結(jié)果是單個(gè)節(jié)點(diǎn),那么可以直接調(diào)用 string、attrs 等屬性獲得其文本和屬性;如果返回結(jié)果是多個(gè)節(jié)點(diǎn)的生成器,則可以轉(zhuǎn)為列表后取出某個(gè)元素,然后再調(diào)用 string、attrs 等屬性獲取其對應(yīng)節(jié)點(diǎn)的文本和屬性。
6. 方法選擇器
前面所講的選擇方法都是通過屬性來選擇的,這種方法非???#xff0c;但是如果進(jìn)行比較復(fù)雜的選擇的話,它就比較煩瑣,不夠靈活了。幸好,Beautiful Soup 還為我們提供了一些查詢方法,比如 find_all 和 find 等,調(diào)用它們,然后傳入相應(yīng)的參數(shù),就可以靈活查詢了。
find_all
find_all,顧名思義,就是查詢所有符合條件的元素,可以給它傳入一些屬性或文本來得到符合條件的元素,功能十分強(qiáng)大。
它的 API 如下:
find_all(name , attrs , recursive , text , **kwargs)
name
我們可以根據(jù)節(jié)點(diǎn)名來查詢元素,下面我們用一個(gè)實(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 節(jié)點(diǎn),返回結(jié)果是列表類型,長度為 2,每個(gè)元素依然都是 bs4.element.Tag 類型。
因?yàn)槎际?Tag 類型,所以依然可以進(jìn)行嵌套查詢。還是同樣的文本,這里查詢出所有 ul 節(jié)點(diǎn)后,再繼續(xù)查詢其內(nèi)部的 li 節(jié)點(diǎn):
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é)果是列表類型,列表中的每個(gè)元素依然還是 Tag 類型。
接下來我們就可以遍歷每個(gè) 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ù)節(jié)點(diǎn)名查詢,我們也可以傳入一些屬性來進(jìn)行查詢,我們用一個(gè)實(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>]
這里查詢的時(shí)候傳入的是 attrs 參數(shù),參數(shù)的類型是字典類型。比如,要查詢 id 為 list-1 的節(jié)點(diǎn),可以傳入 attrs={'id': 'list-1'} 的查詢條件,得到的結(jié)果是列表形式,包含的內(nèi)容就是符合 id 為 list-1 的所有節(jié)點(diǎn)。在上面的例子中,符合條件的元素個(gè)數(shù)是 1,所以結(jié)果是長度為 1 的列表。
對于一些常用的屬性,比如 id 和 class 等,我們可以不用 attrs 來傳遞。比如,要查詢 id 為 list-1 的節(jié)點(diǎn),可以直接傳入 id 這個(gè)參數(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 里是一個(gè)關(guān)鍵字,所以后面需要加一個(gè)下劃線,即 class_='element',返回的結(jié)果依然還是 Tag 組成的列表。
text
text 參數(shù)可用來匹配節(jié)點(diǎn)的文本,傳入的形式可以是字符串,可以是正則表達(dá)式對象,示例如下:
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']
這里有兩個(gè) a 節(jié)點(diǎn),其內(nèi)部包含文本信息。這里在 find_all() 方法中傳入 text 參數(shù),該參數(shù)為正則表達(dá)式對象,結(jié)果返回所有匹配正則表達(dá)式的節(jié)點(diǎn)文本組成的列表。
find
除了 find_all 方法,還有 find 方法,只不過 find 方法返回的是單個(gè)元素,也就是第一個(gè)匹配的元素,而 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é)果不再是列表形式,而是第一個(gè)匹配的節(jié)點(diǎn)元素,類型依然是 Tag 類型。
另外還有許多的查詢方法,用法與前面介紹的 find_all、find 方法完全相同,只不過查詢范圍不同,在此做一下簡單的說明。
find_parents 和 find_parent:前者返回所有祖先節(jié)點(diǎn),后者返回直接父節(jié)點(diǎn)。
find_next_siblings 和 find_next_sibling:前者返回后面所有的兄弟節(jié)點(diǎn),后者返回后面第一個(gè)兄弟節(jié)點(diǎn)。
find_previous_siblings 和 find_previous_sibling:前者返回前面所有的兄弟節(jié)點(diǎn),后者返回前面第一個(gè)兄弟節(jié)點(diǎn)。
find_all_next 和 find_next:前者返回節(jié)點(diǎn)后所有符合條件的節(jié)點(diǎn),后者返回第一個(gè)符合條件的節(jié)點(diǎn)。
find_all_previous 和 find_previous:前者返回節(jié)點(diǎn)前所有符合條件的節(jié)點(diǎn),后者返回第一個(gè)符合條件的節(jié)點(diǎn)。
7. CSS 選擇器
Beautiful Soup 還提供了另外一種選擇器,那就是 CSS 選擇器。如果對 Web 開發(fā)熟悉的話,那么對 CSS 選擇器肯定也不陌生。如果不熟悉的話,可以參考 CSS 選擇器參考手冊 了解。
使用 CSS 選擇器,只需要調(diào)用 select 方法,傳入相應(yīng)的 CSS 選擇器即可,我們用一個(gè)實(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'>
這里我們用了 3 次 CSS 選擇器,返回的結(jié)果均是符合 CSS 選擇器的節(jié)點(diǎn)組成的列表。例如,select('ul li') 則是選擇所有 ul 節(jié)點(diǎn)下面的所有 li 節(jié)點(diǎn),結(jié)果便是所有的 li 節(jié)點(diǎn)組成的列表。
最后一句我們打印輸出了列表中元素的類型,可以看到類型依然是 Tag 類型。
嵌套選擇
select 方法同樣支持嵌套選擇,例如我們先選擇所有 ul 節(jié)點(diǎn),再遍歷每個(gè) 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>]
可以看到正常輸出了遍歷每個(gè) ul 節(jié)點(diǎn)之后,其下的所有 li 節(jié)點(diǎn)組成的列表。
獲取屬性
我們知道節(jié)點(diǎn)類型是 Tag 類型,所以獲取屬性還可以用原來的方法。仍然是上面的 HTML 文本,這里嘗試獲取每個(gè) 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 屬性。此外,還有一個(gè)方法,那就是 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)的文本值。
8. 結(jié)語
到此 BeautifulSoup 的使用介紹基本就結(jié)束了,最后做一下簡單的總結(jié):
-
推薦使用 LXML 解析庫,必要時(shí)使用 html.parser。
-
節(jié)點(diǎn)選擇篩選功能弱但是速度快。
-
建議使用 find、find_all 方法查詢匹配單個(gè)結(jié)果或者多個(gè)結(jié)果。
-
如果對 CSS 選擇器熟悉的話可以使用 select 選擇法。
如果本文對你有幫助不要忘記點(diǎn)贊,收藏+關(guān)注!