ftfy 实测:已经进库的中文乱码怎么修——MySQL 连接踩坑、多层套娃与 CLI 入口
干过数据清洗的人都见过这一幕:数据库里躺着一片"ä½ å¥½",日志文件里躺着"锛屼笘鐣"。排查一圈,根因在入口:连接没指定编码、文件另存为换了格式、上游系统按错误编码解了字节。字节流没错,错在解读它的那个字符集。
字节已经解错了,字符串已经落库了,还能修吗?能,条件是解读过程没丢信息。ftfy(Robyn Speer 开发,Apache-2.0)就是把这件事做到极致的库:给定一段乱码文本,推断它原本的字节含义,还原出正确字符串。我实测了 6.3.1,这篇记录能修的、不能修的,和一个 CLI 的坑。
一、它解决的到底是什么问题
乱码的本质:一段 UTF-8 字节,被某个环节按 Latin-1(或 GBK、CP1252)解成了字符。字节本身还在字符串里躺着,只是被翻译错了。由于 UTF-8 的字节结构自描述性强,"翻译错了"这件事可以被反向识别:一段以 Ã、Â、ä½ 开头的文本,几乎不可能是正常文本,只可能是误判的产物。ftfy 的核心就是这个反向推断,外加弯引号、HTML 实体、不间断空格等一堆现实世界里叠加上来的清理。
二、安装
pip install ftfy
实测版本 6.3.1,纯 Python,仅依赖 wcwidth,装在 Python 3.14 上直接可用。
三、能修的:四种常见乱码
场景一:MySQL 连接踩的坑。 库表是 UTF-8,连接按 Latin-1 读,中文全变"ä½ å¥½":
>>> import ftfy
>>> s = '你好'.encode('utf-8').decode('latin-1')
>>> s
'ä½\xa0好'
>>> ftfy.fix_text(s)
'你好'
这是最常见、也最无损的一种:Latin-1 是单字节编码,任何字节序列都能无损解码,所以字节信息完整,修起来一击必中。
场景二:双重编码。 乱码又被人按 UTF-8 存了一遍,套娃:
>>> s = '你好'.encode('utf-8').decode('latin-1').encode('utf-8').decode('latin-1')
>>> s
'ä½Â\xa0好'
>>> ftfy.fix_text(s)
'你好'
官方文档管这个叫同时修多层乱码。实测两层直接还原。
场景三:乱码上面又叠了弯引号。 Word 类编辑器"智能引号"改过的文本:
>>> s = 'l’humanité'
>>> ftfy.fix_text(s)
"l'humanité"
弯引号把字节结构又搅乱一层,ftfy 的处理顺序是先 uncurl 再解编码。
场景四:多层套娃。
>>> s = 'The Mona Lisa doesn’t have eyebrows.'
>>> ftfy.fix_text(s)
"The Mona Lisa doesn't have eyebrows."
四、不能修的:replace 之后,神仙没辙
这是本篇最重要的实测结论。很多人修乱码的第一步就错了:用 errors='replace' 把乱码"洗干净"再处理。
>>> s = '你好,世界'.encode('utf-8').decode('gbk', errors='replace')
>>> s
'浣犲ソ锛屼笘鐣\ufffd'
>>> ftfy.fix_text(s)
'浣犲ソ锛屼笘鐣\ufffd' # 原样返回,修不动
原因:errors='replace' 把解不出的字节替换成了 U+FFFD(替换符),原始字节信息在这一步就被丢掉了。ftfy 再聪明,也没法从一堆替换符里猜回原来的字节。同理,UTF-8 字节硬按 GBK 解,部分字节序列根本解不出来:
>>> '你好,世界'.encode('utf-8').decode('gbk')
UnicodeDecodeError: 'gbk' codec can't decode byte 0x8c in position 14: incomplete multibyte sequence
走到这一步同样没救。所以顺序很重要:修复要在任何清洗之前,且千万别对乱码文本做有损转换。入库前指定对编码,比入库后任何修复工具都可靠。
五、不误伤:这是它的设计目标
修乱码的工具最怕什么?把正常文本改坏。ftfy 把"避免误报"当硬指标。实测三条正常文本,全部原样返回:
>>> ftfy.fix_text('已经正确的文本')
'已经正确的文本'
>>> ftfy.fix_text('Hello, world!')
'Hello, world!'
>>> ftfy.fix_text('价格是 ¥199.00')
'价格是 ¥199.00'
哪怕第三句里的 ¥ 单独看也"像"乱码特征,组合起来文本是自洽的,它就不动。批量清洗整个库之前,这个特性比准确率更重要。
六、fix_encoding 和 fix_text,别用混
两个入口的语义不同,用错了要么漏修要么修过头:
>>> s = 'PÉREZ café'
>>> ftfy.fix_encoding(s)
'PÉREZ café' # 只修编码,HTML 实体不动
>>> ftfy.fix_text(s)
'PÉREZ café' # 连 HTML 实体一起解
只想要字节还原,用 fix_encoding;要的是"把文本弄干净"(实体、弯引号、怪异空格一起收拾),用 fix_text。日常入库清洗用后者,做字节级比对场景用前者。
七、CLI 的一个坑:文档入口已失效
官方文档写的命令行用法是 python -m ftfy,实测 6.3.1:
$ python -m ftfy input.csv
No module named ftfy.__main__; 'ftfy' is a package and cannot be directly executed
模块入口已经没了。可用的有两个:python -m ftfy.cli,或者 pip 装出来的 ftfy 命令(pip 用户态安装的可执行文件目录)。实测用 python -m ftfy.cli 批量修一个 CSV:
$ python -m ftfy.cli ftfy_test.csv > fixed.csv
$ cat fixed.csv
name,note
1,你好
2,正常文本无需修复
第一行是 latin-1 误读的中文,被修回;第二行正常文本原样保留。批处理场景够用了。
八、适合谁,边界在哪
适合:历史数据清洗(老库、老系统导出的错码文本);NLP 语料预处理;日志、工单、爬虫文本的兜底清洗;入口修不了时的止损。
不适合:还没发生乱码的场景(在入口用对编码,比事后修便宜一个数量级);已经过有损转换的文本(U+FFFD 之后无法还原,第四节实测);繁简转换、字形规范化这类它不碰的活。
最后一个忠告来自实测:把它放进清洗管线的第一站,且这一站只做修复、不做任何有损转换。修乱码是补救,不是常规操作。
参考与署名:本文实测项目为 ftfy(Apache-2.0,作者 Robyn Speer,实测版本 6.3.1);文中全部代码与输出均为 Python 3.14 本机实跑记录;未使用项目宣传图。
(完)
- 点赞
- 收藏
- 关注作者
评论(0)