在資料處理、網頁爬蟲或 LLM 輸出解析中,
我們常看到 .*? 這樣的寫法。
這篇筆記將透過實際程式碼,帶您徹底搞懂它的原理。
0. 環境準備與範例資料
首先,我們準備一段模擬 HTML 的字串。
我們的目標是提取每個 <div> 標籤中的內容。
import re
# 範例字串:模擬一段 HTML 代碼
# 目標:我們想分別提取 '你好' 和 '世界'
text = "<div>你好</div><div>世界</div>"
print(f"原始文本: {text}")1. 貪婪匹配 (Greedy Match): .*
符號意義
.: 匹配任意字元 (換行除外)*: 匹配 0 到無限多次
行為特性
「盡可能吃到越多越好」。它會從第一個開始的標籤,一路吃到最後一個結束標籤才肯停下來。
# 貪婪模式:直接用 .*
# 行為:它會從第一個 <div> 吃到「最後一個」 </div>
pattern_greedy = r"<div>(.*)</div>"
match = re.findall(pattern_greedy, text)
print("貪婪匹配結果:", match)
# 預期結果: ['你好</div><div>世界']
# 失敗原因: 它把中間的 </div> 也當作內容吞掉了2. 非貪婪/懶惰匹配 (Lazy Match): .*?
符號意義
?: 當它跟在*後面時,意思是「一旦滿足條件就停止」。
行為特性
「夠用就好」。它會尋找最近的一個結束標籤,滿足條件後就停止,然後繼續找下一個。
# 懶惰模式:加上 ? 變成 .*?
# 行為:遇到「第一個」 </div> 就停止,然後繼續找下一個
pattern_lazy = r"<div>(.*?)</div>"
match = re.findall(pattern_lazy, text)
print("懶惰匹配結果:", match)
# 預期結果: ['你好', '世界']
# 成功原因: 每次遇到 </div> 就結案,不貪心3. 總結比較表
| 寫法 | 名稱 | 行為描述 | 記憶口訣 |
|---|---|---|---|
.* | 貪婪匹配 (Greedy) | 吃到撐死為止 (吃到最後一個結束符號) | 能吃多少吃多少 |
.*? | 非貪婪匹配 (Lazy) | 只要吃飽就停 (遇到第一個結束符號就停) | 夠用就好 |
4. 實戰應用:提取 LLM 回覆中的 JSON
在處理 AI (如 ChatGPT, Ollama) 的回應時,它們常會包含一些對話文字。我們可以使用 .*? 搭配 re.DOTALL (讓 . 也能匹配換行) 來精準提取程式碼區塊。
# 模擬一個 LLM 的混合輸出,包含廢話和 JSON
llm_output = """
Here is the result you asked for:
```json
{
"id": 1,
"name": "GPT",
"role": "Assistant"
}
```
I hope this helps! Let me know if you need anything else.
"""
# 使用 .*? 鎖定 ```json 和 ``` 之間的內容
# flags=re.DOTALL 重要!讓 . 可以匹配換行符號
#
# 解析正則表達式 r"```json\s*(.*?)\s*```":
# 1. ```json : 匹配字面上的 "```json" 字串,這是 Markdown 代碼區塊的開頭。
# 2. \s* : 匹配 0 到多個空白字元 (包含換行 \n),目的是吃掉 "```json" 後面跟著的任何換行。
# 3. (.*?) : 非貪婪捕獲組。這就是我們要的內容!它會一直匹配任意字元,直到遇到下面的結束標記。
# 4. \s* : 匹配 0 到多個空白字元,目的是吃掉內容結尾處可能多餘的換行或空白。
# 5. ``` : 匹配字面上的 "```",這是 Markdown 代碼區塊的結尾。
json_pattern = r"```json\s*(.*?)\s*```"
json_match = re.findall(json_pattern, llm_output, flags=re.DOTALL)
print("提取到的 JSON 內容:")
for m in json_match:
print(m)
# 進階:直接轉成 Python Dict
import json
if json_match:
data = json.loads(json_match[0])
print(f"\n解析成功! Name: {data['name']}")推薦hahow線上學習python: https://igrape.net/30afN
記憶口訣:問號 (?) 的本質是「猶豫」與「客氣」
我們可以把 ? 想像成一個很客氣、不確定、猶豫的人。
- 當 ? 獨自出現時 (量詞模式):0 或 1 次
情境:colou?r
心態:它在猶豫「這裡到底有沒有 ‘u’ 呢?」。
“有沒有都可以啦,沒有也行 (0),有也就這一個 (1)。”
口訣:「可有可無」。 - 當 ? 跟在 * 或 + 後面時 (懶惰模式):非貪婪
情境:.? 心態:它在吃東西 (匹配字串) 時很客氣、很猶豫。 貪婪 (.) 像個餓死鬼:「我要把所有能吃的都吃光!」
懶惰 (.*?) 則是很客氣:「呃… 我是不是該停了?這裡就可以停了嗎?好,那我就停這裡吧。」
它只要一看到能讓條件成立的機會,就會「猶豫」著要不要繼續吃,最後選擇最少的量就停手。
口訣:「見好就收」。
圖像化記憶法
只要記得一個核心概念
? 的本質就是「最小化 (Minimize)」。
- 作為量詞:最小化數量 (最少 0)。
- 作為修飾符:最小化範圍 (最短匹配)。
希望這個譬喻能幫助您一輩子記住它!
推薦hahow線上學習python: https://igrape.net/30afN