鋼鐵知識庫,一個學習python爬蟲、數據分析的知識庫。人生苦短,快用python。 上一章我們講解針對結構化的html、xml數據,使用Xpath實現網頁內容爬取。本章我們再來聊另一個高效的神器:Beautiful Soup4。相比於傳統正則表達方式去解析網頁源代碼,這個就簡單得多,實踐是檢驗真理 ...
鋼鐵知識庫,一個學習python爬蟲、數據分析的知識庫。人生苦短,快用python。
上一章我們講解針對結構化的html
、xml
數據,使用Xpath
實現網頁內容爬取。本章我們再來聊另一個高效的神器:Beautiful Soup4
。相比於傳統正則表達方式去解析網頁源代碼,這個就簡單得多,實踐是檢驗真理的唯一標準,話不多說直接上號開搞驗證。
Beautiful Soup 簡介
首先說說BeautifulSoup是什麼。簡單來說,這是Python的一個HTML或XML的解析庫,我們可以用它方便從網頁中提取數據,官方解釋如下:
BeautifulSoup 提供一些簡單的、Python 式的函數用來處理導航、搜索、修改分析樹等功能。它是一個工具箱,通過解析文檔為用戶提供需要抓取的數據,因為簡單,所以不需要多少代碼就可以寫出一個完整的應用程式。 BeautifulSoup 自動將輸入文檔轉換為 Unicode 編碼,輸出文檔轉換為 utf-8 編碼。你不需要考慮編碼方式,除非文檔沒有指定一個編碼方式,這時你僅僅需要說明一下原始編碼方式就可以了。 BeautifulSoup 已成為和 lxml、html5lib 一樣出色的 Python 解釋器,為用戶靈活地提供不同的解析策略或強勁的速度。
所以,利用它可以省去很多繁瑣的提取工作,提高解析效率。
BeautifulSoup 安裝
BeautifulSoup3 目前已經停止開發,推薦使用 BeautifulSoup4,不過它也被移植到bs4
了,也就是說導入時我們需要import bs4
在開始之前,請確保已經正確安裝beautifulsoup4
和lxml
,使用pip安裝命令如下:
pip install beautifulsoup4
pip install lxml
解析器
BeautifulSoup在解析時實際上依賴解析器。除了支持Python標準庫中的HTML解析器,還支持一些第三方的解析器,如果不安裝它,則Python會使用預設的解析器。
下麵列出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的功能,相比預設的HTML解析器更加強大,速度,容錯能力強。
推薦使用它,下麵統一使用lxml進行演示。使用時只需在初始化時第二個參數改為 lxml 即可。
from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'lxml')
print(soup.p.string)
'''
Hello
'''
基本使用
下麵舉個實例來看看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標簽都沒有閉合。
經過初始化,使用prettify()
方法把要解析的字元串以標準縮進格式輸出,發現結果中自動補全了html和body標簽。這一步不是prettify()
方法做的,而是在初始化BeautifulSoup時就完成了。然後調用soup.title.string
拿到title裡面的文本內容。
通過簡單調用幾個屬性完成文本提取,是不是非常方便呢?
節點選擇器
直接調用節點的名稱就可以選擇節點元素,再調用 string 屬性就可以得到節點內的文本了,這種選擇方式速度非常快。如果單個節點結構層次非常清晰,可以選用這種方式來解析。
選擇元素
還是以上面的HTML代碼為例,詳細說明選擇元素的方法:
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>
'''
首先輸出title節點的選擇結果,包含標簽。
接下來輸出它的類型,是一個bs4.element.Tag
類型,Tag具有一些屬性,比如string。
調用string屬性可以看到輸出節點的文本內容。
繼續嘗試head、p節點。發現p只取了第一個匹配的節點。說明當有多個節點時只取一個。
獲取屬性
每個節點可能有多個屬性比如id 、class等,選擇元素後可以調用attrs獲取所有屬性:
print(soup.p.attrs)
print(soup.p.attrs['name'])
'''
{'class': ['title'], 'name': 'dromouse'}
dromouse
'''
可以看到attrs返回結果是字典,它把選擇節點所有屬性都組合成一個字典。取值直接按字典方式即可。
當然還有一種更簡單的獲取方式:不寫attrs,直接在元素後面中括弧取值也行:
print(soup.p['name'])
print(soup.p['class'])
'''
dromouse
['title']
'''
但是註意區分:有的返回字元串、有的返回字元串組成的列表。
對於class,一個節點元素可能有多個class,所以返回的是列表。
子節點和子孫節點
選取節點元素之後,如果想要獲取它的直接子節點,可以調用 contents 屬性,示例如下:
html4 = """
<html>
<head>
<title>The Dormouse's story</title>
</head>
<body>
<p class="story">
鋼鐵知識庫
<a href="http://a.com" class="鋼鐵學數據分析" id="link1">
<span>Elsie</span>
</a>
<a href="http://b.com" class="鋼鐵學自動化" id="link2">Lacie</a>
and
<a href="http://example.com" class="cccc" id="link3">Tillie</a>
鋼鐵學爬蟲.
</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html4, 'lxml')
print(soup.p.contents)
'''
['\n 鋼鐵知識庫\n ', <a class="鋼鐵學數據分析" href="http://a.com" id="link1">
<span>Elsie</span>
</a>, '\n', <a class="鋼鐵學自動化" href="http://b.com" id="link2">Lacie</a>, ' \n and\n ', <a class="cccc" href="http://example.com" id="link3">Tillie</a>, '\n 鋼鐵學爬蟲.\n ']
'''
可以看到返回結果是列表形式。p 節點里既包含節點,又包含文本,最後統一返回列表。
需要註意,列表中的每個元素都是 p 節點的直接子節點。比如第一個 a 節點裡面的span節點,這相當於子孫節點了,但返回結果並沒有單獨把span節點列出來。所以說,contents屬性得到的結果是直接子節點的列表。
同樣,我們可以調用children屬性得到相應的結果:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.children)
for i, child in enumerate(soup.p.children):
print(i, child)
'''
<list_iterator object at 0x0000000001D9A1C0>
0
鋼鐵知識庫
1 <a class="鋼鐵學數據分析" href="http://a.com" id="link1">
<span>Elsie</span>
</a>
2
3 <a class="鋼鐵學自動化" href="http://b.com" id="link2">Lacie</a>
4
and
5 <a class="cccc" href="http://example.com" id="link3">Tillie</a>
6
鋼鐵學爬蟲.
'''
還是同樣的 HTML 文本,這裡調用了 children 屬性來選擇,返回結果是生成器類型。接下來,我們用 for 迴圈輸出相應的內容。
如果要得到所有的子孫節點的話,可以調用 descendants 屬性:
<generator object Tag.descendants at 0x000001D77A90E570>
0
鋼鐵知識庫
1 <a class="鋼鐵學數據分析" href="http://a.com" id="link1">
<span>Elsie</span>
</a>
2
3 <span>Elsie</span>
4 Elsie
5
6
7 <a class="鋼鐵學自動化" href="http://b.com" id="link2">Lacie</a>
8 Lacie
9
and
10 <a class="cccc" href="http://example.com" id="link3">Tillie</a>
11 Tillie
12
鋼鐵學爬蟲.
此時返回結果還是生成器。遍歷輸出一下可以看到,這次的輸出結果就包含了 span 節點。descendants 會遞歸查詢所有子節點,得到所有的子孫節點。
除此之外,還有父節點parent
和祖先節點parents
,兄弟節點next_sibling
和previous_siblings
日常用得少不再演示,後續需要自行查官方文檔即可。
方法選擇器
前面聊的通過屬性選擇節點,但如果進行比較複雜的話還是比較繁瑣。幸好BeautifulSoup還為我們提供另外一些查詢方法,比如find_all 和 find ,調用他們傳入相應參數就可以靈活查詢。
find_all
顧名思義,就是查詢所有符合條件的元素,可以給它傳入一些屬性或文本來得到符合條件的元素,功能十分強大。
它的 API 如下:
find_all(name , attrs , recursive , text , **kwargs)
我們可以根據節點名來查詢元素,下麵我們用一個實例來感受一下:
html5='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">鋼鐵</li>
<li class="element">知識</li>
<li class="element">倉庫</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">python</li>
<li class="element">java</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html5, 'lxml')
print(soup.find_all(name='ul'))
print(type(soup.find_all(name='ul')[0]))
'''
[<ul class="list" id="list-1">
<li class="element">鋼鐵</li>
<li class="element">知識</li>
<li class="element">倉庫</li>
</ul>, <ul class="list list-small" id="list-2">
<li class="element">python</li>
<li class="element">java</li>
</ul>]
<class 'bs4.element.Tag'>
'''
可以看到返回了一個列表,分別是兩個ul長度為2,且類型依然是bs4.element.Tag類型。
因為都是Tag類型,所以依然可以繼續嵌套查詢,還是同樣文本,查詢ul節點後再繼續查詢內部li節點。
from bs4 import BeautifulSoup
soup = BeautifulSoup(html5, 'lxml')
for ul in soup.find_all(name='ul'):
print(ul.find_all(name='li'))
'''
[<li class="element">鋼鐵</li>, <li class="element">知識</li>, <li class="element">倉庫</li>]
[<li class="element">python</li>, <li class="element">java</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">鋼鐵</li>, <li class="element">知識</li>, <li class="element">倉庫</li>]
鋼鐵
知識
倉庫
[<li class="element">python</li>, <li class="element">java</li>]
python
java
'''
find
除了 find_all 方法,還有 find 方法,不過 find 方法返回的是單個元素,也就是第一個匹配的元素,而 find_all 返回的是所有匹配的元素組成的列表。示例如下:
html5='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">鋼鐵</li>
<li class="element">知識</li>
<li class="element">倉庫</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">python</li>
<li class="element">java</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html5, '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">鋼鐵</li>
<li class="element">知識</li>
<li class="element">倉庫</li>
</ul>
<class 'bs4.element.Tag'>
<ul class="list" id="list-1">
<li class="element">鋼鐵</li>
<li class="element">知識</li>
<li class="element">倉庫</li>
</ul>
'''
返回結果不再是列表形式,而是第一個匹配的節點元素,類型依然是 Tag 類型。
其它方法
另外還有許多的查詢方法,用法與前面介紹的 find_all、find 方法完全相同,只不過查詢範圍不同,在此做一下簡單的說明。
find_parents 和 find_parent:前者返回所有祖先節點,後者返回直接父節點。
find_next_siblings 和 find_next_sibling:前者返回後面所有的兄弟節點,後者返回後面第一個兄弟節點。
find_previous_siblings 和 find_previous_sibling:前者返回前面所有的兄弟節點,後者返回前面第一個兄弟節點。
find_all_next 和 find_next:前者返回節點後所有符合條件的節點,後者返回第一個符合條件的節點。
find_all_previous 和 find_previous:前者返回節點前所有符合條件的節點,後者返回第一個符合條件的節點。
CSS選擇器
BeautifulSoup還提供了另外一種選擇器,CSS選擇器。如果對 Web 開發熟悉的話,那麼對 CSS 選擇器肯定也不陌生。如果不熟悉的話,可以參考 http://www.w3school.com.cn/cssref/css_selectors.asp 瞭解。
使用 CSS 選擇器,只需要調用 select 方法,傳入相應的 CSS 選擇器即可,我們用一個實例來感受一下:
html5='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">鋼鐵</li>
<li class="element">知識</li>
<li class="element">倉庫</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">python</li>
<li class="element">java</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html5, '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">鋼鐵</li>, <li class="element">知識</li>, <li class="element">倉庫</li>, <li class="element">python</li>, <li class="element">java</li>]
[<li class="element">python</li>, <li class="element">java</li>]
<class 'bs4.element.Tag'>
'''
結果為所有匹配的節點。例如select('ul li')
則是所有ul節點下麵的所有li節點,返回結果是列表。
select 方法同樣支持嵌套選擇(soup.select('ul'))、屬性獲取(ul['id']),以及文本獲取(li.string/li.get_text())
---- 鋼鐵知識庫 2022.08.22
結語
到此 BeautifulSoup 的使用介紹基本就結束了,最後鋼鐵知識庫做一下簡單的總結:
- 推薦使用 LXML 解析庫,速度快、容錯能力強。
- 建議使用 find、find_all 方法查詢匹配單個結果或者多個結果。
- 如果對 CSS 選擇器熟悉的話可以使用 select 匹配,可以像Xpath一樣匹配所有。