Python 爬蟲十六式 - 第五式:BeautifulSoup,美味的湯

Connor_Zhang發表於2019-01-13

BeautifulSoup 美味的湯

學習一時爽,一直學習一直爽!

   Hello,大家好,我是Connor,一個從無到有的技術小白。上一次我們說到了 Xpath 的使用方法。Xpath 我覺得還是比較繞該怎麼辦呢???有沒有更加簡單易懂的方法呢?答案是肯定的,當然有更加簡單易懂的方法了,那就是 BeautifulSoup 美味的湯。這個方法對於正則和 Xpath 來說更加的簡單方便,更加易懂,能夠節省我們大量的分析時間。

1.BeautifulSoup 的簡介

  BeautifulSoup是一個HTML資料提取庫。幾乎沒有什麼資料可以難住BeautifulSoup。只要是你可以獲取的到的資料,那麼你都可以通過BeautifulSoup簡單快捷的進行資料提取。是一款非常適合新手入門使用的資料提取庫。當然,作為一個HTML資料提取庫,requests 都有了官方中文文件,那麼 BeautifulSoup 當然也不能少啊,你可以訪問 BeautifulSoup 的官方中文文件:點我走起 >>>


2.BeautifulSoup 的安裝

  既然我又來說到安裝了,那就證明這個庫和我們平常想的庫不太一樣,它具體的安裝方法為:

pip install beautifulsoup4
複製程式碼

  注意,是beautifulsoup4,並不是beautifulsoup,雖然我們beautifulsoup的叫,但人家實際叫beautifulsoup4,一定要記清楚哈。

Python 爬蟲十六式 - 第五式:BeautifulSoup,美味的湯

3.BeautifulSoup 的基礎使用

  安裝完了,下面我們就正式開始使用,老規矩,我們先來一段html文件,然後逐一舉例,來看BeautifulSoup如何使用:

  首先我們來隨意編寫一段html程式碼:

html = """
<html>
    <head>
        <title>Hello,Wrold</title>
    </head>
    <body>
        <div class="book">
            <span><!--這裡是註釋的部分--></span>
            <a href="https://www.baidu.com">百度一下,你就知道</a>
            <img src="https://abc.jpg" />
            <p class="abc">這是一個示例</p>
        </div>
    </body>
</html>"""
複製程式碼

3.1 簡單的使用

  在進行內容提取之前,我們需要將獲取的html內容轉換成BeautifulSoup物件:

In [1]: from bs4 import BeautifulSoup
    
In [2]: soup = BeautifulSoup(html, "html5lib")
    
In [3]: type(soup)
Out[3]: bs4.BeautifulSoup
複製程式碼

  可以看的到,我們生成的物件是一個 bs4.BeautfulSoup 物件,我們所有的內容提取都基於這個物件。切記進行內容提取之前先生成 bs4.BeautifulSoup 物件。

3.2 解析器的使用

  在上面的語句中,大家可以看到我們使用了一個 html5lib 這是一個解析器,在構造BeautifulSoup 物件的時候,需要用到解析器。BeautifulSoup 支援python自帶的解析器和少數第三方解析器。詳細對比如下:

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

  一般來說,對於速度或效能要求不太高的話,還是建議大家使用 html5lib 來進行解析的,但是當規模達到一定程度的時候,解析速度就會影響到整體專案的快慢了,所以如果你對效能有要求的話,還是推薦使用 lxml 來進行解析的。具體的視情況而定吧。


3.3 節點物件

  BeautifulSoup將複雜的HTML文件轉換成了一個樹狀的結構,每個節點都是一個Python物件,所有的物件都可以歸納為四類:TagNavigableStringBeautifulSoup,Commnet

3.3.1 Tag 物件

  Tag 就是我們平時所說的標籤,Tag下擁有許多屬性和方法,和前端類似,例如 a 標籤一定會有它的href屬性,某些屬性是某些標籤所獨有的。下面我們來看一下如何提取一個 Tag 物件:

In [1]: soup = BeautifulSoup(html)

In [2]: tag = soup.p

In [3]: type(tag)
Out[3]: bs4.element.Tag
複製程式碼

  可以看的到,我們生成了一個 Tag 物件,我們再來看看 Tag 物件有哪些屬性:

  • name屬性:

  每一個tag標籤都有name屬性

