Python3 爬蟲學(xué)習(xí)筆記04-BeautifulSoup

正則表達(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&eacute; 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中最重要的屬性: nameattributes

  • name
  1. 獲取name左胞。每個(gè)tag都有自己的名字,通過 .name 來獲取:
tag.name
# u'b'
  1. 改變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è)值俭嘁。

  1. 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)顯示;
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()

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惋耙,隨后出現(xiàn)的幾起案子捣炬,更是在濱河造成了極大的恐慌,老刑警劉巖绽榛,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件湿酸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡灭美,警方通過查閱死者的電腦和手機(jī)推溃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來届腐,“玉大人铁坎,你說我怎么就攤上這事±缢眨” “怎么了硬萍?”我有些...
    開封第一講書人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)围详。 經(jīng)常有香客問我朴乖,道長(zhǎng),這世上最難降的妖魔是什么助赞? 我笑而不...
    開封第一講書人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任买羞,我火速辦了婚禮,結(jié)果婚禮上雹食,老公的妹妹穿的比我還像新娘畜普。我一直安慰自己,他們只是感情好婉徘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開白布漠嵌。 她就那樣靜靜地躺著咐汞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪儒鹿。 梳的紋絲不亂的頭發(fā)上化撕,一...
    開封第一講書人閱讀 51,245評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音约炎,去河邊找鬼植阴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛圾浅,可吹牛的內(nèi)容都是我干的掠手。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼狸捕,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼喷鸽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灸拍,我...
    開封第一講書人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤做祝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后鸡岗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體混槐,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年轩性,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了声登。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揣苏,死狀恐怖悯嗓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舒岸,我是刑警寧澤绅作,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站蛾派,受9級(jí)特大地震影響俄认,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洪乍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一眯杏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧壳澳,春花似錦岂贩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卸伞。三九已至,卻和暖如春锉屈,著一層夾襖步出監(jiān)牢的瞬間荤傲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工颈渊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遂黍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓俊嗽,卻偏偏與公主長(zhǎng)得像雾家,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绍豁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容