小白學 Python 爬蟲(21):解析庫 Beautiful Soup(上)
小白學 Python 爬蟲(21):解析庫 Beautiful Soup(上)
人生苦短,我用 Python
前文傳送門:
小白學 Python 爬蟲(1):開篇
小白學 Python 爬蟲(2):前置準備(一)基本類庫的安裝
小白學 Python 爬蟲(3):前置準備(二)Linux基礎入門
小白學 Python 爬蟲(4):前置準備(三)Docker基礎入門
小白學 Python 爬蟲(5):前置準備(四)資料庫基礎
小白學 Python 爬蟲(6):前置準備(五)爬蟲框架的安裝
小白學 Python 爬蟲(7):HTTP 基礎
小白學 Python 爬蟲(8):網頁基礎
小白學 Python 爬蟲(9):爬蟲基礎
小白學 Python 爬蟲(10):Session 和 Cookies
小白學 Python 爬蟲(11):urllib 基礎使用(一)
小白學 Python 爬蟲(12):urllib 基礎使用(二)
小白學 Python 爬蟲(13):urllib 基礎使用(三)
小白學 Python 爬蟲(14):urllib 基礎使用(四)
小白學 Python 爬蟲(15):urllib 基礎使用(五)
小白學 Python 爬蟲(16):urllib 實戰之爬取妹子圖
小白學 Python 爬蟲(17):Requests 基礎使用
小白學 Python 爬蟲(18):Requests 進階操作
小白學 Python 爬蟲(19):Xpath 基操
小白學 Python 爬蟲(20):Xpath 進階
引言
首先當然是各種資料地址敬上:
- 官方網站:https://www.crummy.com/software/BeautifulSoup/
- 官方文件:https://www.crummy.com/software/BeautifulSoup/bs4/doc/
- 中文文件:https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/
先看下官方對自己的介紹:
Beautiful Soup 提供一些簡單的、 Python 式的函式來處理導航、搜尋、修改分析樹等功能。它是一個工具箱,通過解析文件為使用者提供需要抓取的資料,因為簡單,所以不需要多少程式碼就可以寫出一個完整的應用程式。
Beautiful Soup 自動將輸入文件轉換為 Unicode 編碼,輸出文件轉換為 UTF-8 編碼。你不需要考慮編碼方式,除非文件沒有指定一個編碼方式,這時你僅僅需要說明一下原始編碼方式就可以了。
Beautiful Soup 已成為和 lxml 、 html6lib 一樣出色的 Python 直譯器,為使用者靈活地提供不同的解析策略或強勁的速度。
講人話就是 Beautiful Soup 是一個非常好用、速度又快的 HTML 或 XML 的解析庫。
Beautiful Soup 在解析時實際上依賴解析器,它除了支援Python標準庫中的HTML解析器外,還支援一些第三方解析器。下表列出了主要的解析器,以及它們的優缺點(以下內容來自:https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/# ):
解析器 | 使用方法 | 優勢 | 劣勢 |
---|---|---|---|
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格式的文件 | 速度慢、不依賴外部擴充套件 |
推薦使用 lxml 作為解析器,因為效率更高。在 Python2.7.3 之前的版本和 Python3 中 3.2.2 之前的版本,必須安裝 lxml 或 html5lib ,因為那些 Python 版本的標準庫中內建的 HTML 解析方法不夠穩定。
提示: 如果一段 HTML 或 XML 文件格式不正確的話,那麼在不同的解析器中返回的結果可能是不一樣的,檢視 解析器之間的區別 瞭解更多細節。
基本操作
爬取物件還是小編的個人部落格(小編看著部落格的流量在暗暗心痛)。最基本的,還是先列印首頁的 HTML 原始碼,使用的類庫為 Requests + bs4。
import requests
from bs4 import BeautifulSoup
response = requests.get('https://www.geekdigging.com/')
soup = BeautifulSoup(response.content, "html5lib")
print(soup.prettify())
結果就不貼了,太長,浪費大家翻頁的時間。
首先先解釋一下這裡為什麼選擇了 html5lib
的解析器而不是 lxml 的解析器,因為經過小編測試 lxml 的解析器無法解析某些 HTML 標籤,經過小編的測試,使用 Python 標準庫或者 html5lib
解析器都無此問題,所以這裡選擇使用 Python 標準庫。
上面這段程式碼主要是呼叫了 prettify()
,這個方法的主要作用是把要解析的字串以標準的縮排格式輸出。值得注意的是,這裡的輸出會自動更正 HTML 的格式,但是這一步並不是由 prettify()
這個方法來做的,而是在初始化 BeautifulSoup 時就完成了。
節點選擇
我們使用 Beautiful Soup 的目的是什麼?當然是要選擇我們需要的節點,並從節點中提取出我們需要的資料。
Beautiful Soup 將複雜 HTML 文件轉換成一個複雜的樹形結構,每個節點都是 Python 物件,所有物件可以歸納為4種: Tag
, NavigableString
, BeautifulSoup
, Comment
。
我們直接呼叫節點的名稱就可以選擇節點元素,再呼叫 string 屬性就可以得到節點內的文字了,這種選擇方式速度非常快。
print(soup.title)
print(type(soup.title))
print(soup.title.string)
print(soup.a)
結果如下:
<title>極客挖掘機</title>
<class 'bs4.element.Tag'>
極客挖掘機
<a class="logo" href="/">
<img src="/favicon.jpg" style="margin-right: 10px;"/>極客挖掘機
</a>
可以看到,我們這裡直接輸出的 title
節點,它的型別是 bs4.element.Tag
,並且使用 string 屬性,直接得到了該節點的內容。
這裡我們列印了 a
節點,可以看到,只打印出來了第一個 a
節點,後面的節點並未列印,說明當有多個節點時,這種方式只能獲得第一個節點。
獲取名稱
每個 tag 都有自己的名字,通過 .name
來獲取:
tag = soup.section
print(tag.name)
結果如下:
section
獲取屬性
一個 tag 可能有很多個屬性, tag 的屬性的操作方法與字典相同:
print(tag['class'])
結果如下:
['content-wrap']
也可以直接”點”取屬性, 比如: .attrs
:
print(tag.attrs)
結果如下:
{'class': ['content-wrap']}
獲取內容
可以利用 string
屬性獲取節點元素包含的文字內容,比如要獲取 title
標籤的內容:
print(soup.title.string)
結果如下:
極客挖掘機
巢狀選擇
在上面的示例中,我們的資訊都是從通過 tag 的屬性獲得的,當然 tag 是可以繼續巢狀的選擇下去,比如我們剛才獲取了第一個 a
標籤,我們可以繼續獲取其中的 img
標籤:
print(soup.a.img)
print(type(soup.a.img))
print(soup.a.img.attrs)
結果如下:
<img src="/favicon.jpg" style="margin-right: 10px;"/>
<class 'bs4.element.Tag'>
{'src': '/favicon.jpg', 'style': 'margin-right: 10px;'}
可以看到我們在 a
標籤上繼續選擇的 img
標籤,它的型別依然是 bs4.element.Tag
,並且我們成功的獲取了 img
標籤的屬性值,也就是說,我們在 Tag 型別的基礎上再次選擇得到的依然還是 Tag 型別,所以這樣就可以做巢狀選擇了。
關聯選擇
在選擇節點的時候,我們很少可以一步到位,直接選到所需要的節點,這就需要我們先選中其中的某一個節點,再已它為基準,再選擇它的子節點、父節點或者兄弟節點。
子節點
獲取子節點,我們可以選擇使用 contents 屬性,示例如下:
print(soup.article.contents)
結果太長了,小編就不貼了,這裡輸出了第一個 article
的所有節點,並且返回結果是列表形式。 article
節點裡既包含文字,又包含節點,最後會將它們以列表形式統一返回。
這裡需要注意的是,列表中的每個元素都是 article
的直接子節點,並沒有將再下一級的元素列出來。而使用 children
也可以得到相同的效果。
for child in enumerate(soup.article.children):
print(child)
結果得到的還是相同的的 HTML 文字,這裡呼叫了 children
屬性來選擇,返回結果是生成器型別。
想要得到所有的孫子節點的話,可以使用 descendants
:
for i, child in enumerate(soup.article.descendants):
print(i, child)
父節點
獲取父節點可以使用 parent
屬性,示例如下:
print(soup.title.parent)
結果有些長,就不貼了,各位同學可以自行嘗試一下。
兄弟節點
想要獲取兄弟節點可以使用屬性 next_sibling
和 previous_sibling
。
print('next_sibling:', soup.title.next_sibling)
print('previous_sibling:', soup.title.previous_sibling)
print('next_siblings:', soup.title.next_siblings)
print('previous_siblings:', soup.title.previous_siblings)
結果如下:
next_sibling:
previous_sibling:
next_siblings: <generator object PageElement.next_siblings at 0x00000183342C5D48>
previous_siblings: <generator object PageElement.previous_siblings at 0x00000183342C5D48>
可以看到, next_sibling 和 previous_sibling 分別獲取節點的下一個和上一個兄弟元素,在這裡並沒有獲取到值,而是獲取了很多的空行,這個是在初始化 BeautifulSoup 的時候初始化出來的,而 next_siblings 和 previous_siblings 則分別返回所有前面和後面的兄弟節點的生成器。
示例程式碼
本系列的所有程式碼小編都會放在程式碼管理倉庫 Github 和 Gitee 上,方便大家取用。
示例程式碼-Github
示例程式碼-Gitee
參考
https://beautifulsoup.readthedocs.io/zh_CN/v4.4.