正則表達(dá)式寫起來費(fèi)勁又出錯(cuò)率高,代替方法之一是BeautifulSoup(另一種是使用 Xpath 神器统倒,后續(xù)再學(xué))。
1 BeautifulSoup 簡(jiǎn)介
引用 BeautifulSoup 官網(wǎng)的說明:
Beautiful Soup is a Python library for pulling data out of HTML and XML files. It works with your favorite parser to provide idiomatic ways of navigating, searching, and modifying the parse tree. It commonly saves programmers hours or days of work.
->BeautifulSoup官網(wǎng)提供中文文檔
2 安裝 BeautifulSoup
目前 BeautifulSoup 最新版本是 4.6.0氛雪,它是支持 Python3的檐薯。所以可以大膽去升級(jí)安裝使用。
安裝方法有:
- 使用pip
比較推薦使用這種方式注暗,既簡(jiǎn)單又方便管理坛缕。
pip3 install beautifulsoup4
# 如果出現(xiàn)因下載失敗導(dǎo)致安裝不上的情況,可以先啟動(dòng) ss 再執(zhí)行安裝命令
# 或者在終端中使用代理
pip --proxy http://代理ip:端口 install beautifulsoup4
- 使用easy_install
easy_install beautifulsoup4
- 使用系統(tǒng)包管理
sudo apt-get install Python-bs4
# 適用于 ubuntu 系統(tǒng)以及 Debian 系統(tǒng)
3 初識(shí)BeautifulSoup
下面的一段HTML代碼將作為例子被多次用到.這是 愛麗絲夢(mèng)游仙境的 的一段內(nèi)容(以后內(nèi)容中簡(jiǎn)稱為 愛麗絲 的文檔):
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><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" id="link1">Elsie</a>,
<a class="sister" id="link2">Lacie</a> and
<a class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
3.1按照標(biāo)準(zhǔn)的縮進(jìn)格式的結(jié)構(gòu)輸出
使用BeautifulSoup解析這段代碼,能夠得到一個(gè) BeautifulSoup 的對(duì)象,并能按照標(biāo)準(zhǔn)的縮進(jìn)格式的結(jié)構(gòu)輸出捆昏。
代碼:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc)
print(soup.prettify())
輸出結(jié)果:
# <html>
# <head>
# <title>
# The Dormouse's story
# </title>
# </head>
# <body>
# <p class="title">
# <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" id="link1">
# Elsie
# </a>
# ,
# <a class="sister" id="link2">
# Lacie
# </a>
# and
# <a class="sister" id="link2">
# Tillie
# </a>
# ; and they lived at the bottom of a well.
# </p>
# <p class="story">
# ...
# </p>
# </body>
# </html>
3.2 幾個(gè)簡(jiǎn)單的瀏覽結(jié)構(gòu)化數(shù)據(jù)的方法
soup.title
# <title>The Dormouse's story</title>
soup.title.name
# u'title'
soup.title.string
# u'The Dormouse's story'
soup.title.parent.name
# u'head'
soup.p
# <p class="title"><b>The Dormouse's story</b></p>
soup.p['class']
# u'title'
soup.a
# <a class="sister" id="link1">Elsie</a>
soup.find_all('a')
# [<a class="sister" id="link1">Elsie</a>,
# <a class="sister" id="link2">Lacie</a>,
# <a class="sister" id="link3">Tillie</a>]
soup.find(id="link3")
# <a class="sister" id="link3">Tillie</a>
3.3 從文檔中找到所有<a>標(biāo)簽的鏈接
for link in soup.find_all('a'):
print(link.get('href'))
# http://example.com/elsie
# http://example.com/lacie
# http://example.com/tillie
3.4 從文檔中獲取所有文字內(nèi)容
print(soup.get_text())
# The Dormouse's story
#
# The Dormouse's story
#
# Once upon a time there were three little sisters; and their names were
# Elsie,
# Lacie and
# Tillie;
# and they lived at the bottom of a well.
#
# ...
4 BeautifulSoup詳解
4.1 如何使用
將一段文檔傳入BeautifulSoup 的構(gòu)造方法,就能得到一個(gè)文檔的對(duì)象, 可以傳入一段字符串或一個(gè)文件句柄.
舉個(gè)栗子:
from bs4 import BeautifulSoup
soup = BeautifulSoup(open("index.html")) #文件句柄
soup = BeautifulSoup("<html>data</html>") #字符串
首先,文檔被轉(zhuǎn)換成Unicode,并且HTML的實(shí)例都被轉(zhuǎn)換成Unicode編碼
BeautifulSoup("Sacré bleu!")
<html><head></head><body>Sacré bleu!</body></html>
然后,Beautiful Soup選擇最合適的解析器來解析這段文檔,如果手動(dòng)指定解析器那么Beautiful Soup會(huì)選擇指定的解析器來解析文檔。
默認(rèn)情況下Beautiful Soup會(huì)將當(dāng)前文檔作為HTML格式解析骗卜,推薦使用lxml作為解析器,因?yàn)樾矢? 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必須安裝lxml或html5lib, 因?yàn)槟切㏄ython版本的標(biāo)準(zhǔn)庫中內(nèi)置的HTML解析方法不夠穩(wěn)定宠页。
4.2 BeautifulSoup對(duì)象
Beautiful Soup將復(fù)雜HTML文檔轉(zhuǎn)換成一個(gè)復(fù)雜的樹形結(jié)構(gòu),每個(gè)節(jié)點(diǎn)都是Python對(duì)象,所有對(duì)象可以歸納為4種: Tag
,NavigableString
, BeautifulSoup
, Comment
.
4.2.1 Tag 標(biāo)簽
Tag
對(duì)象與XML或HTML原生文檔中的tag相同:Tag有很多方法和屬性,后續(xù)有詳細(xì)解釋.現(xiàn)在介紹一下tag中最重要的屬性: name
和attributes
name
- 獲取name左胞。每個(gè)tag都有自己的名字,通過 .name 來獲取:
tag.name
# u'b'
- 改變name。如果改變了tag的name,那將影響所有通過當(dāng)前Beautiful Soup對(duì)象生成的HTML文檔:
tag.name = "blockquote"
tag
# <blockquote class="boldest">Extremely bold</blockquote>
attributes
tag屬性與屬性的值举户,例:
tag <b class="boldest"> 有一個(gè) “class” 的屬性,值為 “boldest” 烤宙。
一個(gè)tag可能有很多個(gè)屬性,一個(gè)屬性可能有很多個(gè)值俭嘁。
- tag屬性的操作
tag屬性的操作方法與字典相同
1.1獲取tag的指定屬性的屬性值
tag['class']
# u'boldest'
1.2獲取tag的全部屬性及屬性值: .attrs :
tag.attrs
# {'class': 'boldest'}
1.3 tag屬性及屬性值添加,刪除或修改:
#修改屬性值
tag['class'] = 'verybold'
#增加屬性及屬性值
tag['id'] = 1
tag
# <blockquote class="verybold" id="1">Extremely bold</blockquote>
#刪除屬性
del tag['class']
del tag['id']
tag
# <blockquote>Extremely bold</blockquote>
#與字典操作一致
tag['class']
# KeyError: 'class'
print(tag.get('class'))
# None
1.4 多值屬性tag的操作
在Beautiful Soup中多值屬性的返回類型一般是list躺枕,但當(dāng)某個(gè)屬性在任何版本的HTML定義中都沒有被定義為多值屬性時(shí),Beautiful Soup會(huì)將這個(gè)屬性作為字符串返回:
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
css_soup.p['class']
# ["body", "strikeout"]
css_soup = BeautifulSoup('<p class="body"></p>')
css_soup.p['class']
# ["body"]
id_soup = BeautifulSoup('<p id="my id"></p>')
id_soup.p['id']
# 'my id'
將tag轉(zhuǎn)換成字符串時(shí),多值屬性會(huì)合并為一個(gè)值
rel_soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>')
rel_soup.a['rel']
# ['index']
rel_soup.a['rel'] = ['index', 'contents']
print(rel_soup.p)
# <p>Back to the <a rel="index contents">homepage</a></p>
如果轉(zhuǎn)換的文檔是XML格式,那么tag中不包含多值屬性
xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml')
xml_soup.p['class']
# u'body strikeout'
4.2.2 NavigableString 可以遍歷的字符串
字符串常被包含在tag內(nèi)供填,Beautiful Soup用 NavigableString 類來包裝tag中的字符串拐云。獲取tag內(nèi)的文字使用.string即可:
tag.string
# u'Extremely bold'
type(tag.string)
# <class 'bs4.element.NavigableString'>
-
NavigableString
字符串與Python中的Unicode字符串相同,并且還支持包含在 遍歷文檔樹和 搜索文檔樹中的一些特性. 通過unicode()
方法可以直接將NavigableString
對(duì)象轉(zhuǎn)換成Unicode字符串:
unicode_string = unicode(tag.string)
unicode_string
# u'Extremely bold'
type(unicode_string)
# <type 'unicode'>
- tag中包含的字符串不能編輯,但是可以被替換成其它的字符串,用
replace-with()
方法:
tag.string.replace_with("No longer bold")
tag
# <blockquote>No longer bold</blockquote>
如果想在Beautiful Soup之外使用
NavigableString
對(duì)象,需要調(diào)用unicode()
方法近她,將該對(duì)象轉(zhuǎn)換成普通的Unicode字符串叉瘩,否則就算Beautiful Soup已方法已經(jīng)執(zhí)行結(jié)束,該對(duì)象的輸出也會(huì)帶有對(duì)象的引用地址粘捎。這樣會(huì)浪費(fèi)內(nèi)存薇缅。
4.2.3 BeautifulSoup
BeautifulSoup
對(duì)象表示的是一個(gè)文檔的全部?jī)?nèi)容。大部分時(shí)候,可以把它當(dāng)作 Tag 對(duì)象,支持包含在 遍歷文檔樹和 搜索文檔樹中的大部分的方法.
BeautifulSoup
對(duì)象并不是真正的HTML或XML的tag,所以它沒有name和attribute屬性.但有時(shí)查看它的 .name 屬性是很方便的,所以 BeautifulSoup 對(duì)象包含了一個(gè)值為 “[document]” 的特殊屬性 .name
soup.name
# u'[document]'
4.2.4 Comment注釋及特殊字符串
Tag
, NavigableString
, BeautifulSoup
幾乎覆蓋了html和xml中的所有內(nèi)容,但是還有一些特殊對(duì)象--文檔的注釋部分:
- Comment 對(duì)象是一個(gè)特殊類型的 NavigableString 對(duì)象
- 當(dāng)它出現(xiàn)在HTML文檔中時(shí), Comment 對(duì)象會(huì)使用特殊的格式輸出
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup)
comment = soup.b.string
type(comment)
# <class 'bs4.element.Comment'>
comment
# u'Hey, buddy. Want to buy a used parser'
print(soup.b.prettify())
# <b>
# <!--Hey, buddy. Want to buy a used parser?-->
# </b>
4.3遍歷文檔樹
遍歷文檔樹:從文檔的一段內(nèi)容找到另一段內(nèi)容攒磨。
為后面的例子做準(zhǔn)備:
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<p class="title"><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" id="link1">Elsie</a>,
<a class="sister" id="link2">Lacie</a> and
<a 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_doc)
4.3.1子節(jié)點(diǎn)
什么是子節(jié)點(diǎn):一個(gè)Tag可能包含多個(gè)字符串或其它的Tag,這些都是這個(gè)Tag的子節(jié)點(diǎn)泳桦。嵌套在子節(jié)點(diǎn)里面的叫子孫節(jié)點(diǎn)。
Beautiful Soup提供了許多操作和遍歷子節(jié)點(diǎn)的屬性咧纠。
- Beautiful Soup中字符串節(jié)點(diǎn)不支持這些屬性,因?yàn)樽址疀]有子節(jié)點(diǎn)蓬痒。
4.3.1.1 獲取tag的子節(jié)點(diǎn)--tag的name.
- 最簡(jiǎn)單的方法就是tag的name.(獲取子節(jié)點(diǎn)),例如:如果想獲取 <head> 標(biāo)簽,只要用 soup.head :
soup.head
# <head><title>The Dormouse's story</title></head>
soup.title
# <title>The Dormouse's story</title>
- 獲取tag下多級(jí)標(biāo)簽(獲取子孫節(jié)點(diǎn))漆羔,例如:獲取<body>標(biāo)簽中的第一個(gè)<b>標(biāo)簽:
soup.body.b
# <b>The Dormouse's story</b>
通過點(diǎn)取屬性的方式只能獲得當(dāng)前名字的第一個(gè)tag:
soup.a # <a class="sister" id="link1">Elsie</a>
如果想要得到所有的<a>標(biāo)簽,或是通過名字得到比一個(gè)tag更多的內(nèi)容的時(shí)候,就需要用到 Searching the tree 中描述的方法,比如:
find_all()
4.3.1.2 獲取tag全部子節(jié)點(diǎn)-- .contents
和 .children
-
.contents
:可以將tag的子節(jié)點(diǎn)以列表的方式輸出- BeautifulSoup 對(duì)象本身一定會(huì)包含子節(jié)點(diǎn),也就是說<html>標(biāo)簽也是 BeautifulSoup 對(duì)象的子節(jié)點(diǎn)梧奢。
- 字符串沒有
.contents
屬性,因?yàn)樽址疀]有子節(jié)點(diǎn)。
head_tag = soup.head
head_tag
# <head><title>The Dormouse's story</title></head>
head_tag.contents
[<title>The Dormouse's story</title>]
title_tag = head_tag.contents[0]
title_tag
# <title>The Dormouse's story</title>
title_tag.contents
# [u'The Dormouse's story']
len(soup.contents)
# 1
soup.contents[0].name
# u'html'
text = title_tag.contents[0]
text.contents
# AttributeError: 'NavigableString' object has no attribute 'contents'
-
.children
: 生成器,可以對(duì)tag的子節(jié)點(diǎn)進(jìn)行循環(huán)
for child in title_tag.children:
print(child)
# The Dormouse's story
4.3.1.3 獲取tag全部子節(jié)點(diǎn)及子孫節(jié)點(diǎn)--.descendants
<title>標(biāo)簽也包含一個(gè)子節(jié)點(diǎn):字符串 “The Dormouse’s story”,這種情況下字符串 “The Dormouse’s story”也屬于<head>標(biāo)簽的子孫節(jié)點(diǎn)` .descendants' 屬性可以對(duì)所有tag的子孫節(jié)點(diǎn)進(jìn)行遞歸循環(huán)
for child in head_tag.descendants:
print(child)
# <title>The Dormouse's story</title>
# The Dormouse's story
4.3.1.4 節(jié)點(diǎn)內(nèi)的字符串
-
.string
:如果tag只有一個(gè) NavigableString 類型子節(jié)點(diǎn)或僅有一個(gè)子節(jié)點(diǎn)演痒,這個(gè)tag可以使用 .string 得到子節(jié)點(diǎn)字符串亲轨;如果tag包含了多個(gè)子節(jié)點(diǎn),tag就無法確定 .string 方法應(yīng)該調(diào)用哪個(gè)子節(jié)點(diǎn)的內(nèi)容, .string 的輸出結(jié)果是 None:
title_tag.string
# u'The Dormouse's story'
head_tag.contents
# [<title>The Dormouse's story</title>]
head_tag.string
# u'The Dormouse's story'
print(soup.html.string)
# None
-
.strings
:如果tag中包含多個(gè)字符串.strings
來循環(huán)獲取:
for string in soup.strings:
print(repr(string))
# u"The Dormouse's story"
# u'\n\n'
# u"The Dormouse's story"
# u'\n\n'
# u'Once upon a time there were three little sisters; and their names were\n'
# u'Elsie'
# u',\n'
# u'Lacie'
# u' and\n'
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'\n\n'
# u'...'
# u'\n'
-
.stripped_strings
:.strings
輸出的字符串中可能包含了很多空格或空行鸟顺,使用.stripped_strings
可以去除多余空白內(nèi)容(全部是空格的行會(huì)被忽略掉,段首和段末的空白):
for string in soup.stripped_strings:
print(repr(string))
# u"The Dormouse's story"
# u"The Dormouse's story"
# u'Once upon a time there were three little sisters; and their names were'
# u'Elsie'
# u','
# u'Lacie'
# u'and'
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'...'
4.3.2父節(jié)點(diǎn)
父節(jié)點(diǎn):每個(gè)tag或字符串都有上級(jí)節(jié)點(diǎn)--被包含在某個(gè)tag中惦蚊。
.parent
:獲取某個(gè)元素的父節(jié)點(diǎn).
- 文檔的頂層節(jié)點(diǎn)比如<html>的父節(jié)點(diǎn)是 BeautifulSoup 對(duì)象;
- BeautifulSoup 對(duì)象的 .parent 是None;
title_tag = soup.title
title_tag
# <title>The Dormouse's story</title>
title_tag.parent
# <head><title>The Dormouse's story</title></head>
title_tag.string.parent
# <title>The Dormouse's story</title>
html_tag = soup.html
type(html_tag.parent)
# <class 'bs4.BeautifulSoup'>
print(soup.parent)
# None
.parents
:遞歸得到元素的所有父輩節(jié)點(diǎn)。
link = soup.a
link
# <a class="sister" id="link1">Elsie</a>
for parent in link.parents:
if parent is None:
print(parent)
else:
print(parent.name)
# p
# body
# html
# [document]
# None
4.3.3兄弟節(jié)點(diǎn)
什么是兄弟節(jié)點(diǎn):同一個(gè)元素的同級(jí)子節(jié)點(diǎn)讯嫂。
看一段簡(jiǎn)單的例子:
sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>")
print(sibling_soup.prettify())
# <html>
# <body>
# <a>
# <b>
# text1
# </b>
# <c>
# text2
# </c>
# </a>
# </body>
# </html>
<b>標(biāo)簽和<c>標(biāo)簽是同一個(gè)元素的同一層子節(jié)點(diǎn)蹦锋,所以<b>和<c>可以被稱為兄弟節(jié)點(diǎn)。一段文檔以標(biāo)準(zhǔn)格式輸出時(shí)欧芽,兄弟節(jié)點(diǎn)有相同的縮進(jìn)級(jí)別莉掂。在代碼中也可以使用這種關(guān)系。
-
.next_siblings
和.previous_siblings
在文檔樹中,使用.next_sibling
和.previous_sibling
屬性來查詢兄弟節(jié)點(diǎn)千扔。- 沒有兄弟節(jié)點(diǎn)時(shí)憎妙,顯示
None
库正; - 標(biāo)簽之間的頓號(hào)和換行符也會(huì)被當(dāng)作兄弟節(jié)點(diǎn)顯示;
- 沒有兄弟節(jié)點(diǎn)時(shí)憎妙,顯示
sibling_soup.b.next_sibling
# <c>text2</c>
sibling_soup.c.previous_sibling
# <b>text1</b>
print(sibling_soup.b.previous_sibling)
# None
print(sibling_soup.c.next_sibling)
# None
link = soup.a
link
# <a class="sister" id="link1">Elsie</a>
link.next_sibling
# u',\n'
link.next_sibling.next_sibling
# <a class="sister" id="link2">Lacie</a>
-
.next_siblings
和.previous_siblings
通過.next_siblings
和.previous_siblings
屬性可以對(duì)當(dāng)前節(jié)點(diǎn)的兄弟節(jié)點(diǎn)迭代輸出
for sibling in soup.a.next_siblings:
print(repr(sibling))
# u',\n'
# <a class="sister" id="link2">Lacie</a>
# u' and\n'
# <a class="sister" id="link3">Tillie</a>
# u'; and they lived at the bottom of a well.'
# None
for sibling in soup.find(id="link3").previous_siblings:
print(repr(sibling))
# ' and\n'
# <a class="sister" id="link2">Lacie</a>
# u',\n'
# <a class="sister" id="link1">Elsie</a>
# u'Once upon a time there were three little sisters; and their names were\n'
# None
4.4.4回退和前進(jìn)
-
.next_element
和.previous_element
與.next_sibling
.previous_sibling
不同厘唾,它并不是針對(duì)于兄弟節(jié)點(diǎn)褥符,而是在所有節(jié)點(diǎn),不分層次抚垃。
<head><title>The Dormouse's story</title></head>
-
.next_elements
和.previous_elements
通過.next_elements
和.previous_elements
的迭代器就可以向前或向后訪問文檔的解析內(nèi)容,就好像文檔正在被解析一樣喷楣。
for element in last_a_tag.next_elements:
print(repr(element))
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'\n\n'
# <p class="story">...</p>
# u'...'
# u'\n'
# None
4.5搜索文檔樹
Beautiful Soup定義了很多搜索方法,官網(wǎng)主要著重介紹了2個(gè): find() 和 find_all() 。其它方法的參數(shù)和用法類似讯柔。
再以“愛麗絲”文檔作為例子:
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<p class="title"><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" id="link1">Elsie</a>,
<a class="sister" id="link2">Lacie</a> and
<a 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_doc)
過濾器:貫穿整個(gè)搜索的API.過濾器可以被用在tag的name中,節(jié)點(diǎn)的屬性中,字符串中或他們的混合中.
- 字符串:最簡(jiǎn)單的過濾器是字符串.在搜索方法中傳入一個(gè)字符串參數(shù),Beautiful Soup會(huì)查找與字符串完整匹配的內(nèi)容,下面的例子用于查找文檔中所有的<b>標(biāo)簽:
soup.find_all('b') # [<b>The Dormouse's story</b>]
如果傳入字節(jié)碼參數(shù),Beautiful Soup會(huì)當(dāng)作UTF-8編碼,可以傳入一段Unicode 編碼來避免Beautiful Soup解析編碼出錯(cuò)
- 正則表達(dá)式:如果傳入正則表達(dá)式作為參數(shù),Beautiful Soup會(huì)通過正則表達(dá)式的 match() 來匹配內(nèi)容.下面例子中找出所有以b開頭的標(biāo)簽,這表示<body>和<b>標(biāo)簽都應(yīng)該被找到:
import re for tag in soup.find_all(re.compile("^b")): print(tag.name) # body # b #下面代碼找出所有名字中包含”t”的標(biāo)簽: for tag in soup.find_all(re.compile("t")): print(tag.name) # html # title
- 列表:如果傳入列表參數(shù),Beautiful Soup會(huì)將與列表中任一元素匹配的內(nèi)容返回.下面代碼找到文檔中所有<a>標(biāo)簽和<b>標(biāo)簽:
soup.find_all(["a", "b"]) # [<b>The Dormouse's story</b>, # <a class="sister" id="link1">Elsie</a>, # <a class="sister" id="link2">Lacie</a>, # <a class="sister" id="link3">Tillie</a>]
- True:可以匹配任何值,下面代碼查找到所有的tag,但是不會(huì)返回字符串節(jié)點(diǎn)
for tag in soup.find_all(True): print(tag.name) # html # head # title # body # p # b # p # a # a # a # p
- 方法:如果沒有合適過濾器,那么還可以定義一個(gè)方法,方法只接受一個(gè)元素參數(shù),如果這個(gè)方法返回
True
表示當(dāng)前元素匹配并且被找到,如果不是則反回False
def has_class_but_no_id(tag): #包含 class 屬性卻不包含 id 屬性 return tag.has_attr('class') and not tag.has_attr('id')
將這個(gè)方法作為參數(shù)傳入 find_all() 方法,將得到所有<p>標(biāo)簽
soup.find_all(has_class_but_no_id) # [<p class="title"><b>The Dormouse's story</b></p>, # <p class="story">Once upon a time there were...</p>, # <p class="story">...</p>] //返回結(jié)果中只有<p>標(biāo)簽沒有<a>標(biāo)簽,因?yàn)?lt;a>標(biāo)簽還定義了”id”,沒有返回<html>和<head>,因?yàn)?lt;html>和<head>中沒有定義”class”屬性.
下面代碼找到所有被文字包含的節(jié)點(diǎn)內(nèi)容
from bs4 import NavigableString def surrounded_by_strings(tag): return (isinstance(tag.next_element, NavigableString) and isinstance(tag.previous_element, NavigableString)) for tag in soup.find_all(surrounded_by_strings): print tag.name # p # a # a # a # p
4.5.1 find_all()
find_all( name , attrs, recursive, text, **kwargs)
:搜索當(dāng)前tag的所有tag子節(jié)點(diǎn),并判斷是否符合過濾器的條件.
name 參數(shù):可以查找所有名字為 name 的tag,字符串對(duì)象會(huì)被自動(dòng)忽略掉抡蛙。 name參數(shù)的值可以使任一類型的過濾器:字符竄,正則表達(dá)式,列表,方法或True护昧。
-
keyword 參數(shù):如果一個(gè)指定名字的參數(shù)不是搜索內(nèi)置的參數(shù)名,搜索時(shí)會(huì)把該參數(shù)當(dāng)作指定名字tag的屬性來搜索.
- 例1:包含一個(gè)名字為 id 的參數(shù),Beautiful Soup會(huì)搜索每個(gè)tag的”id”屬性.
soup.find_all(id='link2') # [<a class="sister" id="link2">Lacie</a>]
- 例2:如果傳入 href 參數(shù),Beautiful Soup會(huì)搜索每個(gè)tag的”href”屬性:
soup.find_all(href=re.compile("elsie")) # [<a class="sister" id="link1">Elsie</a>] #搜索指定名字的屬性時(shí)可以使用的參數(shù)值包括 字符串, 正則表達(dá)式 , 列表, True . soup.find_all(id=True) # 文檔樹中查找所有包含 id 屬性的tag,無論 id 的值是什么 # [<a class="sister" id="link1">Elsie</a>, # <a class="sister" id="link2">Lacie</a>, # <a class="sister" id="link3">Tillie</a>] soup.find_all(href=re.compile("elsie"), id='link1') #使用多個(gè)指定名字的參數(shù)可以同時(shí)過濾tag的多個(gè)屬性 # [<a class="sister" id="link1">three</a>] data_soup = BeautifulSoup('<div data-foo="value">foo!</div>') #有些tag屬性在搜索不能使用,比如HTML5 中的 data-* 屬性 data_soup.find_all(data-foo="value") # SyntaxError: keyword can't be an expression data_soup.find_all(attrs={"data-foo": "value"}) #可以通過 find_all() 方法的 attrs 參數(shù)定義一個(gè)字典參數(shù)來搜索包含特殊屬性的tag # [<div data-foo="value">foo!</div>]
class_ 參數(shù)(按CSS搜索):按照CSS類名搜索tag的功能非常實(shí)用,但標(biāo)識(shí)CSS類名的關(guān)鍵字 class 在Python中是保留字,使用 class 做參數(shù)會(huì)導(dǎo)致語法錯(cuò)誤.從Beautiful Soup的4.1.1版本開始,可以通過 class_ 參數(shù)搜索有指定CSS類名的tag魂迄。
soup.find_all("a", class_="sister")
# [<a class="sister" id="link1">Elsie</a>,
# <a class="sister" id="link2">Lacie</a>,
# <a class="sister" id="link3">Tillie</a>]
#class_ 參數(shù)同樣接受不同類型的 過濾器 ,字符串,正則表達(dá)式,方法或 True
soup.find_all(class_=re.compile("itl"))
# [<p class="title"><b>The Dormouse's story</b></p>]
def has_six_characters(css_class):
return css_class is not None and len(css_class) == 6
soup.find_all(class_=has_six_characters)
# [<a class="sister" id="link1">Elsie</a>,
# <a class="sister" id="link2">Lacie</a>,
# <a class="sister" id="link3">Tillie</a>]
#tag的 class 屬性是 多值屬性 .按照CSS類名搜索tag時(shí),可以分別搜索tag中的每個(gè)CSS類名:
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
css_soup.find_all("p", class_="strikeout")
# [<p class="body strikeout"></p>]
css_soup.find_all("p", class_="body")
# [<p class="body strikeout"></p>]
#搜索 class 屬性時(shí)也可以通過CSS值完全匹配
css_soup.find_all("p", class_="body strikeout")
# [<p class="body strikeout"></p>]
#完全匹配 class 的值時(shí),如果CSS類名的順序與實(shí)際不符,將搜索不到結(jié)果:
soup.find_all("a", attrs={"class": "sister"})
# [<a class="sister" id="link1">Elsie</a>,
# <a class="sister" id="link2">Lacie</a>,
# <a class="sister" id="link3">Tillie</a>]
- text 參數(shù):通過 text 參數(shù)可以搜搜文檔中的字符串內(nèi)容.與 name 參數(shù)的可選值一樣, text 參數(shù)接受 字符串 , 正則表達(dá)式 , 列表, True .
soup.find_all(text="Elsie")
# [u'Elsie']
soup.find_all(text=["Tillie", "Elsie", "Lacie"])
# [u'Elsie', u'Lacie', u'Tillie']
soup.find_all(text=re.compile("Dormouse"))
[u"The Dormouse's story", u"The Dormouse's story"]
def is_the_only_string_within_a_tag(s):
""Return True if this string is the only child of its parent tag.""
return (s == s.parent.string)
soup.find_all(text=is_the_only_string_within_a_tag)
# [u"The Dormouse's story", u"The Dormouse's story", u'Elsie', u'Lacie', u'Tillie', u'...']
#雖然 text 參數(shù)用于搜索字符串,還可以與其它參數(shù)混合使用來過濾tag.Beautiful Soup會(huì)找到 .string 方法與 text 參數(shù)值相符的tag.下面代碼用來搜索內(nèi)容里面包含“Elsie”的<a>標(biāo)簽:
soup.find_all("a", text="Elsie")
# [<a class="sister" id="link1">Elsie</a>]
- limit 參數(shù):find_all() 方法返回全部的搜索結(jié)構(gòu),如果文檔樹很大那么搜索會(huì)很慢.如果我們不需要全部結(jié)果,可以使用 limit 參數(shù)限制返回結(jié)果的數(shù)量.效果與SQL中的limit關(guān)鍵字類似,當(dāng)搜索到的結(jié)果數(shù)量達(dá)到 limit 的限制時(shí),就停止搜索返回結(jié)果.
#文檔樹中有3個(gè)tag符合搜索條件,但結(jié)果只返回了2個(gè),因?yàn)槲覀兿拗屏朔祷財(cái)?shù)量:
soup.find_all("a", limit=2)
# [<a class="sister" id="link1">Elsie</a>,
# <a class="sister" id="link2">Lacie</a>]
- recursive 參數(shù):調(diào)用tag的 find_all() 方法時(shí),Beautiful Soup會(huì)檢索當(dāng)前tag的所有子孫節(jié)點(diǎn),如果只想搜索tag的直接子節(jié)點(diǎn),可以使用參數(shù) recursive=False .
<html>
<head>
<title>
The Dormouse's story
</title>
</head>
...
#是否使用 recursive 參數(shù)的搜索結(jié)果:
soup.html.find_all("title")
# [<title>The Dormouse's story</title>]
soup.html.find_all("title", recursive=False)
# []
像調(diào)用 find_all() 一樣調(diào)用tag
find_all() 幾乎是Beautiful Soup中最常用的搜索方法,所以我們定義了它的簡(jiǎn)寫方法. BeautifulSoup 對(duì)象和 tag 對(duì)象可以被當(dāng)作一個(gè)方法來使用,這個(gè)方法的執(zhí)行結(jié)果與調(diào)用這個(gè)對(duì)象的 find_all() 方法相同,下面代碼是等價(jià)的:soup.find_all("a") soup("a") soup.title.find_all(text=True) soup.title(text=True)
4.5.2 find()
find( name , attrs , recursive , text , **kwargs )
:find_all() 方法將返回文檔中符合條件的所有tag,盡管有時(shí)候我們只想得到一個(gè)結(jié)果.比如文檔中只有一個(gè)<body>標(biāo)簽,那么使用 find_all() 方法來查找<body>標(biāo)簽就不太合適, 使用 find_all 方法并設(shè)置 limit=1 參數(shù)不如直接使用 find() 方法.
#下面兩行代碼是等價(jià)的
soup.find_all('title', limit=1)
# [<title>The Dormouse's story</title>]
soup.find('title')
# <title>The Dormouse's story</title>
唯一的區(qū)別:find_all() 方法的返回結(jié)果是值包含一個(gè)元素的列表,而 find() 方法直接返回結(jié)果.find_all() 方法沒有找到目標(biāo)是返回空列表, find() 方法找不到目標(biāo)時(shí),返回 None .
4.5.3 其他方法
Beautiful Soup中還有10個(gè)用于搜索的API.它們中的五個(gè)用的是與 find_all() 相同的搜索參數(shù),另外5個(gè)與 find() 方法的搜索參數(shù)類似.區(qū)別僅是它們搜索文檔的不同部分.
find_parents() 和 find_parent()
find_next_siblings() 合 find_next_sibling()
find_previous_siblings() 和 find_previous_sibling()
find_all_next() 和 find_next()
find_all_previous() 和 find_previous()