鋼鐵知識庫,一個學習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 name="dromouse"><b>The Dormouse's story</b></p>
<p >Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a href="http://example.com/lacie" id="link2">Lacie</a> and
<a href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p >...</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 name="dromouse">
<b>
The Dormouse's story
</b>
</p>
<p >
Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" id="link1">
<!-- Elsie -->
</a>
,
<a href="http://example.com/lacie" id="link2">
Lacie
</a>
and
<a href="http://example.com/tillie" id="link3">
Tillie
</a>
;
and they lived at the bottom of a well.
</p>
<p >
...
</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 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 >
鋼鐵知識庫
<a href="http://a.com" id="link1">
<span>Elsie</span>
</a>
<a href="http://b.com" id="link2">Lacie</a>
and
<a href="http://example.com" id="link3">Tillie</a>
鋼鐵學爬蟲.
</p>
<p >...</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html4, 'lxml')
print(soup.p.contents)
'''
['\n 鋼鐵知識庫\n ', <a href="http://a.com" id="link1">
<span>Elsie</span>
</a>, '\n', <a href="http://b.com" id="link2">Lacie</a>, ' \n and\n ', <a 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 href="http://a.com" id="link1">
<span>Elsie</span>
</a>
2
3 <a href="http://b.com" id="link2">Lacie</a>
4
and
5 <a href="http://example.com" id="link3">Tillie</a>
6
鋼鐵學爬蟲.
'''
還是同樣的 HTML 文本,這里呼叫了 children 屬性來選擇,回傳結果是生成器型別,接下來,我們用 for 回圈輸出相應的內容,
如果要得到所有的子孫節點的話,可以呼叫 descendants 屬性:
<generator object Tag.descendants at 0x000001D77A90E570>
0
鋼鐵知識庫
1 <a href="http://a.com" id="link1">
<span>Elsie</span>
</a>
2
3 <span>Elsie</span>
4 Elsie
5
6
7 <a href="http://b.com" id="link2">Lacie</a>
8 Lacie
9
and
10 <a 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 >
<div >
<h4>Hello</h4>
</div>
<div >
<ul id="list-1">
<li >鋼鐵</li>
<li >知識</li>
<li >倉庫</li>
</ul>
<ul id="list-2">
<li >python</li>
<li >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 id="list-1">
<li >鋼鐵</li>
<li >知識</li>
<li >倉庫</li>
</ul>, <ul id="list-2">
<li >python</li>
<li >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 >鋼鐵</li>, <li >知識</li>, <li >倉庫</li>]
[<li >python</li>, <li >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 >鋼鐵</li>, <li >知識</li>, <li >倉庫</li>]
鋼鐵
知識
倉庫
[<li >python</li>, <li >java</li>]
python
java
'''
find
除了 find_all 方法,還有 find 方法,不過 find 方法回傳的是單個元素,也就是第一個匹配的元素,而 find_all 回傳的是所有匹配的元素組成的串列,示例如下:
html5='''
<div >
<div >
<h4>Hello</h4>
</div>
<div >
<ul id="list-1">
<li >鋼鐵</li>
<li >知識</li>
<li >倉庫</li>
</ul>
<ul id="list-2">
<li >python</li>
<li >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 id="list-1">
<li >鋼鐵</li>
<li >知識</li>
<li >倉庫</li>
</ul>
<class 'bs4.element.Tag'>
<ul id="list-1">
<li >鋼鐵</li>
<li >知識</li>
<li >倉庫</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 >
<div >
<h4>Hello</h4>
</div>
<div >
<ul id="list-1">
<li >鋼鐵</li>
<li >知識</li>
<li >倉庫</li>
</ul>
<ul id="list-2">
<li >python</li>
<li >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 >
<h4>Hello</h4>
</div>]
[<li >鋼鐵</li>, <li >知識</li>, <li >倉庫</li>, <li >python</li>, <li >java</li>]
[<li >python</li>, <li >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一樣匹配所有,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/502432.html
標籤:Python
