网页文字里藏着一堆看不见或者看起来一样的字符:不间断空格、零宽空格、全角数字和逗号、组合重音。它们让字符串比较、去重和价格解析静默地出错。这篇逐类用 Python 验证,并给出一个清洗函数。
字符 类别 isspace strip 去掉 split 当分隔 \s 匹配 显示 普通空格 Zs True 是 是 是 U+0020 制表符 Cc True 是 是 是 U+0009 换行 Cc True 是 是 是 U+000A 不间断空格 nbsp Zs True 是 是 是 U+00A0 全角空格(表意文字空格) Zs True 是 是 是 U+3000 细空格 U+2009 Zs True 是 是 是 U+2009 窄不间断空格 U+202F Zs True 是 是 是 U+202F 零宽空格 U+200B Cf False 否 否 否 U+200B 零宽不连字 U+200C Cf False 否 否 否 U+200C 零宽连字 U+200D Cf False 否 否 否 U+200D 字节顺序标记/零宽不换行 U+FEFF Cf False 否 否 否 U+FEFF 软连字符 U+00AD Cf False 否 否 否 U+00AD 左到右标记 U+200E Cf False 否 否 否 U+200E 单词连接符 U+2060 Cf False 否 否 否 U+2060 例:'Price:\xa0¥100\u200b \u3000 ok\ufeff'.strip() = 'Price:\xa0¥100\u200b \u3000 ok\ufeff';.split() = ['Price:', '¥100\u200b', 'ok\ufeff']——零宽空格和 BOM 留在了结果里,看不见但存在
不间断空格 nbsp、全角空格 U+3000、细空格都被 str.strip()、str.split()、正则 \s 当作空白。零宽空格、零宽连字、BOM、软连字符、方向标记属于 Cf 类,isspace() 为 False,留在文本里——比较、去重、关键词匹配会莫名其妙地失败。
'123ABC,.' → NFKC '123ABC,.' (有变化) 'アイウ(ハンカク)' → NFKC 'アイウ(ハンカク)' (有变化) '㈱テスト' → NFKC '(株)テスト' (有变化) '①②③' → NFKC '123' (有变化) 'x²+y³' → NFKC 'x2+y3' (有变化) 'file flow' → NFKC 'file flow' (有变化) '™ ® ©' → NFKC 'TM ® ©' (有变化) '12,345.67円' → NFKC '12,345.67円' (有变化) '“引号”‘单引号’' → NFKC '“引号”‘单引号’' (不变) '…' → NFKC '...' (有变化) '——' → NFKC '——' (不变) '~' → NFKC '~' (有变化) '‐ – — −' → NFKC '‐ – — −' (不变) 注意:NFKC 把 "x²+y³" 变成 "x2+y3"(数学含义变了)、把 "①" 变成 "1"、把 "fi" 变成 "fi"、把 "™" 变成 "TM";中文引号、省略号、破折号等有的变有的不变——不要无脑对全文做 NFKC 中文标点 NFKC 之后:,→, 。→。 !→! ?→? :→: ;→; (→( )→) 【→【 】→】 、→、
NFKC 把全角数字字母和逗号变成半角(12,345.67円 → 12,345.67円),但同时改变含义:x² 变 x2、① 变 1、™ 变 TM、… 变 ...。只对需要的字段做,不要对全文做。
'café' 与 'café':显示都是 café;==:False;len:4 和 5;NFC 之后相等:True
韩文 "한글":NFC len=2,NFD len=6,==:False,NFC 之后相等:True
去重集合:{a, b} 的大小 = 2;规范化后 = 1带重音的字符可以是一个码点,也可以是“字母加组合符号”;显示一样,== 为 False。去重或比较之前统一成 NFC。
'123' int() = 123 float() = 123.0 NFKC 后 '123' '12.5' int() = ValueError float() = ValueError NFKC 后 '12.5' '١٢٣' int() = 123 float() = 123.0 NFKC 后 '١٢٣' '123' int() = 123 float() = 123.0 NFKC 后 '123' '1,234.56' int() = ValueError float() = ValueError NFKC 后 '1,234.56' '1,234.56' int() = ValueError float() = ValueError NFKC 后 '1,234.56' '1 234,56' int() = ValueError float() = ValueError NFKC 后 '1 234,56' '¥100' int() = ValueError float() = ValueError NFKC 后 '¥100' '¥100' int() = ValueError float() = ValueError NFKC 后 '¥100' '100円' int() = ValueError float() = ValueError NFKC 后 '100円' '12.5万' int() = ValueError float() = ValueError NFKC 后 '12.5万'
Python 的 int() 接受各种 Unicode 数字(全角、阿拉伯-印度),但不接受全角小数点、千分位逗号、货币符号和“万”。NFKC 能转全角,逗号与符号还要自己处理。
原始 HTML:'<div class="p"> Price: <b>¥12,345</b><br>half\u200bwidth アイウ\n next​line ok <span>a</span><span>b</span></div>'
text_content():' Price:\xa0¥12,345half\u200bwidth アイウ\n next\u200bline\u2003ok ab'
"".join(itertext()):' Price:\xa0¥12,345half\u200bwidth アイウ\n next\u200bline\u2003ok ab'
xpath string():' Price:\xa0¥12,345half\u200bwidth アイウ\n next\u200bline\u2003ok ab'
" ".join(itertext()):' Price:\xa0 ¥12,345 half\u200bwidth アイウ\n next\u200bline\u2003ok a b'
clean(text_content()):'Price: ¥12,345halfwidth アイウ nextline ok ab'
clean(" ".join(itertext())):'Price: ¥12,345 halfwidth アイウ nextline ok a b'
从清洗后的文本里取价格:12,345 → 12345
不清洗直接用同一个正则:匹配到 '12'——Python 的 \d 匹配全角数字,但全角逗号 "," 不在 [\d,] 里,价格被截断;int('12') = 12,静默得到了错误的 12 而不是 12345text_content()、itertext()、string() 都保留不间断空格、零宽字符和全角字符,<br> 不产生分隔。清洗函数的步骤:NFKC(只针对要做的字段)、去掉零宽/软连字符/方向标记、把各种空白折叠成一个空格。不清洗时用 \d 的正则会把全角价格截成 12——这种错误没有任何异常。