In [4]: tag.name
Out[4]: 'p'
複製程式碼
  • Attributes

  在html中,某個 Tag 可能有多個屬性, Tag 屬性使用和字典一樣的方法取值:

In [5]: tag["class"]
Out[5]: ['abc']
複製程式碼

如果某個 Tag 屬性有多個值,那麼返回的是一個列表:

In [6]: soup = BeautifulSoup('<p class="body strikeout"></p>')

In [7]: soup.p['class']
Out[7]: ['body', 'strikeout']
複製程式碼
  • get_text()

  通過 get_text() 方法我們可以獲取某個 Tag 下所有的文字內容:

In [8]: soup.a.get_text()
Out[8]: '百度一下,你就知道'
複製程式碼

3.3.2 NavigableString 物件

  NavigableString 的意思是可以遍歷的字串,一般被標籤包裹在自種的文字就是NavigableString 格式:

In [9]: soup.p.string
Out[9]: '這是一個示例'
   
In [10]: type(soup.p.string)
Out[10]: bs4.element.NavigableString
複製程式碼

3.3.3 BeautifulSoup 物件

BeautifulSoup 物件就是通過解析網頁所得到的物件,我們的 soup 即是 BeautifulSoup 物件:

In [1]: from bs4 import BeautifulSoup
    
In [2]: soup = BeautifulSoup(html, "html5lib")
    
In [3]: type(soup)
Out[3]: bs4.BeautifulSoup
複製程式碼

3.3.4 Comment 物件

Comment 物件是網頁中的註釋及特殊字串,當你提取網頁中的註釋的時候,它會自動幫你生成Comment 物件:

In [4]: comment = soup.body.span.string

In [5]: type(comment)
Out[5]: bs4.element.Comment
複製程式碼

瞭解了 BeautifulSoup 的基礎使用之後,我們來看一下 BeautifulSoup 的進階用法:

4 BeautifulSoup 的高階用法

4.1 Tag與遍歷文件樹

  Tag 物件可以說 BeautifulSoup 中最為重要的物件,通過 BeautifulSoup 來提取資料基本都圍繞著這個物件來進行操作。

  首先,一個節點中是可以包含多個子節點和多個字串的。例如html節點中包含著headbody節點。所以BeautifulSoup就可以將一個HTML的網頁用這樣一層層巢狀的節點來進行表示。

  使用我們的例子,你可以這樣做:

4.1.1 contents 和 children

  通過 contents 可以獲取某個節點的所有子節點,包括裡面的 NavigbleString 物件,獲取的子節點是列表格式:

In [5]: soup.head.contents
Out[5]: [<title>Hello,Wrold</title>]
複製程式碼

  通過 children 也可以獲取某個節點的所有子節點,但是返回的是一個迭代器,這種方式使用起來比列表更加的省記憶體:

In [6]: tags = soup.head.children

In [7]: print(tags)
<list_iterator object at 0x000002E5B44E6860>

In [8]: for tag in tags:
    ...:     print(tag)
    ...:

<title>Hello,Wrold</title>
複製程式碼

4.1.2 descendants

  上面的contentschildren獲取的是某個節點的直接子節點,而無法獲得子孫節點。通過descendants可以獲得所有子孫節點,返回的結果跟children一樣,需要迭代或者轉型別使用。

In [15]: tags = soup.body.descendants

In [16]: for tag in tags:
    ...:     print(tag)
    ...:

<div class="book">
<span><!--這裡是註釋的部分--></span>
<a href="https://www.baidu.com">百度一下,你就知道</a>
<img src="https://abc.jpg"/>
<p class="abc">這是一個示例</p>
</div>

<span><!--這裡是註釋的部分--></span>
這裡是註釋的部分

<a href="https://www.baidu.com">百度一下,你就知道</a>
百度一下,你就知道

<img src="https://abc.jpg"/>

<p class="abc">這是一個示例</p>
這是一個示例
複製程式碼

  通過上圖我們可以看得出通過 descendants 首先找出了 body 標籤的第一個子節點,然後將子節點中的字串提取出來。提取出子節點的字串之後再提取子節點的子節點,再將其內容提取出來。直到該節點不再擁有子節點。這麼說可能有些抽象,我們來直接看圖:

Python 爬蟲十六式 - 第五式:BeautifulSoup,美味的湯
  是不是一目瞭然了???鬼知道我為了做這個圖到底經歷了什麼…
Python 爬蟲十六式 - 第五式:BeautifulSoup,美味的湯

4.1.3 string 和 strings

  我們常常會遇到需要獲取某個節點中的文字值的情況,如果這個節點中只有一個字串,那麼使用string可以正常將其取出。

In [17]: soup.body.a.string
Out[18]: '百度一下,你就知道'
複製程式碼

  但是如果一個節點下有多個節點中包含有字串的時候,這時使用 string 方法就無法準確的取出字串了,它無法確定你要取出的是哪個字串,這時你需要使用 strings

In [19]: strings = soup.body.strings

In [20]: strings
Out[20]: <generator object _all_strings at 0x000002E5B44EA1A8>

In [21]: for string in strings:
    ...:     print(string)
    ...:
百度一下,你就知道
這是一個示例

複製程式碼

  使用 strings 也會給你返回一個可迭代物件。當然,你會發現裡面有很多的'\n','\t'啊等這樣的轉義字元,上面的程式中沒有是因為為了美觀我手動去掉了。如果你想要獲取的內容中沒有轉義字元的話,你可以使用 stripped_strings 來去掉內容中的空白:

In [22]: strings = soup.body.stripped_strings

In [23]: strings
Out[23]: <generator object stripped_strings at 0x000002E5B39DF3B8>

In [24]: for string in strings:
    ...:     print(string)
    ...:
百度一下,你就知道
這是一個示例
複製程式碼

4.1.4 父節點 parent 和 parents

  有的時候我們也需要去獲取某個節點的父節點,就是當前節點的上一層節點:

In [25]: soup.a.parent
Out[25]:
<div class="book">
<span><!--這裡是註釋的部分--></span>
<a href="https://www.baidu.com">百度一下,你就知道</a>
<img src="https://abc.jpg"/>
<p class="abc">這是一個示例</p>
</div>
複製程式碼

  如果使用 parents 的話將會遞迴獲取該節點的所有父輩元素:

In [26]: soup.a.parents
Out[26]: <generator object parents at 0x000002E5B38C3150>
複製程式碼

  同樣這種方式獲取的父輩元素也是一個可迭代物件,需要處理後才能使用


4.1.5 兄弟節點

  兄弟節點就是指當前節點同級節點。

  • next_sibling 和 previous_sibling

    兄弟節點選取的方法與當前節點的位置有關,next_sibling獲取的是當前節點的下一個兄弟節點,previous_sibling獲取的是當前節點的上一個兄弟節點。

    所以,兄弟節點中排第一個的節點是沒有previous_sibling的,最後一個節點是沒有next_sibling的。

    In [27]: soup.head.next_sibling
    Out[27]: '\n'
    
    In [28]: soup.head.previos_sibling
    
    In [29]: soup.body.previous_sibling
    Out[29]: '\n'
    複製程式碼
  • next_siblings 和 previous_siblings

    相對應的,next_siblings獲取的是下方所有的兄弟節點,previous_siblings獲取的上方所有的兄弟節點。

    In [30]: [i.name for i in soup.head.next_siblings]
    Out[30]: [None, 'body', None]
    
    In [31]: [i.name for i in soup.body.next_siblings]
    Out[31]: [None]
    
    In [32]: [i.name for i in soup.body.previous_siblings]
    Out[32]: [None, 'head', None]
    複製程式碼

4.2. find_all()

  在前面我們講了通過標籤的屬性來進行標籤的訪問的方法,大多都只適用於簡單的一些場景,所以 BeautifulSoup 還提供了搜尋整個文件樹的方法,即 find_all()。該方法基本適用於任何節點:

4.2.1 通過 name 搜尋

最簡單的使用方式就是使用 name 屬性進行搜尋,你可以這樣做:

In [33]: soup.find_all('a')
Out[33]: [<a href="https://www.baidu.com">百度一下,你就知道</a>]
複製程式碼

  通過 find_all() 方法獲取的內容是一個列表物件。如果你給的條件是一個列表,則會匹配列表裡的全部標籤,例如:

In [34]: soup.find_all(['a','p'])
Out[34]: [<a href="https://www.baidu.com">百度一下,你就知道</a>, <p class="abc">這是一個示例</p>]
複製程式碼

  通過上面的例子我們可以看得出,我們可以看得到, BeautifulSoup 物件匹配出了所有的 a標籤和 p 標籤。


4.2.2 通過屬性搜尋

  除了通過 name 屬性來進行匹配之外,我們還可以通過屬性進行匹配。這個時候我們需要向 find_all() 方法傳遞一個字典引數:

In [35]: soup.find_all(attrs={'class':'book'})
Out[35]:
[<div class="book">
 <span><!--這裡是註釋的部分--></span>
 <a href="https://www.baidu.com">百度一下,你就知道</a>
 <img src="https://abc.jpg"/>
 <p class="abc">這是一個示例</p>
 </div>]
複製程式碼

  如果一個標籤有多個引數,為了查詢的準確性,你也可以向attrs傳遞多個引數。


4.2.3 通過文字搜尋

  在find_all()方法中,還可以根據文字內容來進行搜尋。

In [36]: soup.find_all("a", text="百度一下,你就知道")
Out[36]: [<a href="https://www.baidu.com">百度一下,你就知道</a>]
複製程式碼

可見找到的都是字串物件,如果想要找到包含某個文字的tag,加上tag名即可。


4.2.4 限制查詢範圍為子節點

  find_all() 方法會預設的去所有的子孫節點中搜尋,而如果將 recursive 引數設定為False,則可以將搜尋範圍限制在直接子節點中:

In [37]: soup.find_all("a",recursive=False)
Out[37]: []
    
In [38]: soup.find_all("a",recursive=True)
Out[38]: [<a href="https://www.baidu.com">百度一下,你就知道</a>]
複製程式碼

4.2.5 通過正規表示式來篩選結果

  在BeautifulSoup中,也是可以與re模組進行相互配合的,將re.compile編譯的物件傳入find_all()方法,即可通過正則來進行搜尋。

In [39]: import re

In [40]: soup.find_all(re.compile("b"))
Out[40]:
[<body>
<div class="book">
<span><!--這裡是註釋的部分--></span>
<a href="https://www.baidu.com">百度一下,你就知道</a>
<img src="https://abc.jpg"/>
<p class="abc">這是一個示例</p>
</div>
</body>]
複製程式碼

  可以看到,通過正規表示式,我們找到了所有以 b 開頭的標籤。正則怎麼用我們會在之後的文章中詳細的說的,大家不用著急。當然,正則除了能用在標籤上,也可以用在屬性上:

In [57]: soup.find_all(attrs={"class":re.compile("a")})
Out[57]: [<p class="abc">這是一個示例</p>]
複製程式碼

4.3 CSS選擇器

  在BeautifulSoup中,同樣也支援使用CSS選擇器來進行搜尋。使用select(),在其中傳入字串引數,就可以使用CSS選擇器的語法來找到tag:

In [58]: soup.select("title")
Out[58]: [<title>Hello,Wrold</title>]
    
In [60]: soup.select(".book")
Out[60]:
[<div class="book">
 <span><!--這裡是註釋的部分--></span>
 <a href="https://www.baidu.com">百度一下,你就知道</a>
 <img src="https://abc.jpg"/>
 <p class="abc">這是一個示例</p>
 </div>]
複製程式碼

下期預告:

   Xpath 和 BueatifulSoup 都很好用,但是有時候遇到複雜的選擇也很麻煩,有沒有能像 JQuery一樣快速通過 CSS 來選擇的工具啊???當然有了!!!那就是我們的 PyQuery,一個和 JQuery 像兄弟一樣的庫,敬請期待下期——PyQuery,一個類似JQuery的庫。

Python 爬蟲十六式 - 第五式:BeautifulSoup,美味的湯

  好了,這就是今天最美味的湯了,不知道你喝了以後有什麼感受,我是Connor,一個從無到有的技術小白,希望你能和我一同進步,一同成長!我們下期再見!

學習一時爽,一直學習一直爽!


系列文章連線:

Python 爬蟲十六式 - 第一式:HTTP協議 >>>
Python 爬蟲十六式 - 第二式:urllib 與 urllib3 >>>
Python 爬蟲十六式 - 第三式:Requests的用法 >>>
Python 爬蟲十六式 - 第四式: 使用Xpath提取網頁內容 >>>
Python 爬蟲十六式 - 第六式:JQuery的假兄弟-pyquery >>>
Python 爬蟲十六式 - 第七式:正則的藝術 >>>
Python 爬蟲十六式 - 第八式:例項解析-全書網 >>>

相關文章