文本清洗的常見陷阱:全形、零寬字元與 Unicode 正規化
看起來一樣的兩個字串,在程式眼中可能完全不同。中文資料尤其容易中招。
資料清洗最惱人的問題是「肉眼看不出差異」。去重沒去掉、比對永遠不相等、檢索查不到,很多都源於這一類問題。
四個常見陷阱
- 全形與半形:
A與A、(與(、全形空格。 - 零寬字元:零寬空格、零寬連接符,複製網頁內容時常常帶進來。
- Unicode 組合字:同一個字元可能有組合與預組合兩種表示法。
- 不可見控制字元:BOM、方向控制符。
import unicodedata, re
ZERO_WIDTH = re.compile(r'[\u200b-\u200f\ufeff\u2060]')
def normalize(text):
text = unicodedata.normalize('NFKC', text) # 全形轉半形、組合字統一
text = ZERO_WIDTH.sub('', text)
text = re.sub(r'[ \t]+', ' ', text) # 連續空白收斂
return text.strip()NFKC 的副作用
NFKC 很好用,但它也會把 ① 變成 1、把 ㎡ 變成 m2。若你的資料需要保留這些符號的原貌,改用 NFC 只做組合字正規化,另外單獨處理全形轉換。
要在哪一層做
正規化應該在入庫前做一次,並且對查詢字串套用同一套規則。只在查詢端做,索引裡的髒資料還是查不到。
寫一個小工具把字串轉成碼點列印出來。除錯「明明一樣卻不相等」時,這是最快的定位方式。