上篇文章只是簡單講述正則表達式如何讀懂以及 re 常見的函數的用法。我們可能讀懂別人的正則表達式,但是要自己寫起正則表達式的話,可能會陷入如何寫的困境。正則表達式寫起來費勁又出錯率高,那么有沒有替代方案呢?俗話說得好,條條道路通羅馬。目前還兩種代替其的辦法,一種是使用 Xpath 神器,另一種就是本文要講的 BeautifulSoup。
1 BeautifulSoup 簡介
引用 BeautifulSoup 官網的說明:
Beautiful Soup is a Python library for pulling data out of HTML and XML files. It works with your favorite parser to provide idiomatic ways of navigating, searching, and modifying the parse tree. It commonly saves programmers hours or days of work.
大致意思如下: BeautifulSoup 是一個能從 HTML 或 XML 文件中提取數據的 Python 庫。它能通過自己定義的解析器來提供導航、搜索,甚至改變解析樹。它的出現,會大大節省開發者的時間。
2 安裝 BeautifulSoup
目前 BeautifulSoup 最新版本是 4.6.0,它是支持 Python3的。所以可以大膽去升級安裝使用。
安裝方法有兩種:
- 使用
pip
比較推薦使用這種方式,既簡單又方便管理。
pip install beautifulsoup4
# 如果出現因下載失敗導致安裝不上的情況,可以先啟動 ss 再執行安裝命令
# 或者在終端中使用代理
pip --proxy http://代理ip:端口 install beautifulsoup4
- 使用
easy_install
easy_install beautifulsoup4
- 使用系統包管理
sudo apt-get install Python-bs4
# 適用于 ubuntu 系統以及 Debian 系統
3 初始 BeautifulSoup
首先導入 BeautifulSoup 庫,然后創建一個 BeautifulSoup 對象,再利用對象做文章。
具體參考示例代碼:
from bs4 import BeautifulSoup
soup = BeautifulSoup(response)
print(soup.prettify())
上面代碼中,response 可以urlllib
或者request
請求返回的內容,也可以是本地 HTML 文本。如果要打開本地,代碼需要改為
soup = BeautifulSoup(open("index.html"))
# 打開當前目錄下 index.html 文件
soup.prettify()
函數的作用是打印整個 html 文件的 dom 樹,例如上面執行結果如下:
<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</p>
<a class="sister" id="link1"><!-- Elsie --></a>
</body>
</html>
4 解析 BeautifulSoup 對象
想從 html 中獲取到自己所想要的內容,我歸納出三種辦法:
1)利用 Tag 對象
從上文得知,BeautifulSoup 將復雜 HTML 文檔轉換成一個復雜的樹形結構,每個節點都是Python對象。跟安卓中的Gson
庫有異曲同工之妙。節點對象可以分為 4 種:Tag
, NavigableString
, BeautifulSoup
, Comment
。
Tag 對象可以看成 HTML 中的標簽。這樣說,你大概明白具體是怎么回事。我們再通過例子來更加深入了解 Tag 對象。以下代碼是以 prettify() 打印的結果為前提。
- 例子1
獲取head
標簽內容
print(soup.head)
# 輸出結果如下:
<head><title>The Dormouse's story</title></head>
- 例子2
獲取title
標簽內容
print(soup.title)
# 輸出結果如下:
<title>The Dormouse's story</title>
- 例子3
獲取p
標簽內容
print(soup.p)
# 輸出結果如下:
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
如果 Tag 對象要獲取的標簽有多個的話,它只會返回所以內容中第一個符合要求的標簽。
對象一般含有屬性,Tag 對象也不例外。它具有兩個非常重要的屬性, <font color='red'>name</font> 和 <font color='red'>attrs</font>。
name
name 屬性是 Tag 對象的標簽名。不過也有特殊的,soup 對象的 name 是 [document]
print(soup.name)
print(soup.head.name)
# 輸出結果如下:
[document]
head
attrs
attrs 屬性是 Tag 對象所包含的屬性值,它是一個字典類型。
print(soup.p.attrs)
# 輸出結果如下:
{'class': ['title'], 'name': 'dromouse'}
其他三個屬性也順帶介紹下:
- NavigableString
說白了就是:Tag 對象里面的內容
print(soup.title.string)
# 輸出結果如下:
The Dormouse's story
- BeautifulSoup
BeautifulSoup 對象表示的是一個文檔的全部內容.大部分時候,可以把它當作 Tag 對象。它是一個特殊的 Tag。
print(type(soup.name))
print(soup.name)
print(soup.attrs)
# 輸出結果如下:
<type 'unicode'>
[document]
{} 空字典
- Comment
Comment 對象是一個特殊類型的 NavigableString 對象。如果 HTML 頁面中含有注釋及特殊字符串的內容。而那些內容不是我們想要的,所以我們在使用前最好做下類型判斷。例如:
if type(soup.a.string) == bs4.element.Comment:
... # 執行其他操作,例如打印內容
2)利用過濾器
過濾器其實是一個find_all()
函數, 它會將所有符合條件的內容以列表形式返回。它的構造方法如下:
find_all(name, attrs, recursive, text, **kwargs )
name 參數可以有多種寫法:
- (1)節點名
print(soup.find_all('p'))
# 輸出結果如下:
[<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</p>]
- (2)正則表達式
print(soup.find_all(re.compile('^p')))
# 輸出結果如下:
[<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</p>]
- (3)列表
如果參數為列表,過濾標準為列表中的所有元素。看下具體代碼,你就會一目了然了。
print(soup.find_all(['p', 'a']))
# 輸出結果如下:
[<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</p>, <a class="sister" id="link1"><!-- Elsie --></a>]
另外 attrs 參數可以也作為過濾條件來獲取內容,而 limit 參數是限制返回的條數。
3)利用 CSS 選擇器
以 CSS 語法為匹配標準找到 Tag。同樣也是使用到一個函數,該函數為select()
,返回類型也是 list。它的具體用法如下, 同樣以 prettify() 打印的結果為前提:
- (1)通過 tag 標簽查找
print(soup.select(head))
# 輸出結果如下:
[<head><title>The Dormouse's story</title></head>]
- (2)通過 id 查找
print(soup.select('#link1'))
# 輸出結果如下:
[<a class="sister" id="link1"><!-- Elsie --></a>]
- (3)通過 class 查找
print(soup.select('.sister'))
# 輸出結果如下:
[<a class="sister" id="link1"><!-- Elsie --></a>]
- (4)通過屬性查找
print(soup.select('p[name=dromouse]'))
# 輸出結果如下:
[<p class="title" name="dromouse"><b>The Dormouse's story</b></p>]
print(soup.select('p[class=title]'))
# 輸出結果如下:
[<p class="title" name="dromouse"><b>The Dormouse's story</b></p>]
- (5)組合查找
print(soup.select("body p"))
# 輸出結果如下:
[<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</p>]
print(soup.select("p > a"))
# 輸出結果如下:
[<a class="sister" id="link1"><!-- Elsie --></a>]
print(soup.select("p > .sister"))
# 輸出結果如下:
[<a class="sister" id="link1"><!-- Elsie --></a>]
5 處理上下關系
從上文可知,我們已經能獲取到節點對象,但有時候需要獲取其父節點或者子節點的內容,我們要怎么做了?這就需要對parse tree
進行遍歷
(1)獲取子節點
利用.children
屬性,該屬性會返回當前節點所以的子節點。但是它返回的類型不是列表,而是迭代器
(2)獲取所有子孫節點
使用.descendants
屬性,它會返回所有子孫節點的迭代器
(3)獲取父節點
通過.parent
屬性可以獲得所有子孫節點的迭代器
(4)獲取所有父節點
.parents
屬性,也是返回所有子孫節點的迭代器
(5)獲取兄弟節點
兄弟節點可以理解為和本節點處在統一級的節點,.next_sibling
屬性獲取了該節點的下一個兄弟節點,.previous_sibling
則與之相反,如果節點不存在,則返回 None
注意:實際 HTML 中的 tag 的.next_sibling
和 .previous_sibling
屬性通常是字符串或空白,因為空白或者換行也可以被視作一個節點,所以得到的結果可能是空白或者換行
(5)獲取所有兄弟節點
通過.next_siblings
和.previous_siblings
屬性可以對當前節點的兄弟節點迭代輸出
上篇文章:Python 正則表達式
推薦閱讀:詳解 python3 urllib