1.3 正規表示式#
概念說明#
正規表示式(Regular Expression,簡稱 regex)是一種描述文字模式的迷你語言。它源自形式語言理論中的「正規語言」:任何正規表示式都等價於一個有限自動機(finite automaton),這也是為什麼它可以用極高效率進行比對。常見語法包括:
- 字面字元:
abc比對字串 "abc"。 - 字元類別:
[abc]比對 a、b、c 任一;[0-9]比對數字;預設類別\d(數字)、\w(文字字元)、\s(空白)。 - 數量詞:
*(0 次以上)、+(1 次以上)、?(0 或 1 次)、{m,n}(m 到 n 次)。 - 錨點:
^開頭、$結尾。 - 群組與擷取:
(...)可擷取子字串供事後取用。 - 或運算:
cat|dog比對 cat 或 dog。
正規表示式最著名的陷阱是貪婪比對:.* 會盡量多吃字元。例如對 "<a><b>" 套用 <.*> 會比對到整個字串,而 .*?(非貪婪)才會停在第一個 >。寫 regex 時務必留意。
在理論上,正規語言無法表達「對稱括號」這類需要計數的結構,因此 HTML 解析等任務不該用 regex 硬做——這是「regex 不是萬能」的經典教訓。
演算法步驟:用 regex 擷取資料#
- 明確列出目標文字的結構(如日期、電話、Email)。
- 將結構翻譯成模式,注意用非貪婪量詞與字元類別縮小範圍。
- 用
re.finditer或re.findall掃描全文取得所有命中。 - 若有擷取群組,從 match 物件取出各群組內容。
- 用多個樣本測試,特別檢查邊界情況(空字串、特殊符號)。
Python 程式碼範例#
import re
text = """聯絡資訊:
電話 0912-345-678,市話 (02)2766-1234
Email: alice@example.com, bob@test.com.tw
日期:2026/10/18 與 2026-10-19
標籤:<a><b>文字</b></a>
"""
# 1) 手機號碼:\d 代表數字
print(re.findall(r'09\d{2}-\d{3}-\d{3}', text))
# ['0912-345-678']
# 2) Email 擷取(含群組)
for m in re.finditer(r'([\w.]+)@([\w.]+)', text):
print(m.group(0), '=> 帳號:', m.group(1), '網域:', m.group(2))
# 3) 貪婪 vs 非貪婪
print(re.findall(r'<.*>', text)) # ['<a><b>文字</b></a>']
print(re.findall(r'<.*?>', text)) # ['<a>', '<b>', '</b>', '</a>']
# 4) 日期兩種格式:| 連接分支
print(re.findall(r'\d{4}[/\-]\d{2}[/\-]\d{2}', text))
# 5) sub:遮蔽電話中間三碼
print(re.sub(r'(09\d{2})-\d{3}(-\d{3})', r'\1-***\2', text))
# 6) 中文字元類別(Unicode 範圍)
print(re.findall(r'[\u4e00-\u9fff]+', text))
# 7) split:以逗號與空白切分
print(re.split(r'[,,、\s]+', '蘋果, 香蕉、鳳梨 芒果'))
複雜度分析#
比對長度 n 的文字與長度 m 的模式:基於有限自動機(NFA 模擬或 DFA)的引擎,時間為 O(n·m) 或建好 DFA 後 O(n),且不會發生災難性回溯(catastrophic backtracking)。Python 的 re 採用回溯法,多數實用模式為線性,但像 (a+)+b 這類巢狀量詞模式在不可能比對成功時,可能退化到指數時間——這是「正規表示式阻斷服務(ReDoS)」攻擊的根源。撰寫模式時應避免巢狀不定量詞,或改用非回溯引擎(如 Rust 的 regex crate、Google RE2)。
練習#
- 寫出一個比對台灣身分證字號(一個大寫英文字母加 9 位數字)的模式,並測試
A123456789。 - 寫一個模式,找出文字中所有「以西元 19xx 或 20xx 開頭的年份」,並說明你如何避免比對到 20261 這種錯誤年份(提示:使用邊界條件
(?!\d))。 - 對模式
(x+x+)+y在字串"xxxxxxxxxxxxxxxxxxxxxxxx"(不含 y)上執行re.match並計時,觀察回溯造成的時間爆炸。