Python3中BeautifulSoup的使用方法

BeautifulSoup的使用

我們學習了正則表達式的相關用法,但是一旦正則寫的有問題,可能得到的就不是我們想要的結果了,而且對於一個網頁來說,都有一定的特殊的結構和層級關係,而且很多標籤都有id或class來對作區分,所以我們藉助於它們的結構和屬性來提取不也是可以的嗎?

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

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

BeautifulSoup簡介

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

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

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

安裝

使用之前,我們當然需要首先說明一下它的安裝方式。目前BeautifulSoup的最新版本是4.x版本,之前的版本已經停止開發了,推薦使用pip來安裝,安裝命令如下:

pip3 install beautifulsoup4

當然也可以從pypi下載whl文件安裝,鏈接如下:

pypi.python.org/pypi/be

好,安裝完成之後可以驗證一下,寫一段Python程序試驗一下。

from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'html.parser')
print(soup.p.string)

運行結果

Hello

如果沒有報錯,則證明安裝沒有問題,關於它的解析用法我們在後面會詳細介紹。

注意在這裏我們雖然安裝的是beautifulsoup4這個包,但是在引入的時候是引入的bs4,這是因爲這個包源代碼本身的庫文件夾名稱就是bs4,所以安裝完成之後,這個庫文件夾就被移入到我們本機Python3的lib庫裏,所以識別到的庫文件名稱就叫做bs4,所以我們引入的時候就引入bs4這個包。因此,包本身的名稱和我們使用時導入的包的名稱並不一定是一致的。

解析器

BeautifulSoup在解析的時候實際上是依賴於解析器的,它除了支持Python標準庫中的HTML解析器,還支持一些第三方的解析器比如lxml,下面我們對BeautifulSoup支持的解析器及它們的一些優缺點做一個簡單的對比。

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

所以通過以上對比可以看出,lxml這個解析器有解析HTML和XML的功能,而且速度快,容錯能力強,所以推薦使用這個庫來進行解析,但是這裏的劣勢是必須安裝一個C語言庫,它叫做lxml,我們在這裏依然使用pip安裝即可,命令如下:

pip3 install lxml

安裝完成之後,我們就可以使用lxml這個解析器來解析了,在初始化的時候我們可以把第二個參數改爲lxml,如下:

from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'lxml')
print(soup.p.string)

運行結果是完全一致的,後面BeautifulSoup的用法實例也統一用這個庫來演示。

基本使用

下面我們首先用一個實例來感受一下BeautifulSoup的基本使用:

html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://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)

運行結果:

<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,它是一個HTML字符串,但是注意到,它並不是一個完整的HTML字符串,<body><html>標籤都沒有閉合,但是我們將它當作第一個參數傳給BeautifulSoup對象,第二個參數傳入的是解析器的類型,在這裏我們使用lxml,這樣就完成了BeaufulSoup對象的初始化,將它賦值給soup這個變量。

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

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

然後我們調用了soup.title.string,這個實際上是輸出了HTML中<title>標籤的文本內容。所以soup.title就可以選擇出HTML中的<title>標籤,再調用string屬性就可以得到裏面的文本了,所以我們就可以通過簡單地調用幾個屬性就可以完成文本的提取了,是不是非常方便?

標籤選擇器

剛纔我們選擇元素的時候直接通過調用標籤的名稱就可以選擇節點元素了,然後再調用string屬性就可以得到標籤內的文本了,這種選擇方式速度非常快,如果單個標籤結構話層次非常清晰,可以選用這種方式來解析。

選擇元素

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

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)

運行結果:

<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標籤的選擇結果,輸出結果正是title標籤加里面的文字內容。接下來輸出了它的類型,是bs4.element.Tag類型,這是BeautifulSoup中的一個重要的數據結構,經過選擇器選擇之後,選擇結果都是這種Tag類型,它具有一些屬性比如string屬性,調用Tag的string屬性,就可以得到節點的文本內容了,所以接下來的輸出結果正是節點的文本內容。

接下來我們又嘗試選擇了head標籤,結果也是標籤加其內部的所有內容,再接下來選擇了p標籤,不過這次情況比較特殊,我們發現結果是第一個p標籤的內容,後面的幾個p標籤並沒有選擇到,也就是說,當有多個標籤時,這種選擇方式只會選擇到第一個匹配的標籤,其他的後面的標籤都會忽略。

提取信息

在上面我們演示了調用string屬性來獲取文本的值,那我們要獲取標籤屬性值怎麼辦呢?獲取標籤名怎麼辦呢?下面我們來統一梳理一下信息的提取方式

獲取名稱

可以利用name屬性來獲取標籤的名稱。還是以上面的文本爲例,我們選取title標籤,然後調用name屬性就可以得到標籤名稱。

print(soup.title.name)

運行結果:

title

獲取屬性

每個標籤可能有多個屬性,比如id,class等等,我們選擇到這個節點元素之後,可以調用attrs獲取所有屬性。

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

運行結果:

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

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

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

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

運行結果:

dromouse
['title']

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

獲取內容

可以利用string屬性獲取節點元素包含的文本內容,比如上面的文本我們獲取第一個p標籤的文本:

print(soup.p.string)

運行結果:

The Dormouse's story

再次注意一下這裏選擇到的p標籤是第一個p標籤,獲取的文本也就是第一個p標籤裏面的文本。

嵌套選擇

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

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)

運行結果:

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

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

最後輸出了一下它的string屬性,也就是標籤裏的文本內容。

關聯選擇

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

子節點和子孫節點

選取到了一個節點元素之後,如果想要獲取它的直接子節點可以調用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>
"""

運行結果:

['\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        ']

返回的結果是列表形式,p標籤裏面既包含文本,又包含標籤,返回的結果會將他們以列表形式都統一返回。

注意得到的列表的每一個元素都是p標籤的直接子節點,比如第一個a標籤裏面包含了一層span標籤,這個就相當於孫子節點了,但是返回結果中並沒有單獨把span標籤選出來作爲結果的一部分,所以說contents屬性得到的結果是直接子節點的列表。

同樣地我們可以調用children屬性,得到相應的結果。

from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.children)
for i, child in enumerate(soup.p.children):
    print(i, child)

運行結果:

<list_iterator object at 0x1064f7dd8>
0
            Once upon a time there were three little sisters; and their names were
            
1 <a class="sister" href="http://example.com/elsie" id="link1">
<span>Elsie</span>
</a>
2

3 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
4  
            and
            
5 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
6
            and they lived at the bottom of a well.

還是同樣的HTML文本,在這裏我們調用了children屬性來進行選擇,返回結果可以看到是生成器類型,所以接下來我們用for循環輸出了一下相應的內容,內容其實是一樣的,只不過children返回的是生成器類型,而contents返回的是列表類型。

如果我們要得到所有的子孫節點的話可以調用descendants屬性。

from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.descendants)
for i, child in enumerate(soup.p.descendants):
    print(i, child)

運行結果:

<generator object descendants at 0x10650e678>
0
            Once upon a time there were three little sisters; and their names were
            
1 <a class="sister" href="http://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  
            and
            
10 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
11 Tillie
12
            and they lived at the bottom of a well.

返回結果還是生成器,遍歷輸出一下可以看到這次的輸出結果就包含了span標籤,descendants會遞歸地查詢所有子節點,得到的是所有的子孫節點。

父節點和祖先節點

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

運行結果:

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

在這裏我們選擇的是第一個a標籤的父節點元素,很明顯它的父節點是p標籤,輸出結果便是p標籤及其內部的內容。

注意到這裏輸出的僅僅是a標籤的直接父節點,而沒有再向外尋找父節點的祖先節點,如果我們要想獲取所有的祖先節點,可以調用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)))

運行結果:

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

返回結果是一個生成器類型,我們在這裏用列表輸出了它的索引和內容,可以發現列表中的元素就是a標籤的祖先節點。

兄弟節點

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

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

運行結果:

Next Sibling 
            Hello
            
Prev Sibling 
            Once upon a time there were three little sisters; and their names were
            
Next Siblings [(0, '\n            Hello\n            '), (1, <a class="sister" href="http://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            ')]

可以看到在這裏我們調用了四個不同的屬性,next_sibling和previous_sibling分別可以獲取節點的下一個和上一個兄弟元素,next_siblings和previous_siblings則分別返回所有前面和後面的兄弟節點的生成器。

提取信息

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

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

運行結果:

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

如果返回結果是單個節點,那麼可以直接調用string、attrs等屬性來獲得其文本和屬性,如果返回結果是多個節點的生成器,則可以轉爲list後取出某個元素,然後再調用string、attrs等屬性來獲取其對應節點等文本和屬性。

方法選擇器

前面我們所講的選擇方法都是用.這種運算符來選擇元素的,這種選擇方法非常快,但是如果要進行比較複雜的選擇的話則會比較繁瑣,不夠靈活。所以BeautifulSoup還爲我們提供了一些查詢的方法,比如find_all()、find()等方法,我們可以調用方法然後傳入相應等參數就可以靈活地進行查詢了。

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

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


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

name

我們可以根據標籤名來查詢元素,下面我們用一個實例來感受一下:

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

運行結果:

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

在這裏我們調用了find_all()方法,傳入了一個name參數,參數值爲ul,也就是說我們想要查詢所有ul標籤,返回結果是list類型,長度爲2,每個元素依然都是bs4.element.Tag類型。

因爲都是Tag類型,所以我們依然可以進行嵌套查詢,還是同樣的文本,在這裏我們查詢出所有ul標籤後再繼續查詢其內部的li標籤。

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

運行結果:

[<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>]

返回結果是列表類型,列表中的每個元素依然還是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)

運行結果:

[<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

除了根據標籤名查詢,我們也可以傳入一些屬性來進行查詢,我們用一個實例感受一下:

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

運行結果:

[<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參數,參數的類型是字典類型,比如我們要查詢id爲list-1的節點,那就可以傳入attrs={'id': 'list-1'}的查詢條件,得到的結果是列表形式,包含的內容就是符合id爲list-1的所有節點,上面的例子中符合條件的元素個數是1,所以結果是長度爲1的列表。

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

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

運行結果:

[<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的節點元素了。而對於class來說,由於class在python裏是一個關鍵字,所以在這裏後面需要加一個下劃線,class_='element',返回的結果依然還是Tag組成的列表。

text

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

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

運行結果:

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

在這裏有兩個a節點,其內部包含有文本信息,在這裏我們調用find_all()方法傳入text參數,參數爲正則表達式對象,結果會返回所有匹配正則表達式的節點文本組成的列表。

find(name , attrs , recursive , text , **kwargs)

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

html='''
<div class="panel">
<div class="panel-heading">
        <h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
            <li class="element">Foo</li>
            <li class="element">Bar</li>
            <li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
            <li class="element">Foo</li>
            <li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find(name='ul'))
print(type(soup.find(name='ul')))
print(soup.find(class_='list'))

運行結果:

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

返回結果不再是列表形式,而是第一個匹配的節點元素,類型依然是Tag類型。

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

find_parents() find_parent()

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

find_next_siblings() find_next_sibling()

find_next_siblings()返回後面所有兄弟節點,find_next_sibling()返回後面第一個兄弟節點。

find_previous_siblings() find_previous_sibling()

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

find_all_next() find_next()

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

find_all_previous() 和 find_previous()

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

CSS選擇器

BeautifulSoup還提供了另外一種選擇器,那就是CSS選擇器,如果對web開發熟悉對話,CSS選擇器肯定也不陌生,如果不熟悉的話,可以看一下CSS選擇器參考手冊。

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

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

運行結果:

[<div class="panel-heading">
<h4>Hello</h4>
</div>]
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>, <li class="element">Foo</li>, <li class="element">Bar</li>]
[<li class="element">Foo</li>, <li class="element">Bar</li>]
<class 'bs4.element.Tag'>

在這裏我們用了三次CSS選擇器,返回的結果均是符合CSS選擇器的節點組成的列表。例如select('ul li')則是選擇所有ul節點下面的所有li節點,結果便是所有的li節點組成的列表。

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

嵌套選擇

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

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

運行結果:

[<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節點之後,其下的所有li節點組成的列表。

獲取屬性

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

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

運行結果:

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)

運行結果:

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

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

綜述

到此BeautifulSoup的使用介紹基本就結束了,最後做一下簡單的總結:

  • 推薦使用lxml解析庫,必要時使用html.parser

  • 標籤選擇篩選功能弱但是速度快

  • 建議使用find()、find_all() 查詢匹配單個結果或者多個結果

  • 如果對CSS選擇器熟悉的話可以使用select()選擇法


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章