我們也可以根據網頁 HTML 元素的屬性來萃取指定的 HTML 節點,例如搜尋 id
屬性為 link2
的節點:
# 根據 id 搜尋 link2_tag = soup.find(id='link2') print(link2_tag)
<a href="/my_link2" id="link2">Link 2</a>
我們可以結合 HTML 節點的名稱與屬性進行更精確的搜尋,例如搜尋 href
屬性為 /my_link1
的 a
節點:
# 搜尋 href 屬性為 /my_link1 的 a 節點 a_tag = soup.find_all("a", href="/my_link1") print(a_tag)
[<a href="/my_link1" id="link1">Link 1</a>]
搜尋屬性時,也可以使用正規表示法,例如以正規表示法比對超連結網址:
import re # 以正規表示法比對超連結網址 links = soup.find_all(href=re.compile("^/my_linkd")) print(links)
[<a href="/my_link1" id="link1">Link 1</a>, <a href="/my_link2" id="link2">Link 2</a>]
我們也可以同時使用多個屬性的條件進行篩選:
# 以多個屬性條件來篩選 link = soup.find_all(href=re.compile("^/my_linkd"), id="link1") print(link)
[<a href="/my_link1" id="link1">Link 1</a>]
在 HTML5 中有一些屬性名稱若直接寫在 Python 的參數中會有一些問題,例如 data-*
這類的屬性直接寫的話,就會產生錯誤訊息:
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>', 'html.parser') # 錯誤的用法 data_soup.find_all(data-foo="value")
SyntaxError: keyword can't be an expression
遇到這種狀況,可以把屬性的名稱與值放進一個 dictionary 中,再將此 dictionary 指定給 attrs
參數即可:
# 正確的用法 data_soup.find_all(attrs={"data-foo": "value"})
[<div data-foo="value">foo!</div>]
由於 class
是 Python 程式語言的保留字,所以 Beautiful Soup 改以 class_
這個名稱代表 HTML 節點的 class 屬性,例如搜尋 class 為 boldtext
的 b
節點:
# 搜尋 class 為 boldtext 的 b 節點 b_tag = soup.find_all("b", class_="boldtext") print(b_tag)
[<b class="boldtext">Bold Text</b>]
CSS 的 class 屬性也可以使用正規表示法搜尋:
# 以正規表示法搜尋 class 屬性 b_tag = soup.find_all(class_=re.compile("^bold")) print(b_tag)
[<b class="boldtext">Bold Text</b>]
一個 HTML 標籤元素可以同時有多個 CSS 的 class 屬性值,而我們在以 class_
比對時,只要其中一個 class 符合就算比對成功,例如:
css_soup = BeautifulSoup('<p class="body strikeout"></p>', 'html.parser') # 只要其中一個 class 符合就算比對成功 p_tag = css_soup.find_all("p", class_="strikeout") print(p_tag)
[<p class="body strikeout"></p>]
我們也可以拿完整的 class 字串來進行比對:
# 比對完整的 class 字串 p_tag = css_soup.find_all("p", class_="body strikeout") print(p_tag)
[<p class="body strikeout"></p>]
不過如果多個 class 名稱排列順序不同時,就會失敗:
# 若順序不同,則會失敗 p_tag = css_soup.find_all("p", class_="strikeout body") print(p_tag)
[]
遇到多個 CSS class 的狀況,建議改用 CSS 選擇器來篩選:
# 使用 CSS 選擇器 p_tag = css_soup.select("p.strikeout.body") print(p_tag)
[<p class="body strikeout"></p>]
若要依據文字內容來搜尋特定的節點,可以使用 find_all
配合 string
參數:
links_html = """ <a id="link1" href="/my_link1">Link One</a> <a id="link2" href="/my_link2">Link Two</a> <a id="link3" href="/my_link3">Link Three</a> """ soup = BeautifulSoup(links_html, 'html.parser') # 搜尋文字為「Link One」的超連結 soup.find_all("a", string="Link One")
[<a href="/my_link1" id="link1">Link One</a>]
亦可使用正規表示法批配文字內容:
# 以正規表示法搜尋文字為「Link」開頭的超連結 soup.find_all("a", string=re.compile("^Link"))
[<a href="/my_link1" id="link1">Link One</a>, <a href="/my_link2" id="link2">Link Two</a>, <a href="/my_link3" id="link3">Link Three</a>]
前面介紹的 find_all
都是向下搜尋子節點,如果需要向上搜尋父節點的話,可以改用 find_parents
函數(或是 find_parent
),它可讓我們以某個特定節點為起始點,向上搜尋父節點:
html_doc = """ <body><p class="my_par"> <a id="link1" href="/my_link1">Link 1</a> <a id="link2" href="/my_link2">Link 2</a> <a id="link3" href="/my_link3">Link 3</a> <a id="link3" href="/my_link4">Link 4</a> </p></body> """ soup = BeautifulSoup(html_doc, 'html.parser') link2_tag = soup.find(id="link2") # 往上層尋找 p 節點 p_tag = link2_tag.find_parents("p") print(p_tag)
[<p class="my_par"> <a href="/my_link1" id="link1">Link 1</a> <a href="/my_link2" id="link2">Link 2</a> <a href="/my_link3" id="link3">Link 3</a> <a href="/my_link4" id="link3">Link 4</a> </p>]
如果想要在在同一層往前尋找特定節點,則可用 find_previous_siblings
函數(或是 find_previous_sibling
):
# 在同一層往前尋找 a 節點 link_tag = link2_tag.find_previous_siblings("a") print(link_tag)
[<a href="/my_link1" id="link1">Link 1</a>]
如果想要在在同一層往後尋找特定節點,則可用 find_next_siblings
函數(或是 find_next_sibling
):
# 在同一層往後尋找 a 節點 link_tag = link2_tag.find_next_siblings("a") print(link_tag)
[<a href="/my_link3" id="link3">Link 3</a>, <a href="/my_link4" id="link3">Link 4</a>]
如果我們想要用 Beautiful Soup 解析已經下載的 HTML 檔案,可以直接將開啟的檔案交給 BeautifulSoup
處理:
from bs4 import BeautifulSoup # 從檔案讀取 HTML 程式碼進行解析 with open("index.html") as f: soup = BeautifulSoup(f)
以下我們提供了幾個實際以 Beautiful Soup 開發的網路爬蟲範例程式,請繼續閱讀下一頁。