by 豆豆
Beautiful Soup 簡介
Beautiful Soup 是一個可以從 HTML 或 XML 文件中提取數據的 Python 庫,它提供了一些簡單的操作方式來幫助你處理文檔導航,查找,修改文檔等繁瑣的工作。因爲使用簡單,所以 Beautiful Soup 會幫你節省不少的工作時間。
Beautiful Soup 安裝
你可以使用如下命令安裝 Beautiful Soup。二選一即可。
$ easy_install beautifulsoup4
$ pip install beautifulsoup4
Beautiful Soup 不僅支持 Python 標準庫中的 HTML 解析器,還支持很多第三方的解析器,比如 lxml,html5lib 等。初始化 Beautiful Soup 對象時如果不指定解析器,那麼 Beautiful Soup 將會選擇最合適的解析器(前提是你的機器安裝了該解析器)來解析文檔,當然你也可以手動指定解析器。這裏推薦大家使用 lxml 解析器,功能強大,方便快捷,而且該解析器是唯一支持 XML 的解析器。
你可以使用如下命令來安裝 lxml 解析器。二選一即可。
$ easy_install lxml
$ pip install lxml
Beautiful Soup 小試牛刀
Beautiful Soup 使用來起來非常簡單,你只需要傳入一個文件操作符或者一段文本即可得到一個構建完成的文檔對象,有了該對象之後,就可以對該文檔做一些我們想做的操作了。而傳入的文本大都是通過爬蟲爬取過來的,所以 Beautiful Soup 和 requests 庫結合使用體驗更佳。
# demo 1
from bs4 import BeautifulSoup
# soup = BeautifulSoup(open("index.html"))
soup = BeautifulSoup("<html><head><title>index</title></head><body>content</body></html>", "lxml") # 指定解析器
print(soup.head)
# 輸出結果
<head><title>index</title></head>
Beautiful Soup 將複雜的 HTML 文檔轉換成一個複雜的樹形結構,每個節點都是 Python 對象,所有對象可以歸納爲 4 種: Tag,NavigableString,BeautifulSoup,Comment。
Tag 就是 HTML 的一個標籤,比如 div,p 標籤等,也是我們用的最多的一個對象。
NavigableString 指標籤內部的文字,直譯就是可遍歷的字符串。
BeautifulSoup 指一個文檔的全部內容,可以當成一個 Tag 來處理。
Comment 是一個特殊的 NavigableString,其輸出內容不包括注視內容。
爲了故事的順利發展,我們先定義一串 HTML 文本,下文的所有例子都是基於這段文本的。
html_doc = """
<html><head><title>index</title></head>
<body>
<p class="title"><b>首頁</b></p>
<p class="main">我常用的網站
<a href="https://www.google.com" class="website" id="google">Google</a>
<a href="https://www.baidu.com" class="website" id="baidu">Baidu</a>
<a href="https://cn.bing.com" class="website" id="bing">Bing</a>
</p>
<div><!--這是註釋內容--></div>
<p class="content1">...</p>
<p class="content2">...</p>
</body>
"""
子節點
Tag 有兩個很重要的屬性,name 和 attributes。期中 name 就是標籤的名字,attributes 是標籤屬性。標籤的名字和屬性是可以被修改的,注意,這種修改會直接改變 BeautifulSoup 對象。
# demo 2
soup = BeautifulSoup(html_doc, "lxml");
p_tag = soup.p
print(p_tag.name)
print(p_tag["class"])
print(p_tag.attrs)
p_tag.name="myTag" # attrs 同樣可被修改,操作同字典
print(p_tag)
#輸出結果
p
['title']
{'class': ['title']}
<myTag class="title"><b>首頁</b></myTag>
由以上例子我麼可以看出,可以直接通過點屬性的方法來獲取 Tag,但是這種方法只能獲取第一個標籤。同時我們可以多次調用點屬性這個方法,來獲取更深層次的標籤。
# demo 3
soup = BeautifulSoup(html_doc, "lxml");
print(soup.p.b)
#輸出結果
<b>首頁</b>
如果想獲得所有的某個名字的標籤,則可以使用 find_all(tag_name) 函數。
# demo 4
soup = BeautifulSoup(html_doc, "lxml");
a_tags=soup.find_all("a")
print(a_tags)
#輸出結果
[<a class="website" href="https://www.google.com" id="google">Google</a>, <a class="website" href="https://www.baidu.com" id="baidu">Baidu</a>, <a class="website" href="https://cn.bing.com" id="bing">Bing</a>]
我們可以使用 .contents 將 tag 以列表方式輸出,即將 tag 的子節點格式化爲列表,這很有用,意味着可以通過下標進行訪問指定節點。同時我們還可以通過 .children 生成器對節點的子節點進行遍歷。
# demo 5
soup = BeautifulSoup(html_doc, "lxml");
head_tag=soup.head
print(head_tag)
print(head_tag.contents)
for child in head_tag.children:
print("child is : ", child)
#輸出結果
<head><title>index</title></head>
[<title>index</title>]
child is : <title>index</title>
.children 只可以獲取 tag 的直接節點,而獲取不到子孫節點,.descendants 可以滿足你。
# demo 6
soup = BeautifulSoup(html_doc, "lxml");
head_tag=soup.head
for child in head_tag.descendants:
print("child is : ", child)
# 輸出結果
child is : <title>index</title>
child is : index
父節點
通過 .parent 屬性獲取標籤的父親節點。 title 的父標籤是 head,html 的父標籤是 BeautifulSoup 對象,而 BeautifulSoup 對象的父標籤是 None。
# demo 7
soup = BeautifulSoup(html_doc, "lxml");
title_tag=soup.title
print(title_tag.parent)
print(type(soup.html.parent))
print(soup.parent)
# 輸出結果
<head><title>index</title></head>
<class 'bs4.BeautifulSoup'>
None
同時,我們可以通過 parents 得到指定標籤的所有父親標籤。
# demo 8
soup = BeautifulSoup(html_doc, "lxml");
a_tag=soup.a
for parent in a_tag.parents:
print(parent.name)
# 輸出結果
p
body
html
[document]
兄弟節點
通過 .next_sibling 和 .previous_sibling 來獲取下一個標籤和上一個標籤。
# demo 9
soup = BeautifulSoup(html_doc, "lxml");
div_tag=soup.div
print(div_tag.next_sibling)
print(div_tag.next_sibling.next_sibling)
# 輸出結果
<p class="content1">...</p>
你可能會納悶,調用了兩次 next_sibling 怎麼只有一個輸出呢,這方法是不是有 bug 啊。事實上是 div 的第一個 next_sibling 是div 和 p 之間的換行符。這個規則對於 previous_sibling 同樣適用。
另外,我們可以通過 .next_siblings 和 .previous_siblings 屬性可以對當前節點的兄弟節點迭代輸出。在該例子中,我們在每次輸出前加了前綴,這樣就可以更直觀的看到 dib 的第一個 previous_sibling 是換行符了。
# demo 10
soup = BeautifulSoup(html_doc, "lxml");
div_tag=soup.div
for pre_tag in div_tag.previous_siblings:
print("pre_tag is : ", pre_tag)
# 輸出結果
pre_tag is :
pre_tag is : <p class="main">我常用的網站
<a class="website" href="https://www.google.com" id="google">Google</a>
<a class="website" href="https://www.baidu.com" id="baidu">Baidu</a>
<a class="website" href="https://cn.bing.com" id="bing">Bing</a>
</p>
pre_tag is :
pre_tag is : <p class="title"><b>首頁</b></p>
pre_tag is :
前進和後退
通過 .next_element 和 .previous_element 獲取指定標籤的前一個或者後一個被解析的對象,注意這個和兄弟節點是有所不同的,兄弟節點是指有相同父親節點的子節點,而這個前一個或者後一個是按照文檔的解析順序來計算的。
比如在我們的文本 html_doc 中,head 的兄弟節點是 body(不考慮換行符),因爲他們具有共同的父節點 html,但是 head 的下一個節點是 title。即soup.head.next_sibling=title soup.head.next_element=title
。
# demo 11
soup = BeautifulSoup(html_doc, "lxml");
head_tag=soup.head
print(head_tag.next_element)
title_tag=soup.title
print(title_tag.next_element)
# 輸出結果
<title>index</title>
index
同時這裏還需要注意的是 title 下一個解析的標籤不是 body,而是 title 標籤內的內容,因爲 html 的解析順序是打開 title 標籤,然後解析內容,最後關閉 title 標籤。
另外,我們同樣可以通過 .next_elements 和 .previous_elements 來迭代文檔樹。由遺下例子我們可以看出,換行符同樣會佔用解析順序,與迭代兄弟節點效果一致。
# demo 12
soup = BeautifulSoup(html_doc, "lxml");
div_tag=soup.div
for next_element in div_tag.next_elements:
print("next_element is : ", next_element)
# 輸出結果
next_element is : 這是註釋內容
next_element is :
next_element is : <p class="content1">...</p>
next_element is : ...
next_element is :
next_element is : <p class="content2">...</p>
next_element is : ...
next_element is :
next_element is :
Beautiful Soup 總結
本章節介紹了 Beautiful Soup 的使用場景以及操作文檔樹節點的基本操作,看似很多東西其實是有規律可循的,比如函數的命名,兄弟節點或者下一個節點的迭代函數都是獲取單個節點函數的複數形式。
同時由於 HTML 或者 XML 這種循環嵌套的複雜文檔結構,致使操作起來甚是麻煩,掌握了本文對節點的基本操作,將有助於提高你寫爬蟲程序的效率。
代碼地址
示例代碼:https://github.com/JustDoPython/python-100-day/tree/master/day-065
參考內容
https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html#
關注公衆號:python技術,回覆"python"一起學習交流