Python Regex 實戰:精準抓取 XML 屬性值 (findall vs finditer 與 Group 的奧義)

加入好友
加入社群
Python Regex 實戰:精準抓取 XML 屬性值 (findall vs finditer 與 Group 的奧義) - 儲蓄保險王

在處理 XML 或 HTML 字串時,
我們常需要從 這樣的結構中提取特定的值。
這篇文章將以「抓取 XML 中的 rId」為例,
帶你徹底搞懂 Python re 模組的關鍵用法。

  1. 任務目標
    假設我們有一段 XML 字串,裡面混雜了各種屬性,
    我們的目標是只抓出 rId 的編號(例如 rId5, rId6)。
xml_text = """
<w:document>
  <w:p>
    <w:hyperlink r:id="rId5">Google</w:hyperlink>
    <a:blip r:embed="rId6" />
    <a:blip r:link="rId7" />
    <w:other attr="ignore_me" />
  </w:p>
</w:document>
"""

我們想抓到的結果是:['rId5', 'rId6', 'rId7']


2. findall vs finditer:記憶體與效能的抉擇

這兩個函數都是用來「找出所有符合的結果」,但運作方式不同。

re.findall(pattern, string)

  • 行為:一次掃描整串文字,把所有結果存成一個 List 回傳。
  • 優點:簡單直觀,直接拿到清單。
  • 缺點:如果 xml_text 非常巨大(例如 100MB),
    它會一次佔用大量記憶體來存結果。
# %%
import re

# 簡單粗暴直接拿 List
rids = re.findall(r'rId[0-9]+', xml_text)
print(rids) 
# Output: ['rId5', 'rId6', 'rId7']

輸出:

Python Regex 實戰:精準抓取 XML 屬性值 (findall vs finditer 與 Group 的奧義) - 儲蓄保險王

re.finditer(pattern, string) (推薦用於大檔案)
行為:回傳一個 Iterator (迭代器)。
它不會一次做完,而是你用 for 迴圈跑一次,
它才去找下一個(Lazy Evaluation)。
優點:極省記憶體。適合處理大型 XML。
缺點:回傳的是 Match Object,
需要多一步 .group() 才能拿到字串。

import re

# 簡單粗暴直接拿 List
rids = re.findall(r'rId[0-9]+', xml_text)
print(rids) 
# Output: ['rId5', 'rId6', 'rId7']

輸出:

Python Regex 實戰:精準抓取 XML 屬性值 (findall vs finditer 與 Group 的奧義) - 儲蓄保險王
  1. Group 的奧義:精準定位
    在複雜的 XML 中,單純搜 rId[0-9]+ 可能會誤抓
    (例如內文剛好有 “rId5” 這個字)。
    我們通常會加上前綴條件(例如必須是 r:embed=”…”)。

這時候就需要用到 Group (群組)。

什麼是 Group?
在 Regex 中,用小括號 () 包起來的部分就是一個 Group。

範例模式:r’r:embed=”(rId[0-9]+)”‘
Group 0 (完整匹配):代表 Regex 抓到的整個字串。
m.group(0) -> ‘r:embed=”rId6″‘ (包含屬性名和引號,髒資料)
Group 1 (捕獲群組):代表第一個括號 (…) 裡的內容。
m.group(1) -> ‘rId6’ (乾淨的 ID,這才是我們要的!)

# 錯誤示範抓到髒資料
for m in re.finditer(r'r:embed="(rId[0-9]+)"', xml_text):
    print(f"Group 0: {m.group(0)}") 
    # Output: r:embed="rId6" (不是我們要的)

# 正確示範抓到乾淨 ID
for m in re.finditer(r'r:embed="(rId[0-9]+)"', xml_text):
    print(f"Group 1: {m.group(1)}") 
    # Output: rId6 (Bingo!)

輸出:

Python Regex 實戰:精準抓取 XML 屬性值 (findall vs finditer 與 Group 的奧義) - 儲蓄保險王
  1. 非捕獲群組 (?:…):只比對,不佔位
    有時候我們想比對多種屬性名(r:embed 或 r:link 或 r:id),
    但我們不想把這些屬性名存進 Group 裡(因為我們只關心後面的 ID)。

這時候就用 (?:…)。

模式:r'(?:r:embed|r:link|r:id)=”(rId[0-9]+)”‘
解析:
(?:r:embed|r:link|r:id):這是 非捕獲群組。
它負責比對「屬性名必須是這三個之一」,但不會變成 Group 1。
=”(rId[0-9]+)”:這是 捕獲群組。
因為前面的括號被標記為「非捕獲」,
所以這個括號順理成章成為 Group 1。

pattern = r'(?:r:embed|r:link|r:id)="(rId[0-9]+)"'

for m in re.finditer(pattern, xml_text):
    # 因為前面的屬性名是 (?:...),所以這裡的 group(1) 依然是 ID
    print(f"Found ID: {m.group(1)}")

# Output:
# Found ID: rId5 (來自 r:id)
# Found ID: rId6 (來自 r:embed)
# Found ID: rId7 (來自 r:link)

輸出:

Python Regex 實戰:精準抓取 XML 屬性值 (findall vs finditer 與 Group 的奧義) - 儲蓄保險王

總結
大檔案處理:優先使用 re.finditer,避免記憶體爆炸。
精準抓取:使用 () 建立捕獲群組,並用 m.group(1) 提取乾淨資料。
多條件比對:使用 (?:A|B) 非捕獲群組來處理前綴,
確保 group(1) 永遠是你想要的那個值。

推薦hahow線上學習python: https://igrape.net/30afN

加入好友
加入社群
Python Regex 實戰:精準抓取 XML 屬性值 (findall vs finditer 與 Group 的奧義) - 儲蓄保險王

儲蓄保險王

儲蓄險是板主最喜愛的儲蓄工具,最喜愛的投資理財工具則是ETF,最喜愛的省錢工具則是信用卡

You may also like...

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *