溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python3網(wǎng)絡(luò)爬蟲實戰(zhàn)-29、解析庫的使用:BeautifulSoup

發(fā)布時間:2020-07-09 21:49:30 來源:網(wǎng)絡(luò) 閱讀:318 作者:學(xué)Python派森 欄目:編程語言

前面我們介紹了正則表達式的相關(guān)用法,但是一旦正則寫的有問題,可能得到的就不是我們想要的結(jié)果了,而且對于一個網(wǎng)頁來說,都有一定的特殊的結(jié)構(gòu)和層級關(guān)系,而且很多節(jié)點都有id或class來對作區(qū)分,所以我們借助于它們的結(jié)構(gòu)和屬性來提取不也是可以的嗎?

所以,這一節(jié)我們就介紹一個強大的解析工具,叫做 BeautiSoup,它就是借助網(wǎng)頁的結(jié)構(gòu)和屬性等特性來解析網(wǎng)頁的工具,有了它我們不用再去寫一些復(fù)雜的正則,只需要簡單的幾條語句就可以完成網(wǎng)頁中某個元素的提取。

廢話不多說,接下來我們就來感受一下 BeautifulSoup 的強大之處吧。

1. BeautifulSoup簡介

簡單來說,BeautifulSoup 就是 Python 的一個 HTML 或 XML 的解析庫,我們可以用它來方便地從網(wǎng)頁中提取數(shù)據(jù),官方的解釋如下:

BeautifulSoup提供一些簡單的、Python式的函數(shù)用來處理導(dǎo)航、搜索、修改分析樹等功能。它是一個工具箱,通過解析文檔為用戶提供需要抓取的數(shù)據(jù),因為簡單,所以不需要多少代碼就可以寫出一個完整的應(yīng)用程序。 BeautifulSoup 自動將輸入文檔轉(zhuǎn)換為 Unicode 編碼,輸出文檔轉(zhuǎn)換為 utf-8 編碼。你不需要考慮編碼方式,除非文檔沒有指定一個編碼方式,這時你僅僅需要說明一下原始編碼方式就可以了。 BeautifulSoup 已成為和 lxml、html6lib 一樣出色的 Python 解釋器,為用戶靈活地提供不同的解析策略或強勁的速度。

所以說,利用它我們可以省去很多繁瑣的提取工作,提高解析效率。

2. 準備工作

在開始之前請確保已經(jīng)正確安裝好了 BeautifulSoup 和 LXML,如沒有安裝可以參考第一章的安裝過程。

3. 解析器

BeautifulSoup 在解析的時候?qū)嶋H上是依賴于解析器的,它除了支持 Python 標準庫中的 HTML 解析器,還支持一些第三方的解析器比如 LXML,下面我們對 BeautifulSoup 支持的解析器及它們的一些優(yōu)缺點做一個簡單的對比。

解析器 使用方法 優(yōu)勢 劣勢
Python標準庫 BeautifulSoup(markup, "html.parser") Python的內(nèi)置標準庫、執(zhí)行速度適中 、文檔容錯能力強 Python 2.7.3 or 3.2.2)前的版本中 中文容錯能力差
LXML HTML 解析器 BeautifulSoup(markup, "lxml") 速度快、文檔容錯能力強 需要安裝C語言庫
LXML XML 解析器 BeautifulSoup(markup, "xml") 速度快、唯一支持XML的解析器 需要安裝C語言庫
html5lib BeautifulSoup(markup, "html5lib") 最好的容錯性、以瀏覽器的方式解析文檔、生成 HTML5 格式的文檔 速度慢、不依賴外部擴展

所以通過以上對比可以看出,LXML 這個解析器有解析 HTML 和 XML 的功能,而且速度快,容錯能力強,所以推薦使用這個解析器來進行解析。

使用 LXML 這個解析器,在初始化 BeautifulSoup 的時候我們可以把第二個參數(shù)改為 lxml 即可,如下:

from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'lxml')
print(soup.p.string)
Python資源分享qun 784758214 ,內(nèi)有安裝包,PDF,學(xué)習(xí)視頻,這里是Python學(xué)習(xí)者的聚集地,零基礎(chǔ),進階,都歡迎

后面 BeautifulSoup 的用法實例也統(tǒng)一用這個解析器來演示。
4# . 基本使用

下面我們首先用一個實例來感受一下 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  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, 'lxml')
print(soup.prettify())
print(soup.title.string)

運行結(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"  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>
 </body>
</html>
The Dormouse's story

首先我們聲明了一個變量 html,它是一個 HTML 字符串,但是注意到,它并不是一個完整的 HTML 字符串,body 和 html 節(jié)點都沒有閉合,但是我們將它當作第一個參數(shù)傳給 BeautifulSoup 對象,第二個參數(shù)傳入的是解析器的類型,在這里我們使用 lxml,這樣就完成了 BeaufulSoup 對象的初始化,將它賦值給 soup 這個變量。

那么接下來我們就可以通過調(diào)用 soup 的各個方法和屬性對這串 HTML代碼解析了。

我們首先調(diào)用了 prettify() 方法,這個方法可以把要解析的字符串以標準的縮進格式輸出,在這里注意到輸出結(jié)果里面包含了 body 和 html 節(jié)點,也就是說對于不標準的 HTML 字符串 BeautifulSoup 可以自動更正格式,這一步實際上不是由 prettify() 方法做的,這個更正實際上在初始化 BeautifulSoup 時就完成了。

然后我們調(diào)用了 soup.title.string ,這個實際上是輸出了 HTML 中 title 節(jié)點的文本內(nèi)容。所以 soup.title 就可以選擇出 HTML 中的 title 節(jié)點,再調(diào)用 string 屬性就可以得到里面的文本了,所以我們就可以通過簡單地調(diào)用幾個屬性就可以完成文本的提取了,是不是非常方便?

5. 節(jié)點選擇器

剛才我們選擇元素的時候直接通過調(diào)用節(jié)點的名稱就可以選擇節(jié)點元素了,然后再調(diào)用 string 屬性就可以得到節(jié)點內(nèi)的文本了,這種選擇方式速度非??欤绻麊蝹€節(jié)點結(jié)構(gòu)話層次非常清晰,可以選用這種方式來解析。

選擇元素

下面我們再用一個例子詳細說明一下它的選擇方法:

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  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, 'lxml')
print(soup.title)
print(type(soup.title))
print(soup.title.string)
print(soup.head)
print(soup.p)

運行結(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é)點的選擇結(jié)果,輸出結(jié)果正是 title 節(jié)點加里面的文字內(nèi)容。接下來輸出了它的類型,是 bs4.element.Tag 類型,這是 BeautifulSoup 中的一個重要的數(shù)據(jù)結(jié)構(gòu),經(jīng)過選擇器選擇之后,選擇結(jié)果都是這種 Tag 類型,它具有一些屬性比如 string 屬性,調(diào)用 Tag 的 string 屬性,就可以得到節(jié)點的文本內(nèi)容了,所以接下來的輸出結(jié)果正是節(jié)點的文本內(nèi)容。

接下來我們又嘗試選擇了 head 節(jié)點,結(jié)果也是節(jié)點加其內(nèi)部的所有內(nèi)容,再接下來選擇了 p 節(jié)點,不過這次情況比較特殊,我們發(fā)現(xiàn)結(jié)果是第一個 p 節(jié)點的內(nèi)容,后面的幾個 p 節(jié)點并沒有選擇到,也就是說,當有多個節(jié)點時,這種選擇方式只會選擇到第一個匹配的節(jié)點,其他的后面的節(jié)點都會忽略。

提取信息

在上面我們演示了調(diào)用 string 屬性來獲取文本的值,那我們要獲取節(jié)點屬性值怎么辦呢?獲取節(jié)點名怎么辦呢?下面我們來統(tǒng)一梳理一下信息的提取方式

獲取名稱

可以利用 name 屬性來獲取節(jié)點的名稱。還是以上面的文本為例,我們選取 title 節(jié)點,然后調(diào)用 name 屬性就可以得到節(jié)點名稱。

print(soup.title.name)

運行結(jié)果:

title

獲取屬性

每個節(jié)點可能有多個屬性,比如 id,class 等等,我們選擇到這個節(jié)點元素之后,可以調(diào)用 attrs 獲取所有屬性。

print(soup.p.attrs)
print(soup.p.attrs['name'])

運行結(jié)果:

{'class': ['title'], 'name': 'dromouse'}
dromouse

可以看到 attrs 的返回結(jié)果是字典形式,把選擇的節(jié)點的所有屬性和屬性值組合成一個字典,接下來如果要獲取 name 屬性,就相當于從字典中獲取某個鍵值,只需要用中括號加屬性名稱就可以得到結(jié)果了,比如獲取 name 屬性就可以通過 attrs['name'] 得到相應(yīng)的屬性值。

其實這樣的寫法還有點繁瑣,還有一種更簡單的獲取方式,我們可以不用寫 attrs,直接節(jié)點元素后面加中括號,傳入屬性名就可以達到屬性值了,樣例如下:

print(soup.p['name'])
print(soup.p['class'])

運行結(jié)果:

dromouse
['title']

在這里注意到有的返回結(jié)果是字符串,有的返回結(jié)果是字符串組成的列表。比如 name 屬性的值是唯一的,返回的結(jié)果就是單個字符串,而對于 class,一個節(jié)點元素可能由多個 class,所以返回的是列表,所以在實際處理過程中要注意判斷類型。

獲取內(nèi)容

可以利用 string 屬性獲取節(jié)點元素包含的文本內(nèi)容,比如上面的文本我們獲取第一個 p 節(jié)點的文本:

print(soup.p.string)

運行結(jié)果:

The Dormouse's story

再次注意一下這里選擇到的 p 節(jié)點是第一個 p 節(jié)點,獲取的文本也就是第一個 p 節(jié)點里面的文本。

嵌套選擇

在上面的例子中我們知道每一個返回結(jié)果都是 bs4.element.Tag 類型,它同樣可以繼續(xù)調(diào)用節(jié)點進行下一步的選擇,比如我們獲取了 head 節(jié)點元素,我們可以繼續(xù)調(diào)用 head 來選取其內(nèi)部的 head 節(jié)點元素。

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)

運行結(jié)果:

<title>The Dormouse's story</title>
<class 'bs4.element.Tag'>
The Dormouse's story

第一行結(jié)果是我們調(diào)用了 head 之后再次調(diào)用了 title 來選擇的 title 節(jié)點元素,然后我們緊接著打印輸出了它的類型,可以看到它仍然是 bs4.element.Tag 類型,也就是說我們在 Tag 類型的基礎(chǔ)上再次選擇得到的依然還是 Tag 類型,每次返回的結(jié)果都相同,所以這樣我們就可以這樣做嵌套的選擇了。

最后輸出了一下它的 string 屬性,也就是節(jié)點里的文本內(nèi)容。

關(guān)聯(lián)選擇

我們在做選擇的時候有時候不能做到一步就可以選擇到想要的節(jié)點元素,有時候在選擇的時候需要先選中某一個節(jié)點元素,然后以它為基準再選擇它的子節(jié)點、父節(jié)點、兄弟節(jié)點等等。所以在這里我們就介紹下如何來選擇這些節(jié)點元素。

子節(jié)點和子孫節(jié)點

選取到了一個節(jié)點元素之后,如果想要獲取它的直接子節(jié)點可以調(diào)用 contents 屬性,我們用一個實例來感受一下:

print(soup.p.contents)

運行結(jié)果:

[<b>The Dormouse's story</b>]

contents 屬性得到的結(jié)果是直接子節(jié)點的列表。

同樣地我們可以調(diào)用 children 屬性,得到相應(yīng)的結(jié)果:

print(soup.p.children)
for i,child in enumerate(soup.p.children):
    print(child)

運行結(jié)果:

<list_iterator object at 0x10529eef0>
<b>The Dormouse's story</b>

還是同樣的 HTML 文本,在這里我們調(diào)用了 children 屬性來進行選擇,返回結(jié)果可以看到是生成器類型,所以接下來我們用 for 循環(huán)輸出了一下相應(yīng)的內(nèi)容,內(nèi)容其實是一樣的,只不過 children 返回的是生成器類型,而 contents 返回的是列表類型。

如果我們要得到所有的子孫節(jié)點的話可以調(diào)用 descendants 屬性:

print(soup.p.descendants)
for i,child in enumerate(soup.p.descendants):
    print(child)

運行結(jié)果:

<generator object Tag.descendants at 0x103fa5a20>
<b>The Dormouse's story</b>
The Dormouse's story

返回結(jié)果還是生成器,遍歷輸出一下可以看到descendants 會遞歸地查詢所有子節(jié)點,得到的是所有的子孫節(jié)點。

父節(jié)點和祖先節(jié)點

如果要獲取某個節(jié)點元素的父節(jié)點,可以調(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  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)

運行結(jié)果:

<p class="story">
            Once upon a time there were three little sisters; and their names were
            <a class="sister"  id="link1">
<span>Elsie</span>
</a>
</p>

在這里我們選擇的是第一個 a 節(jié)點的父節(jié)點元素,很明顯它的父節(jié)點是 p 節(jié)點,輸出結(jié)果便是 p 節(jié)點及其內(nèi)部的內(nèi)容。

注意到這里輸出的僅僅是 a 節(jié)點的直接父節(jié)點,而沒有再向外尋找父節(jié)點的祖先節(jié)點,如果我們要想獲取所有的祖先節(jié)點,可以調(diào)用 parents 屬性:

html = """
<html>
    <body>
        <p class="story">
            <a  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)))

運行結(jié)果:

<class 'generator'>
[(0, <p class="story">
<a class="sister"  id="link1">
<span>Elsie</span>
</a>
</p>), (1, <body>
<p class="story">
<a class="sister"  id="link1">
<span>Elsie</span>
</a>
</p>
</body>), (2, <html>
<body>
<p class="story">
<a class="sister"  id="link1">
<span>Elsie</span>
</a>
</p>
</body></html>), (3, <html>
<body>
<p class="story">
<a class="sister"  id="link1">
<span>Elsie</span>
</a>
</p>
</body></html>)]

返回結(jié)果是一個生成器類型,我們在這里用列表輸出了它的索引和內(nèi)容,可以發(fā)現(xiàn)列表中的元素就是 a 節(jié)點的祖先節(jié)點。

兄弟節(jié)點

上面說明了子節(jié)點和父節(jié)點的獲取方式,如果要獲取同級的節(jié)點也就是兄弟節(jié)點應(yīng)該怎么辦?我們先用一個實例來感受一下:

html = """
<html>
    <body>
        <p class="story">
            Once upon a time there were three little sisters; and their names were
            <a  class="sister" id="link1">
                <span>Elsie</span>
            </a>
            Hello
            <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>
"""
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)))

運行結(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"  id="link2">Lacie</a>), (2, ' \n            and\n            '), (3, <a class="sister"  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é)點的下一個和上一個兄弟元素,next_siblings 和 previous_siblings 則分別返回所有前面和后面的兄弟節(jié)點的生成器。

提取信息

在上面我們講解了關(guān)聯(lián)元素節(jié)點的選擇方法,如果我們想要獲取它們的一些信息,比如文本、屬性等等也是同樣的方法。

html = """
<html>
    <body>
        <p class="story">
            Once upon a time there were three little sisters; and their names were
            <a  class="sister" id="link1">Bob</a><a  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'])

運行結(jié)果:

Next Sibling:
<class 'bs4.element.Tag'>
<a class="sister"  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"  id="link1">Bob</a><a class="sister"  id="link2">Lacie</a>
</p>
['story']

如果返回結(jié)果是單個節(jié)點,那么可以直接調(diào)用 string、attrs 等屬性來獲得其文本和屬性,如果返回結(jié)果是多個節(jié)點的生成器,則可以轉(zhuǎn)為列表后取出某個元素,然后再調(diào)用 string、attrs 等屬性來獲取其對應(yīng)節(jié)點等文本和屬性。

6. 方法選擇器

前面我們所講的選擇方法都是通過屬性來選擇元素的,這種選擇方法非??欤侨绻M行比較復(fù)雜的選擇的話則會比較繁瑣,不夠靈活。所以 BeautifulSoup 還為我們提供了一些查詢的方法,比如 find_all()、find() 等方法,我們可以調(diào)用方法然后傳入相應(yīng)等參數(shù)就可以靈活地進行查詢了。

最常用的查詢方法莫過于 find_all() 和 find() 了,下面我們對它們的用法進行詳細的介紹。

find_all()

find_all,顧名思義,就是查詢所有符合條件的元素,可以給它傳入一些屬性或文本來得到符合條件的元素,功能十分強大。

它的API如下:

find_all(name?, attrs , recursive , text , **kwargs)

name

我們可以根據(jù)節(jié)點名來查詢元素,下面我們用一個實例來感受一下:

html='''
<div class="panel">
    <div class="panel-heading">
        <h5>Hello</h5>
    </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]))

運行結(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'>
Python資源分享qun 784758214 ,內(nèi)有安裝包,PDF,學(xué)習(xí)視頻,這里是Python學(xué)習(xí)者的聚集地,零基礎(chǔ),進階,都歡迎

在這里我們調(diào)用了 find_all() 方法,傳入了一個 name 參數(shù),參數(shù)值為 ul,也就是說我們想要查詢所有 ul 節(jié)點,返回結(jié)果是列表類型,長度為 2,每個元素依然都是 bs4.element.Tag 類型。

因為都是 Tag 類型,所以我們依然可以進行嵌套查詢,還是同樣的文本,在這里我們查詢出所有 ul 節(jié)點后再繼續(xù)查詢其內(nèi)部的 li 節(jié)點。

for ul in soup.find_all(name='ul'):
    print(ul.find_all(name='li'))

運行結(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)

運行結(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é)點名查詢,我們也可以傳入一些屬性來進行查詢,我們用一個實例感受一下:

html='''
<div class="panel">
    <div class="panel-heading">
        <h5>Hello</h5>
    </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'}))

運行結(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é)點,那就可以傳入attrs={'id': 'list-1'} 的查詢條件,得到的結(jié)果是列表形式,包含的內(nèi)容就是符合 id 為 list-1 的所有節(jié)點,上面的例子中符合條件的元素個數(shù)是 1,所以結(jié)果是長度為 1 的列表。

對于一些常用的屬性比如 id、class 等,我們可以不用 attrs 來傳遞,比如我們要查詢 id 為 list-1 的節(jié)點,我們可以直接傳入 id 這個參數(shù),還是上面的文本,我們換一種方式來查詢。

from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(id='list-1'))
print(soup.find_all(class_='element'))

運行結(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é)點元素了。而對于 class 來說,由于 class 在 python 里是一個關(guān)鍵字,所以在這里后面需要加一個下劃線,class_='element',返回的結(jié)果依然還是 Tag 組成的列表。

text

text 參數(shù)可以用來匹配節(jié)點的文本,傳入的形式可以是字符串,可以是正則表達式對象,我們用一個實例來感受一下:

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')))

運行結(jié)果:

['Hello, this is a link', 'Hello, this is a link, too']

在這里有兩個 a 節(jié)點,其內(nèi)部包含有文本信息,在這里我們調(diào)用 find_all() 方法傳入 text 參數(shù),參數(shù)為正則表達式對象,結(jié)果會返回所有匹配正則表達式的節(jié)點文本組成的列表。

find()

除了 find_all() 方法,還有 find() 方法,只不過 find() 方法返回的是單個元素,也就是第一個匹配的元素,而 find_all() 返回的是所有匹配的元素組成的列表。

html='''
<div class="panel">
    <div class="panel-heading">
        <h5>Hello</h5>
    </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'))

運行結(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é)點元素,類型依然是 Tag 類型。

另外還有許多的查詢方法,用法與前面介紹的 find_all()、find() 方法完全相同,只不過查詢范圍不同,在此做一下簡單的說明。

  • find_parents() find_parent()

find_parents() 返回所有祖先節(jié)點,find_parent() 返回直接父節(jié)點。

  • find_next_siblings() find_next_sibling()
  • find_next_siblings() 返回后面所有兄弟節(jié)點,find_next_sibling() 返回后面第一個兄弟節(jié)點。
  • find_previous_siblings() find_previous_sibling()

find_previous_siblings() 返回前面所有兄弟節(jié)點,find_previous_sibling() 返回前面第一個兄弟節(jié)點。

  • find_all_next() find_next()

find_all_next() 返回節(jié)點后所有符合條件的節(jié)點, find_next() 返回第一個符合條件的節(jié)點。

  • find_all_previous() 和 find_previous()

find_all_previous() 返回節(jié)點后所有符合條件的節(jié)點, find_previous() 返回第一個符合條件的節(jié)點

7. CSS選擇器

BeautifulSoup 還提供了另外一種選擇器,那就是 CSS 選擇器,如果對 Web 開發(fā)熟悉對話,CSS 選擇器肯定也不陌生,如果不熟悉的話,可以看一下:http://www.w3school.com.cn/cs...。

使用 CSS 選擇器,只需要調(diào)用 select() 方法,傳入相應(yīng)的 CSS 選擇器即可,我們用一個實例來感受一下:

html='''
<div class="panel">
    <div class="panel-heading">
        <h5>Hello</h5>
    </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]))

運行結(jié)果:

[<div class="panel-heading">
<h5>Hello</h5>
</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é)點組成的列表。例如 select('ul li') 則是選擇所有 ul 節(jié)點下面的所有 li 節(jié)點,結(jié)果便是所有的 li 節(jié)點組成的列表。

最后一句我們打印輸出了列表中元素的類型,可以看到類型依然是 Tag 類型。

嵌套選擇

select() 方法同樣支持嵌套選擇,例如我們先選擇所有 ul 節(jié)點,再遍歷每個 ul 節(jié)點選擇其 li 節(jié)點,樣例如下:

from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for ul in soup.select('ul'):
    print(ul.select('li'))

運行結(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é)點之后,其下的所有 li 節(jié)點組成的列表。

獲取屬性

我們知道節(jié)點類型是 Tag 類型,所以獲取屬性還是可以用原來的方法獲取,仍然是上面的 HTML 文本,我們在這里嘗試獲取每個 ul 節(jié)點的 id 屬性。

from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for ul in soup.select('ul'):
    print(ul['id'])
    print(ul.attrs['id'])

運行結(jié)果:

list-1
list-1
list-2
list-2

可以看到直接傳入中括號和屬性名和通過 attrs 屬性獲取屬性值都是可以成功的。

獲取文本

那么獲取文本當然也可以用前面所講的 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)

運行結(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
Python資源分享qun 784758214 ,內(nèi)有安裝包,PDF,學(xué)習(xí)視頻,這里是Python學(xué)習(xí)者的聚集地,零基礎(chǔ),進階,都歡迎

二者的效果是完全一致的,都可以獲取到節(jié)點的文本值。

8. 結(jié)語

到此 BeautifulSoup 的使用介紹基本就結(jié)束了,最后做一下簡單的總結(jié):

  • 推薦使用 LXML 解析庫,必要時使用 html.parser。
  • 節(jié)點選擇篩選功能弱但是速度快。
  • 建議使用 find()、find_all() 查詢匹配單個結(jié)果或者多個結(jié)果。
  • 如果對 CSS 選擇器熟悉的話可以使用 select() 選擇法。
向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI