1. 程式人生 > >Python BeautifulSoup詳解

Python BeautifulSoup詳解

1. Beautiful Soup 簡介

簡單來說,Beautiful Soup是python的一個庫,最主要的功能是從網頁抓取資料。官方解釋如下:

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

2. Beautiful Soup 安裝

Beautiful Soup 3 目前已經停止開發,推薦在現在的專案中使用Beautiful Soup 4,不過它已經被移植到BS4了,也就是說匯入時我們需要 import bs4 。所以這裡我們用的版本是 Beautiful Soup 4.3.2 (簡稱BS4),另外據說 BS4 對 Python3 的支援不夠好,不過我用的是 Python2.7.7,如果有小夥伴用的是 Python3 版本,可以考慮下載 BS3 版本。

可以利用 pip 或者 easy_install 來安裝,以下兩種方法均可

  1. easy_install beautifulsoup4
  2. pip install beautifulsoup4
如果想安裝最新的版本,請直接下載安裝包來手動安裝,也是十分方便的方法。下載完成之後解壓,執行下面的命令即可完成安裝
sudo python setup.py install
然後需要安裝 lxml
  1. easy_install lxml
  2. pip install lxml
另一個可供選擇的解析器是純Python實現的 html5lib , html5lib的解析方式與瀏覽器相同,可以選擇下列方法來安裝html5lib:
  1. easy_install html5lib
  2. pip install html5lib
Beautiful Soup支援Python標準庫中的HTML解析器,還支援一些第三方的解析器,如果我們不安裝它,則 Python 會使用 Python預設的解析器,lxml 解析器更加強大,速度更快,推薦安裝。

解析器

使用方法

優勢

劣勢

Python標準庫

BeautifulSoup(markup, “html.parser”)

Python的內建標準庫

執行速度適中

文件容錯能力強

Python 2.7.3 or 3.2.2)前 的版本中文件容錯能力差

lxml HTML 解析器

BeautifulSoup(markup, “lxml”)

速度快

文件容錯能力強

需要安裝C語言庫

lxml XML 解析器

BeautifulSoup(markup, [“lxml”, “xml”])

BeautifulSoup(markup, “xml”)

速度快

唯一支援XML的解析器

需要安裝C語言庫

html5lib

BeautifulSoup(markup, “html5lib”)

最好的容錯性

以瀏覽器的方式解析文件

生成HTML5格式的文件

速度慢

不依

3. 建立 Beautiful Soup 物件

首先必須要匯入 bs4 庫

from bs4 import BeautifulSoup

我們建立一個字串,後面的例子我們便會用它來演示

  1. html = """
  2. <html><head><title>The Dormouse's story</title></head>
  3. <body>
  4. <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
  5. <p class="story">Once upon a time there were three little sisters; and their names were
  6. <a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
  7. <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
  8. <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
  9. and they lived at the bottom of a well.</p>
  10. <p class="story">...</p>
  11. """
建立 beautifulsoup 物件
soup = BeautifulSoup(html)
另外,我們還可以用本地 HTML 檔案來建立物件,例如
soup = BeautifulSoup(open('index.html'))
上面這句程式碼便是將本地 index.html 檔案開啟,用它來建立 soup 物件。下面我們來列印一下 soup 物件的內容,格式化輸出
print soup.prettify()

指定編碼:當html為其他型別編碼(非utf-8和asc ii),比如GB2312的話,則需要指定相應的字元編碼,BeautifulSoup才能正確解析。

  1. htmlCharset = "GB2312"
  2. soup = BeautifulSoup(respHtml, fromEncoding=htmlCharset)
  1. #!/usr/bin/python
  2. # -*- coding: UTF-8 -*-
  3. from bs4 import BeautifulSoup
  4. import re
  5. #待分析字串
  6. html_doc = """
  7. <html>
  8. <head>
  9. <title>The Dormouse's story</title>
  10. </head>
  11. <body>
  12. <p class="title aq">
  13. <b>
  14. The Dormouse's story
  15. </b>
  16. </p>
  17. <p class="story">Once upon a time there were three little sisters; and their names were
  18. <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
  19. <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
  20. and
  21. <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
  22. and they lived at the bottom of a well.
  23. </p>
  24. <p class="story">...</p>
  25. """
  26. # html字串建立BeautifulSoup物件
  27. soup = BeautifulSoup(html_doc, 'html.parser', from_encoding='utf-8')
  28. #輸出第一個 title 標籤
  29. print soup.title
  30. #輸出第一個 title 標籤的標籤名稱
  31. print soup.title.name
  32. #輸出第一個 title 標籤的包含內容
  33. print soup.title.string
  34. #輸出第一個 title 標籤的父標籤的標籤名稱
  35. print soup.title.parent.name
  36. #輸出第一個 p 標籤
  37. print soup.p
  38. #輸出第一個 p 標籤的 class 屬性內容
  39. print soup.p['class']
  40. #輸出第一個 a 標籤的 href 屬性內容
  41. print soup.a['href']
  42. '''
  43. soup的屬性可以被新增,刪除或修改. 再說一次, soup的屬性操作方法與字典一樣
  44. '''
  45. #修改第一個 a 標籤的href屬性為 http://www.baidu.com/
  46. soup.a['href'] = 'http://www.baidu.com/'
  47. #給第一個 a 標籤新增 name 屬性
  48. soup.a['name'] = u'百度'
  49. #刪除第一個 a 標籤的 class 屬性為
  50. del soup.a['class']
  51. ##輸出第一個 p 標籤的所有子節點
  52. print soup.p.contents
  53. #輸出第一個 a 標籤
  54. print soup.a
  55. #輸出所有的 a 標籤,以列表形式顯示
  56. print soup.find_all('a')
  57. #輸出第一個 id 屬性等於 link3 的 a 標籤
  58. print soup.find(id="link3")
  59. #獲取所有文字內容
  60. print(soup.get_text())
  61. #輸出第一個 a 標籤的所有屬性資訊
  62. print soup.a.attrs
  63. for link in soup.find_all('a'):
  64. #獲取 link 的 href 屬性內容
  65. print(link.get('href'))
  66. #對soup.p的子節點進行迴圈輸出
  67. for child in soup.p.children:
  68. print(child)
  69. #正則匹配,名字中帶有b的標籤
  70. for tag in soup.find_all(re.compile("b")):
  71. print(tag.name)
import bs4#匯入BeautifulSoup庫
Soup = BeautifulSoup(html)#其中html 可以是字串,也可以是控制代碼
需要注意的是,BeautifulSoup會自動檢測傳入檔案的編碼格式,然後轉化為Unicode格式
通過如上兩句話,BS自動把文件生成為如上圖中的解析樹。


4. 四大物件種類

Beautiful Soup將複雜HTML文件轉換成一個複雜的樹形結構,每個節點都是Python物件,所有物件可以歸納為4種:

  1. 1. Tag
  2. 2. NavigableString
  3. 3. BeautifulSoup
  4. 4. Comment


(1)Tag

Tag 是什麼?通俗點講就是 HTML 中的一個個標籤,例如

  1. <title>The Dormouse's story</title>
  2. <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
上面的 title a 等等 HTML 標籤加上裡面包括的內容就是 Tag,下面我們來感受一下怎樣用 Beautiful Soup 來方便地獲取 Tags

下面每一段程式碼中註釋部分即為執行結果

  1. print soup.title
  2. #<title>The Dormouse's story</title>
  3. print soup.head
  4. #<head><title>The Dormouse's story</title></head>
  5. print soup.a
  6. #<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
  7. print soup.p
  8. #<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
利用 soup加標籤名輕鬆地獲取這些標籤的內容,是不是感覺比正則表示式方便多了?不過有一點是,它查詢的是在所有內容中的第一個符合要求的標籤,如果要查詢所有的標籤,我們在後面進行介紹。soup.title 得到的是title標籤,soup.p 得到的是文件中的第一個p標籤,要想得到所有標籤,得用find_all函式。find_all 函式返回的是一個序列,可以對它進行迴圈,依次得到想到的東西.。

我們可以驗證一下這些物件的型別

  1. print type(soup.a)
  2. #<class 'bs4.element.Tag'>
對於 Tag,它有兩個重要的屬性,是 name 和 attrs

name

  1. print soup.name
  2. print soup.head.name
  3. #[document]
  4. #head

soup 物件本身比較特殊,它的 name 即為 [document],對於其他內部標籤,輸出的值便為標籤本身的名稱。

attrs

  1. print soup.p.attrs
  2. #{'class': ['title'], 'name': 'dromouse'}
在這裡,我們把 p 標籤的所有屬性列印輸出了出來,得到的型別是一個字典。如果我們想要單獨獲取某個屬性,可以這樣,例如我們獲取它的 class 叫什麼
  1. print soup.p['class']
  2. #['title']
還可以這樣,利用get方法,傳入屬性的名稱,二者是等價的
  1. print soup.p.get('class')
  2. #['title']
我們可以對這些屬性和內容等等進行修改,例如
  1. soup.p['class']="newClass"
  2. print soup.p
  3. #<p class="newClass" name="dromouse"><b>The Dormouse's story</b></p>
還可以對這個屬性進行刪除,例如
  1. del soup.p['class']
  2. print soup.p
  3. #<p name="dromouse"><b>The Dormouse's story</b></p>

不過,對於修改刪除的操作,不是我們的主要用途,在此不做詳細介紹了,如果有需要,請檢視前面提供的官方文件

  1. head = soup.find('head')
  2. #head = soup.head
  3. #head = soup.contents[0].contents[0]
  4. print head
  5. html = soup.contents[0] # <html> ... </html>
  6. head = html.contents[0] # <head> ... </head>
  7. body = html.contents[1] # <body> ... </body>
可以通過Tag.attrs訪問,返回字典結構的屬性。

或者Tag.name這樣訪問特定屬性值,如果是多值屬性則以列表形式返回。

(2)NavigableString

既然我們已經得到了標籤的內容,那麼問題來了,我們要想獲取標籤內部的文字怎麼辦呢?很簡單,用 .string 即可,例如

  1. print soup.p.string
  2. #The Dormouse's story
這樣我們就輕鬆獲取到了標籤裡面的內容,想想如果用正則表示式要多麻煩。它的型別是一個 NavigableString,翻譯過來叫 可以遍歷的字串,不過我們最好還是稱它英文名字吧。來檢查一下它的型別
  1. print type(soup.p.string)
  2. #<class 'bs4.element.NavigableString'>

(3)BeautifulSoup

BeautifulSoup 物件表示的是一個文件的全部內容.大部分時候,可以把它當作 Tag 物件,是一個特殊的 Tag,我們可以分別獲取它的型別,名稱,以及屬性來感受一下

  1. print type(soup.name)
  2. #<type 'unicode'>
  3. print soup.name
  4. # [document]
  5. print soup.attrs
  6. #{} 空字典

(4)Comment

Comment 物件是一個特殊型別的 NavigableString 物件,其實輸出的內容仍然不包括註釋符號,但是如果不好好處理它,可能會對我們的文字處理造成意想不到的麻煩。

我們找一個帶註釋的標籤

  1. print soup.a
  2. print soup.a.string
  3. print type(soup.a.string)
執行結果如下
  1. <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
  2. Elsie
  3. <class 'bs4.element.Comment'>
a 標籤裡的內容實際上是註釋,但是如果我們利用 .string 來輸出它的內容,我們發現它已經把註釋符號去掉了,所以這可能會給我們帶來不必要的麻煩。

另外我們列印輸出下它的型別,發現它是一個 Comment 型別,所以,我們在使用前最好做一下判斷,判斷程式碼如下

  1. if type(soup.a.string)==bs4.element.Comment:
  2. print soup.a.string
上面的程式碼中,我們首先判斷了它的型別,是否為 Comment 型別,然後再進行其他操作,如列印輸出。

5. 遍歷文件樹

(1)直接子節點

Tag.Tag_child1:直接通過下標名稱訪問子節點。
Tag.contents:以列表形式返回所有子節點。
Tag.children:生成器,可用於迴圈訪問:for child in Tag.children

要點:.contents .children 屬性

.contents

tag 的 .content 屬性可以將tag的子節點以列表的方式輸出。可以使用 [num] 的形式獲得。使用contents向後遍歷樹,使用parent向前遍歷樹

  1. print soup.head.contents 
  2. #[<title>The Dormouse's story</title>]
輸出方式為列表,我們可以用列表索引來獲取它的某一個元素
  1. print soup.head.contents[0]
  2. #<title>The Dormouse's story</title>
.children

它返回的不是一個 list,不過我們可以通過遍歷獲取所有子節點。我們列印輸出 .children 看一下,可以發現它是一個 list 生成器物件。

可以使用list可以將其轉化為列表。當然可以使用for 語句遍歷裡面的孩子。

  1. print soup.head.children
  2. #<listiterator object at 0x7f71457f5710>
我們怎樣獲得裡面的內容呢?很簡單,遍歷一下就好了,程式碼及結果如下
  1. for child in soup.body.children:
  2. print child
  1. <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
  2. <p class="story">Once upon a time there were three little sisters; and their names were
  3. <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
  4. <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
  5. <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
  6. and they lived at the bottom of a well.</p>
  7. <p class="story">...</p>

(2)所有子孫節點

知識點:.descendants 屬性

.descendants

.contents 和 .children 屬性僅包含tag的直接子節點,.descendants 屬性可以對所有tag的子孫節點進行遞迴迴圈,和 children類似,我們也需要遍歷獲取其中的內容。

Tag.descendants:生成器,可用於迴圈訪問:for des inTag.descendants

  1. for child in soup.descendants:
  2. print child
執行結果如下,可以發現,所有的節點都被打印出來了,先生成最外層的 HTML標籤,其次從 head 標籤一個個剝離,以此類推。
  1. <html><head><title>The Dormouse's story</title></head>
  2. <body>
  3. <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
  4. <p class="story">Once upon a time there were three little sisters; and their names were
  5. <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
  6. <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
  7. <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
  8. and they lived at the bottom of a well.</p>
  9. <p class="story">...</p>
  10. </body></html>
  11. <head><title>The Dormouse's story</title></head>
  12. <title>The Dormouse's story</title>
  13. The Dormouse's story
  14. <body>
  15. <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
  16. <p class="story">Once upon a time there were three little sisters; and their names were
  17. <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
  18. <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
  19. <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
  20. and they lived at the bottom of a well.</p>
  21. <p class="story">...</p>
  22. </body>
  23. <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
  24. <b>The Dormouse's story</b>
  25. The Dormouse's story
  26. <p class="story">Once upon a time there were three little sisters; and their names were
  27. <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
  28. <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
  29. <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
  30. and they lived at the bottom of a well.</p>
  31. Once upon a time there were three little sisters; and their names were
  32. <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
  33. Elsie
  34. ,
  35. <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
  36. Lacie
  37. and
  38. <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
  39. Tillie
  40. ;
  41. and they lived at the bottom of a well.
  42. <p class="story">...</p>
  43. ...

(3)節點內容

知識點:.string 屬性

Tag.String:Tag只有一個String子節點是,可以這麼訪問,否則返回None
Tag.Strings:生成器,可用於迴圈訪問:for str in Tag.Strings

如果tag只有一個 NavigableString 型別子節點,那麼這個tag可以使用 .string 得到子節點。如果一個tag僅有一個子節點,那麼這個tag也可以使用 .string 方法,輸出結果與當前唯一子節點的 .string 結果相同。通俗點說就是:如果一個標籤裡面沒有標籤了,那麼 .string 就會返回標籤裡面的內容。如果標籤裡面只有唯一的一個標籤了,那麼 .string 也會返回最裡面的內容。如果超過一個標籤的話,那麼就會返回None。例如

  1. print soup.head.string
  2. #The Dormouse's story
  3. print soup.title.string
  4. #The Dormouse's story
如果tag包含了多個子節點,tag就無法確定,string 方法應該呼叫哪個子節點的內容, .string 的輸出結果是 None
  1. print soup.html.string
  2. # None

(4)多個內容

知識點: .strings .stripped_strings 屬性

.strings

獲取多個內容,不過需要遍歷獲取,比如下面的例子

  1. for string in soup.strings:
  2. print(repr(string))
  3. # u"The Dormouse's story"
  4. # u'\n\n'
  5. # u"The Dormouse's story"
  6. # u'\n\n'
  7. # u'Once upon a time there were three little sisters; and their names were\n'
  8. # u'Elsie'
  9. # u',\n'
  10. # u'Lacie'
  11. # u' and\n'
  12. # u'Tillie'
  13. # u';\nand they lived at the bottom of a well.'
  14. # u'\n\n'
  15. # u'...'
  16. # u'\n'
.stripped_strings 
輸出的字串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多餘空白內容
  1. for string in soup.stripped_strings:
  2. print(repr(string))
  3. # u"The Dormouse's story"
  4. # u"The Dormouse's story"
  5. # u'Once upon a time there were three little sisters; and their names were'
  6. # u'Elsie'
  7. # u','
  8. # u'Lacie'
  9. # u'and'
  10. # u'Tillie'
  11. # u';\nand they lived at the bottom of a well.'
  12. # u'...'

(5)父節點

知識點: .parent 屬性

使用parent獲取父節點。

Tag.parent:父節點
Tag.parents:父到根的所有節點

  1. body = soup.body
  2. html = body.parent # html是body的父親


  1. p = soup.p
  2. print p.parent.name
  3. #body
  4. content = soup.head.title.string
  5. print content.parent.name
  6. #title

(6)全部父節點

知識點:.parents 屬性

通過元素的 .parents 屬性可以遞迴得到元素的所有父輩節點,例如

  1. content = soup.head.title.string
  2. for parent in content.parents:
  3. print parent.name
  4. title
  5. head
  6. html
  7. [document]

(7)兄弟節點

知識點:.next_sibling .previous_sibling 屬性

使用nextSibling, previousSibling獲取前後兄弟

Tag.next_sibling
Tag.next_siblings

Tag.previous_sibling
Tag.previous_siblings

兄弟節點可以理解為和本節點處在統一級的節點,.next_sibling 屬性獲取了該節點的下一個兄弟節點,.previous_sibling 則與之相反,如果節點不存在,則返回 None。

注意:實際文件中的tag的 .next_sibling 和 .previous_sibling 屬性通常是字串或空白,因為空白或者換行也可以被視作一個節點,所以得到的結果可能是空白或者換行

  1. print soup.p.next_sibling
  2. # 實際該處為空白
  3. print soup.p.prev_sibling
  4. #None 沒有前一個兄弟節點,返回 None
  5. print soup.p.next_sibling.next_sibling
  6. #<p class="story">Once upon a time there were three little sisters; and their names were
  7. #<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
  8. #<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
  9. #<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
  10. #and they lived at the bottom of a well.</p>
  11. #下一個節點的下一個兄弟節點是我們可以看到的節點

.next方法:只能針對單一元素進行.next,或者說是對contents列表元素的挨個清點。

  1. 比如
  2. soup.contents[1]=u'HTML'
  3. soup.contents[2]=u'\n'
則soup.contents[1].next等價於soup.contents[2]
  1. head = body.previousSibling # head和body在同一層,是body的前一個兄弟
  2. p1 = body.contents[0] # p1, p2都是body的兒子,我們用contents[0]取得p1
  3. p2 = p1.nextSibling # p2與p1在同一層,是p1的後一個兄弟, 當然body.content[1]也可得到
contents[]的靈活運用也可以尋找關係節點,尋找祖先或者子孫可以採用findParent(s), findNextSibling(s), findPreviousSibling(s)

(8)全部兄弟節點

知識點:.next_siblings .previous_siblings 屬性

通過 .next_siblings 和 .previous_siblings 屬性可以對當前節點的兄弟節