小白學 Python 爬蟲(21):解析庫 Beautiful Soup(上)

人生苦短,我用 Python
前文傳送門:
小白學 Python 爬蟲(2):前置准備(一)基本類庫的安裝
小白學 Python 爬蟲(3):前置准備(二)Linux基礎入門
小白學 Python 爬蟲(4):前置准備(三)Docker基礎入門
小白學 Python 爬蟲(6):前置准備(五)爬蟲框架的安裝
小白學 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 進階操作
引言
首先當然是各種資料地址敬上:
- 官方網站: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 上,方便大家取用。
