Python爬蟲數據抽取(二):解析庫Beautiful Soup 4

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常用操作

節點公共操作,例如關係控制,替換節點,包裝和拆解節點,提取節點,新增節點等。

  1. 如果只想操作文檔中的部分節點, 而解析全部文檔(非常耗時),解決方案:解析時, 通過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)
    
    運行結果如圖所示:
    在這裏插入圖片描述
  2. 使用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)
    
    運行結果如圖所示:
    在這裏插入圖片描述
  3. 包裝和拆解節點,示例代碼如下:
    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)
    
    運行結果如圖所示:在這裏插入圖片描述
    其他節點公共操作如圖所示:
    在這裏插入圖片描述
  4. 節點導航操作如下所示:
    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  # 祖先節點
    # 通過 .標籤名 可以訪問到指定節點 
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章