1. 簡介
Beautiful Soup(美麗湯)
是一個Python第三方
庫,用於從HTML和XML文件中提取數據。它與您最喜歡的解析器一起使用,提供了導航
,搜索
和修改解析樹
的慣用方式,點擊此處進入官網。最新版本Beautiful Soup 4
簡稱bs4
。優勢:相比於ET
庫, 功能更全,可以選擇解析器來解析文檔,既支持html, 也支持xml,容錯度(簡單理解爲文檔格式自動補全功能)也更高,API也很好用。
2. 安裝
2.1 庫本身的安裝
命令安裝格式如下:
pip install --user -i http://pypi.douban.com/simple --trusted-host pypi.douban.com beautifulsoup4
使用Pycharm圖形化界面安裝如下:
2.2 解析器的安裝
把指定內容,轉換成可解析的對象,不同的解析器,解析的結果不同,容錯能力不同,解析效率不同。如圖所示:
html5lib解析器
安裝圖示如下:
lxml解析器
安裝圖示如下:
如果可以,我建議讀者
安裝並使用lxml以提高速度。基本示例代碼如下:
from bs4 import BeautifulSoup
str1 = """
<a>Amo好帥~</a>
"""
# 1.使用python內置解析器
soup1 = BeautifulSoup(str1, "html.parser")
print(soup1)
print("*" * 30)
# 2.使用lxml解析
soup2 = BeautifulSoup(str1, "lxml")
print(soup2)
print("*" * 30)
# 3.lxml-xml解析xml
# soup3 = BeautifulSoup(str1, "lxml-xml")
soup3 = BeautifulSoup(str1, "xml") # "lxml-xml"和"xml"這兩種寫法都是可以的
print(soup3)
print("*" * 30)
# 4.使用html5lib解析器
soup4 = BeautifulSoup(str1, "html5lib")
print(soup4)
上述代碼執行結果如下:
3. 常用API
bs4重要的幾個類別如圖所示:
3.1 BeautifulSoup常用操作
此類作用: 定義了節點樹操作的基本方法。具體用法如下圖所示:
示例代碼如下:
from bs4 import BeautifulSoup
str1 = """
<div><a>a1<b>b1</b></a><a>a2</a></div>
"""
soup1 = BeautifulSoup(str1, features="lxml")
print(soup1)
print(type(soup1)) # <class 'bs4.BeautifulSoup'>
# aa: 標籤名 第二個參數: 命名空間 "amo":命名空間別名 最後一個參數:屬性
print(soup1.new_tag("aa", "https://blog.csdn.net/xw1680", "amo", {"age": 18}))
print(soup1.new_string("hello")) # 創建文本節點
print(type(soup1.new_string("hello"))) # 查看類型:<class 'bs4.element.NavigableString'>
print(soup1.prettify()) # 美化輸出
# 檢索所有的a標籤
print(soup1.find("a").find("b")) # <b>b1</b>
print(soup1.a.b) # <b>b1</b>
print(soup1("a")) # [<a>a1<b>b1</b></a>, <a>a2</a>]
print(soup1.findAll("a")) # [<a>a1<b>b1</b></a>, <a>a2</a>]
soup1.reset() # 重置文檔
3.2 Tag常用操作
查看及修改標籤名,示例代碼如下:
from bs4 import BeautifulSoup
str1 = """
<div id='main'><a>a1<b>b1</b></a><a>a2</a></div>
"""
soup1 = BeautifulSoup(str1, features="lxml")
print(soup1)
print(soup1.find("div").name) # 返回標籤名: div
# 注意標籤名是可以修改的
soup1.find("div").name = "h"
print(soup1)
上述代碼執行結果如下:
屬性相關操作,示例代碼如下:
from bs4 import BeautifulSoup
str1 = """
<div id='main' class='main xxx'><a>a1<b>b1</b></a><a>a2</a></div>
"""
# 1.使用lxml解析器
soup1 = BeautifulSoup(str1, features="lxml")
# 判斷div中是否有id屬性
print(soup1.find("div").has_attr("id")) # True
# 判斷div中是否有class屬性
print(soup1.find("div").has_attr("class")) # True
# 一般來說一個標籤對應id的值在整個頁面是唯一的 而class的值可以是多個 所以解析爲列表
# {'id': 'main', 'class': ['main', 'xxx']}
print(soup1.find("div").attrs) # 獲取屬性
# 注意如果被解析爲xml 結果爲: {'id': 'main', 'class': 'main xxx'}
# 獲取key對應的屬性值
print(soup1.find("div").attrs["id"]) # main
# 注意:get方法如果找不到key會返回None 而使用[]的方式找不到key報錯
print(soup1.find("div").get("class")) # ['main', 'xxx']
# 增加修改
soup1.find("div").attrs["id"] = "box"
print(soup1)
soup1.find("div").attrs["style"] = "font-size:18px;"
# 刪除屬性
del soup1.find("div").attrs["id"]
print(soup1)
上述代碼執行結果如下:
文本相關操作,示例代碼如下:
from bs4 import BeautifulSoup
str1 = """
<div id='main' class='main xxx'><a> a1 <b>b1</b></a><a>a2</a></div>
<h1><a><span>amo</span></a></h1>
"""
# 1.使用lxml解析器
soup1 = BeautifulSoup(str1, features="lxml")
# .text: 獲取所有文本內容拼接後的字符串
print(soup1.find("div").text) # a1 b1a2
print(soup1.find("div").get_text()) # a1 b1a2
print(type(soup1.find("div").text)) # <class 'str'>
# separator:指名分隔符 strip:默認爲False 指是否壓縮兩側空白
print(soup1.find("div").get_text(separator="-", strip=True)) # a1-b1-a2
# .string: 如果只有一個文本子節點 返回該值
print(soup1.find("b").string) # b1
# .string: 如果沒有子節點,或者多於一個 返回None
print(soup1.find("div").string) # None
# 如果(遞歸)只有一個元素節點,返回該元素節點的string
# h1-->a-->span: amo
print(soup1.find("h1").string) # amo
print(type(soup1.find("h1").string)) # <class 'bs4.element.NavigableString'>
print(soup1)
# 所有子節點清空,只留一個文本節點
# tag.string = "new_value"
soup1.find("h1").string = "xxxx"
print(soup1)
# 獲取標籤內所有文本組成的生成器
# <generator object Tag._all_strings at 0x11b6ac8d0>
print(soup1.find("div").strings)
for s in soup1.find("div").strings:
print(s)
# 獲取標籤內所有文本(壓縮文本兩側空白字符後)組成的生成器
# <generator object Tag.stripped_strings at 0x11b6ac8d0>
print(soup1.find("div").stripped_strings)
for s in soup1.find("div").stripped_strings:
print(s)
上述代碼執行結果如下:
清空/刪除/判定/索引等操作,示例代碼如下:
from bs4 import BeautifulSoup
str1 = """
<div id='main' class='main xxx'><a> a1 <b>b1</b></a><a>a2</a></div>
<h1 id="box"><a><span>amo</span></a></h1>
<img src="1.png"/>
<h2 id="box"><p>xxxx</p></h2>
"""
# 1.使用lxml解析器
soup1 = BeautifulSoup(str1, features="lxml")
div = soup1.div
a = soup1.a
# 索引
print(div.index(a)) # 0
# 判定是否是一個空節點 自關閉
print(soup1.find("img").is_empty_element) # True
# 清空:不清空屬性
soup1.find("h1").clear()
print(soup1.find("h1")) # <h1 id="box"></h1>
soup1.find("h2").decompose() # 把自己全部給幹掉了
print(soup1)
上述代碼執行結果如下:
節點獲取,子/後代節點,示例代碼如下:
from bs4 import BeautifulSoup
str1 = """
<div id='main' class='main xxx'><a> a1 <b>b1</b></a><a>a2</a></div>
<h1 id="box"><a><span>amo</span></a></h1>
<img src="1.png"/>
<h2 id="box"><p>xxxx</p></h2>
"""
# 1.使用lxml解析器
soup1 = BeautifulSoup(str1, features="lxml")
print(soup1.contents)
print("-" * 50)
print(soup1.children) # 類似於contents 一個可迭代對象
for item in soup1.children:
print(item)
print("-" * 50)
# 子孫節點 一個生成器對象:<generator object Tag.descendants at 0x11a634550>
print(soup1.descendants)
# find:
# recursive:是否遞歸遍歷所有子孫節點,默認True
# name:查找所有名字爲name的tag 字符串
print(soup1.find(name="p")) # 字符串 "p" <p>xxxx</p>
print(soup1.find(name=["p", "span"])) # 列表 先找到誰就返回誰 因爲find結果只有一個 <span>amo</span>
print(soup1.find_all(name=["p", "span"])) # [<span>amo</span>, <p>xxxx</p>]
print(soup1.find(name=True)) # 匹配所有標籤 name後面還可以跟函數名
print(soup1.find(name="p", recursive=False)) # None
# 按屬性名和值查找
print(soup1.find(attrs={"id": "box"}))
# 用於搜素字符串 會找到.string方法與text參數值相符的tag
print(soup1.find(text="xxxx"))
print(soup1.find_all("a", limit=2)) # 從解析的字符串中可以看到a有三個 但是隻返回2個
# 如果一個指定名字的參數不是搜索內置的參數名,搜索時會把該參數當作tag的屬性來搜索
# 如果要按class屬性搜索,因爲class是python的保留字,需要寫做class_
print(soup1.find(id="box")) # <h1 id="box"><a><span>amo</span></a></h1>
# <div class="main xxx" id="main"><a> a1 <b>b1</b></a><a>a2</a></div>
print(soup1.find(class_="main xxx"))
上述代碼執行結果如下:
使用css選擇器進行子節點檢索,常用的css選擇器如下:
選擇器 | 示例 |
---|---|
通配符選擇器 | *:選擇所有的節點 |
標籤選擇器 | tag_name:選擇特定標籤名稱的節點 |
類選擇器 | .class:選擇具有特定class的節點 |
id選擇器 | #id:選擇具有特定id屬性值的節點 |
屬性選擇器 | [attr]:選取擁有attr屬性的節點 |
屬性選擇器 | [attr=“val”]:選取attr屬性值等於val的節點 |
屬性選擇器 | [attr^=“val”]:選取attr屬性值以val開頭的節點 |
屬性選擇器 | [attr$=“val”]:選取attr屬性值以val結尾的節點 |
屬性選擇器 | [attr*=“val”]:選取attr屬性值包含val的節點 |
屬性選擇器 | [attr~=“val”]:選取attr屬性包含多個空格分隔的屬性,其中一個等於val的節點 |
關係選擇器 | 選擇器1 選擇器2:選取選擇器1後代節點中的選擇器2 |
關係選擇器 | 選擇器1>選擇器2:選取選擇器1子節點中的選擇器2 |
關係選擇器 | 選擇器1+選擇器2:選取選擇器1後面一個兄弟節點中的選擇器2 |
關係選擇器 | 選擇器1~選擇器2:選取選擇器1後面n個兄弟節點中的選擇器2 |
複合選擇器 | 選擇器1選擇器2:選擇同時滿足兩個選擇器條件的節點(並且) |
羣組選擇器 | 選擇器1,選擇器2:選擇滿足兩個選擇器條件之1的節點(或者) |
簡單示例代碼如下:
from bs4 import BeautifulSoup
str1 = """
<div id="box"><p><span class="sp1">amo1</span></p><p>amo2</p></div>
"""
soup1 = BeautifulSoup(markup=str1, features="lxml")
# 選擇具有id屬性值的節點
# 返回結果:[<div id="box"><p><span class="sp1">amo1</span></p><p>amo2</p></div>]
print(soup1.select("#box"))
print(soup1.select(".sp1")) # [<span class="sp1">amo1</span>]
# <p><span class="sp1">amo1</span></p>
print(soup1.select_one("p"))
3.3 PageElement常用操作
節點公共操作,例如關係控制,替換節點,包裝和拆解節點,提取節點,新增節點等。
- 如果只想操作文檔中的部分節點, 而解析全部文檔(非常耗時),解決方案:解析時, 通過SoupStrainer指定解析片段。示例代碼如下:
運行結果如圖所示:from bs4 import BeautifulSoup from bs4 import SoupStrainer content_str = """ <person> <name>amo</name> <pet> <dog master="amo"> <name>煤球</name> <age>2</age> <color>五彩斑斕黑</color> </dog> <cat master="amo"> <name>皮皮</name> <age>0.2</age> <color>黑白條紋</color> </cat> </pet> </person> """ # 1.創建解析條件對象 cat = SoupStrainer("cat") # 2.解析文檔時 指明parse_only參數 soup = BeautifulSoup(markup=content_str, features="lxml", parse_only=cat) print(soup)
- 使用
replace_with
替換節點,必須有父節點,不要使用父輩節點來替換自己,可以用來替換一個元素節點內的文本內容,示例代碼如下:
運行結果如圖所示:from bs4 import BeautifulSoup content_str = """ <person> <name>amo</name> <pet> <dog master="amo"> <name>煤球</name> <age>2</age> <color>五彩斑斕黑</color> </dog> <cat master="amo"> <name>皮皮</name> <age>0.2</age> <color>黑白條紋</color> </cat> </pet> </person> """ # 替換節點 <name>amo</name> ==> amo文本節點替換爲xxx soup = BeautifulSoup(markup=content_str, features="lxml") soup.find(text="amo").replace_with(soup.new_string("aaa")) print(soup)
- 包裝和拆解節點,示例代碼如下:
運行結果如圖所示:from bs4 import BeautifulSoup content_str = """ <person> <name>amo</name> <pet> <dog master="amo"> <name>煤球</name> <age>2</age> <color>五彩斑斕黑</color> </dog> <cat master="amo"> <name>皮皮</name> <age>0.2</age> <color>黑白條紋</color> </cat> </pet> </person> """ # 包裝和拆解節點 soup = BeautifulSoup(markup=content_str, features="lxml") # 使用指定的tag包裝自己 包裝後的結果還放在self原本的位置 soup.find(text="amo").wrap(soup.find("age")) # 把自己的標籤對抽離掉並返回 soup.find("cat").unwrap() print(soup)
其他節點公共操作如圖所示:
- 節點導航操作如下所示:
soup = BeautifulSoup(markup=content_str, features="lxml") soup.find_next() # 在子節點右側查找 匹配一個 soup.find_all_next() # 在子節點右側查找 匹配n個 soup.find_next_sibling() # 在節點右側查找 兄弟關係 匹配一個 soup.find_next_siblings() # 在節點右側查找 兄弟關係 匹配n個 soup.find_previous() # 在節點左側查找 匹配一個 soup.find_all_previous() # 在節點左側查找 匹配n個 soup.find_previous_sibling() # 在節點左側查找 兄弟關係 匹配一個 soup.find_previous_siblings() # 在節點左側查找 兄弟關係 匹配n個 soup.next # 右邊一個節點 soup.next_siblings # 右邊所有兄弟節點 soup.next_elements # 右邊所有節點 soup.previous # 左邊一個節點 soup.previous_siblings # 左邊所有兄弟節點 soup.previous_elements # 左邊所有節點 soup.find_parent() soup.find_parents() soup.parent() # 父節點 soup.parents # 祖先節點 # 通過 .標籤名 可以訪問到指定節點