系列文章的第3篇介紹了網絡請求庫神器 Requests ,請求把數據返回來以後就要提取目標數據,不一樣的網站返回的內容一般有多種不一樣的格式,一種是 json 格式,這類數據對開發者來講最友好。另外一種 XML 格式的,還有一種最多見格式的是 HTML 文檔,今天就來說講如何從 HTML 中提取出感興趣的數據javascript
本身寫個 HTML 解析器來解析嗎?仍是用正則表達式?這些都不是最好的辦法,好在,Python 社區在這方便早就有了很成熟的方案,BeautifulSoup 就是這一類問題的剋星,它專一於 HTML 文檔操做,名字來源於 Lewis Carroll 的一首同名詩歌。html
BeautifulSoup 是一個用於解析 HTML 文檔的 Python 庫,經過 BeautifulSoup,你只須要用不多的代碼就能夠提取出 HTML 中任何感興趣的內容,此外,它還有必定的 HTML 容錯能力,對於一個格式不完整的HTML 文檔,它也能夠正確處理。java
pip install beautifulsoup4複製代碼
BeautifulSoup3 被官方放棄維護,你要下載最新的版本 BeautifulSoup4。python
學習 BeautifulSoup4 前有必要先對 HTML 文檔有一個基本認識,以下代碼,HTML 是一個樹形組織結構。正則表達式
<html>
<head>
<title>hello, world</title>
</head>
<body>
<h1>BeautifulSoup</h1>
<p>如何使用BeautifulSoup</p>
<body>
</html>複製代碼
構建一個 BeautifulSoup 對象須要兩個參數,第一個參數是將要解析的 HTML 文本字符串,第二個參數告訴 BeautifulSoup 使用哪一個解析器來解析 HTML。json
解析器負責把 HTML 解析成相關的對象,而 BeautifulSoup 負責操做數據(增刪改查)。」html.parser」 是Python內置的解析器,」lxml」 則是一個基於c語言開發的解析器,它的執行速度更快,不過它須要額外安裝網絡
經過 BeautifulSoup 對象就能夠定位到 HTML 中的任何一個標籤節點。學習
from bs4 import BeautifulSoup
text = """
<html>
<head>
<title >hello, world</title>
</head>
<body>
<h1>BeautifulSoup</h1>
<p class="bold">如何使用BeautifulSoup</p>
<p class="big" id="key1"> 第二個p標籤</p>
<a href="http://foofish.net">python</a>
</body>
</html>
"""
soup = BeautifulSoup(text, "html.parser")
# title 標籤
>>> soup.title
<title>hello, world</title>
# p 標籤
>>> soup.p
<p class="bold">\u5982\u4f55\u4f7f\u7528BeautifulSoup</p>
# p 標籤的內容
>>> soup.p.string
u'\u5982\u4f55\u4f7f\u7528BeautifulSoup'複製代碼
BeatifulSoup 將 HTML 抽象成爲 4 類主要的數據類型,分別是Tag , NavigableString , BeautifulSoup,Comment 。每一個標籤節點就是一個Tag對象,NavigableString 對象通常是包裹在Tag對象中的字符串,BeautifulSoup 對象表明整個 HTML 文檔。例如:網站
>>> type(soup)
<class 'bs4.BeautifulSoup'>
>>> type(soup.h1)
<class 'bs4.element.Tag'>
>>> type(soup.p.string)
<class 'bs4.element.NavigableString'>複製代碼
每一個 Tag 都有一個名字,它對應 HTML 的標籤名稱。spa
>>> soup.h1.name
u'h1'
>>> soup.p.name
u'p'複製代碼
標籤還能夠有屬性,屬性的訪問方式和字典是相似的,它返回一個列表對象
>>> soup.p['class']
[u'bold']複製代碼
獲取標籤中的內容,直接使用 .stirng 便可獲取,它是一個 NavigableString 對象,你能夠顯式地將它轉換爲 unicode 字符串。
>>> soup.p.string
u'\u5982\u4f55\u4f7f\u7528BeautifulSoup'
>>> type(soup.p.string)
<class 'bs4.element.NavigableString'>
>>> unicode_str = unicode(soup.p.string)
>>> unicode_str
u'\u5982\u4f55\u4f7f\u7528BeautifulSoup'複製代碼
基本概念介紹完,如今能夠正式進入主題了,如何從 HTML 中找到咱們關心的數據?BeautifulSoup 提供了兩種方式,一種是遍歷,另外一種是搜索,一般二者結合來完成查找任務。
遍歷文檔樹,顧名思義,就是是從根節點 html 標籤開始遍歷,直到找到目標元素爲止,遍歷的一個缺陷是,若是你要找的內容在文檔的末尾,那麼它要遍歷整個文檔才能找到它,速度上就慢了。所以還須要配合第二種方法。
經過遍歷文檔樹的方式獲取標籤節點能夠直接經過 .標籤名
的方式獲取,例如:
獲取 body 標籤:
>>> soup.body
<body>\n<h1>BeautifulSoup</h1>\n<p class="bold">\u5982\u4f55\u4f7f\u7528BeautifulSoup</p>\n</body>複製代碼
獲取 p 標籤
>>> soup.body.p
<p class="bold">\u5982\u4f55\u4f7f\u7528BeautifulSoup</p>複製代碼
獲取 p 標籤的內容
>>> soup.body.p.string
\u5982\u4f55\u4f7f\u7528BeautifulSoup複製代碼
前面說了,內容也是一個節點,這裏就能夠用 .string
的方式獲得。遍歷文檔樹的另外一個缺點是隻能獲取到與之匹配的第一個子節點,例如,若是有兩個相鄰的 p 標籤時,第二個標籤就無法經過 .p
的方式獲取,這是須要借用 next_sibling 屬性獲取相鄰且在後面的節點。此外,還有不少不怎麼經常使用的屬性,好比:.contents 獲取全部子節點,.parent 獲取父節點,更多的參考請查看官方文檔。
搜索文檔樹是經過指定標籤名來搜索元素,另外還能夠經過指定標籤的屬性值來精肯定位某個節點元素,最經常使用的兩個方法就是 find 和 find_all。這兩個方法在 BeatifulSoup 和 Tag 對象上均可以被調用。
find_all( name , attrs , recursive , text , **kwargs )複製代碼
find_all 的返回值是一個 Tag 組成的列表,方法調用很是靈活,全部的參數都是可選的。
第一個參數 name 是標籤節點的名字。
# 找到全部標籤名爲title的節點
>>> soup.find_all("title")
[<title>hello, world</title>]
>>> soup.find_all("p")
[<p class="bold">\xc8\xe7\xba\xce\xca\xb9\xd3\xc3BeautifulSoup</p>,
<p class="big"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]複製代碼
第二個參數是標籤的class屬性值
# 找到全部class屬性爲big的p標籤
>>> soup.find_all("p", "big")
[<p class="big"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]複製代碼
等效於
>>> soup.find_all("p", class_="big")
[<p class="big"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]複製代碼
由於 class 是 Python 關鍵字,因此這裏指定爲 class_。
kwargs 是標籤的屬性名值對,例如:查找有href屬性值爲 "foofish.net" 的標籤
>>> soup.find_all(href="http://foofish.net")
[<a href="http://foofish.net">python</a>]複製代碼
固然,它還支持正則表達式
>>> import re
>>> soup.find_all(href=re.compile("^http"))
[<a href="http://foofish.net">python</a>]複製代碼
屬性除了能夠是具體的值、正則表達式以外,它還能夠是一個布爾值(True/Flase),表示有屬性或者沒有該屬性。
>>> soup.find_all(id="key1")
[<p class="big" id="key1"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]
>>> soup.find_all(id=True)
[<p class="big" id="key1"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]複製代碼
遍歷和搜索相結合查找,先定位到 body 標籤,縮小搜索範圍,再從 body 中找 a 標籤。
>>> body_tag = soup.body
>>> body_tag.find_all("a")
[<a href="http://foofish.net">python</a>]複製代碼
find 方法跟 find_all 相似,惟一不一樣的地方是,它返回的單個 Tag 對象而非列表,若是沒找到匹配的節點則返回 None。若是匹配多個 Tag,只返回第0個。
>>> body_tag.find("a")
<a href="http://foofish.net">python</a>
>>> body_tag.find("p")
<p class="bold">\xc8\xe7\xba\xce\xca\xb9\xd3\xc3BeautifulSoup</p>複製代碼
獲取標籤裏面內容,除了可使用 .string 以外,還可使用 get_text 方法,不一樣的地方在於前者返回的一個 NavigableString 對象,後者返回的是 unicode 類型的字符串。
>>> p1 = body_tag.find('p').get_text()
>>> type(p1)
<type 'unicode'>
>>> p1
u'\xc8\xe7\xba\xce\xca\xb9\xd3\xc3BeautifulSoup'
>>> p2 = body_tag.find("p").string
>>> type(p2)
<class 'bs4.element.NavigableString'> >>> p2 u'\xc8\xe7\xba\xce\xca\xb9\xd3\xc3BeautifulSoup' >>>複製代碼
實際場景中咱們通常使用 get_text 方法獲取標籤中的內容。
BeatifulSoup 是一個用於操做 HTML 文檔的 Python 庫,初始化 BeatifulSoup 時,須要指定 HTML 文檔字符串和具體的解析器。BeatifulSoup 有3類經常使用的數據類型,分別是 Tag、NavigableString、和 BeautifulSoup。查找 HTML元素有兩種方式,分別是遍歷文檔樹和搜索文檔樹,一般快速獲取數據須要兩者結合。最後是一個實踐項目:用 Requests 和 BeatifulSoup 把廖雪峯的教程轉換成 PDF 電子書。