Python3網絡爬蟲實戰---2九、解析庫的使用:BeautifulSoup

上一篇文章: Python3網絡爬蟲實戰---2八、解析庫的使用:XPath
下一篇文章: Python3網絡爬蟲實戰---30、解析庫的使用:PyQuery

前面咱們介紹了正則表達式的相關用法,可是一旦正則寫的有問題,可能獲得的就不是咱們想要的結果了,並且對於一個網頁來講,都有必定的特殊的結構和層級關係,並且不少節點都有id或class來對做區分,因此咱們藉助於它們的結構和屬性來提取不也是能夠的嗎?css

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

廢話很少說,接下來咱們就來感覺一下 BeautifulSoup 的強大之處吧。html5

1. BeautifulSoup簡介

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

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

因此說,利用它咱們能夠省去不少繁瑣的提取工做,提升解析效率。正則表達式

2. 準備工做

在開始以前請確保已經正確安裝好了 BeautifulSoup 和 LXML,如沒有安裝能夠參考第一章的安裝過程。segmentfault

3. 解析器

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語言庫
html5lib BeautifulSoup(markup, "html5lib") 最好的容錯性、以瀏覽器的方式解析文檔、生成 HTML5 格式的文檔 速度慢、不依賴外部擴展

因此經過以上對比能夠看出,LXML 這個解析器有解析 HTML 和 XML 的功能,並且速度快,容錯能力強,因此推薦使用這個解析器來進行解析。網絡

使用 LXML 這個解析器,在初始化 BeautifulSoup 的時候咱們能夠把第二個參數改成 lxml 便可,以下:數據結構

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

後面 BeautifulSoup 的用法實例也統一用這個解析器來演示。
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 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 屬性就能夠獲得裏面的文本了,因此咱們就能夠經過簡單地調用幾個屬性就能夠完成文本的提取了,是否是很是方便?

5. 節點選擇器

剛纔咱們選擇元素的時候直接經過調用節點的名稱就能夠選擇節點元素了,而後再調用 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 屬性,咱們用一個實例來感覺一下:

print(soup.p.contents)

運行結果:

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

contents 屬性獲得的結果是直接子節點的列表。

一樣地咱們能夠調用 children 屬性,獲得相應的結果:

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

運行結果:

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

仍是一樣的 HTML 文本,在這裏咱們調用了 children 屬性來進行選擇,返回結果能夠看到是生成器類型,因此接下來咱們用 for 循環輸出了一下相應的內容,內容實際上是同樣的,只不過 children 返回的是生成器類型,而 contents 返回的是列表類型。

若是咱們要獲得全部的子孫節點的話能夠調用 descendants 屬性:

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

運行結果:

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

返回結果仍是生成器,遍歷輸出一下能夠看到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 等屬性來得到其文本和屬性,若是返回結果是多個節點的生成器,則能夠轉爲列表後取出某個元素,而後再調用 string、attrs 等屬性來獲取其對應節點等文本和屬性。

6. 方法選擇器

前面咱們所講的選擇方法都是經過屬性來選擇元素的,這種選擇方法很是快,可是若是要進行比較複雜的選擇的話則會比較繁瑣,不夠靈活。因此 BeautifulSoup 還爲咱們提供了一些查詢的方法,好比 find_all()、find() 等方法,咱們能夠調用方法而後傳入相應等參數就能夠靈活地進行查詢了。

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

find_all()

find_all,顧名思義,就是查詢全部符合條件的元素,能夠給它傳入一些屬性或文原本獲得符合條件的元素,功能十分強大。

它的API以下:

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

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 節點,返回結果是列表類型,長度爲 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()

除了 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() 返回第一個符合條件的節點

7. CSS選擇器

BeautifulSoup 還提供了另一種選擇器,那就是 CSS 選擇器,若是對 Web 開發熟悉對話,CSS 選擇器確定也不陌生,若是不熟悉的話,能夠看一下:http://www.w3school.com.cn/cs...

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

兩者的效果是徹底一致的,均可以獲取到節點的文本值。

8. 結語

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

  • 推薦使用 LXML 解析庫,必要時使用 html.parser。
  • 節點選擇篩選功能弱可是速度快。
  • 建議使用 find()、find_all() 查詢匹配單個結果或者多個結果。
  • 若是對 CSS 選擇器熟悉的話可使用 select() 選擇法。
上一篇文章: Python3網絡爬蟲實戰---2八、解析庫的使用:XPath
下一篇文章: Python3網絡爬蟲實戰---30、解析庫的使用:PyQuery
相關文章
相關標籤/搜索