TT Lab
开始
学习 学习路径 课程

处理客户数据

姓名列全是方块 — 动手做判定工具

在 TT Lab 中继续学习

目标

构建工具 enc_probe.py,不靠猜测,而是判定客户发来的文件的编码。从 BOM 和能否以 UTF-8 解码开始,往下走到候选编码和样本核对,还原双重编码,找出无法恢复的文件,并用自己的数据证明规范化和看不见的字符如何破坏了比较。

为什么重要

文本文件里没有写明编码。所以“是什么编码”这个问题,始终只能用流程来回答。在编辑器菜单里逐个点的办法,只有文件只有三个时才管用,而且最要命的是,对不对只能靠眼睛判断。 判定结束后,文件会分成三类:能正常读取的;曾被错误读取并固定下来、但可以还原的;以及字符被弄成一团、无法得知原来字节的。在最后一类上花时间不是努力,而是浪费:这个文件必须重新获取。能用代码做出这种区分,才能说清楚该向客户请求什么。 还有一种情况:字符看起来完好,却对不上。同一个韩文字符,如果分别以预组合形式和字母分解形式保存,屏幕上相同,字节却不同。看不见的 NBSP 和零宽空格也是如此。连接结果是 0 条,而原因在屏幕上看不见,这样的局面就出在这里。 评分器不会相信你的文字。它在临时目录里摆好评分器生成的文件,真实运行你的工具,并核对判定。姓名和行数每次运行都会变。

步骤

  1. 创建并运行 /root/enc/gen_inbox.py,在 /root/enc/inbox 下生成七个文件。同样的 24 人名单会以七种状态保存。
  2. 在 /root/enc/enc_probe.py 中实现 detect,让它按 BOM、能否以 UTF-8 解码、候选编码、样本核对的顺序判定编码。
  3. 增加 decode,让它用判定出的编码读取并以 UTF-8 输出到标准输出,并把整个接收目录以相同的名称统一保存到 /root/enc/norm 下。
  4. 增加 repair 来还原双重编码,并让 detect 对这样的文件回答 mojibake 为 true、verdict 为 mojibake。
  5. 给 detect 增加 lossy,把留有 U+FFFD 的文件的 verdict 设为 lost,并把对接收目录的判定写入 /root/enc/lost.json。
  6. 增加 keys,让它把姓名列经 NFC 规范化后的比较用键输出,并把 NFD 版和 UTF-8 版的姓名在规范化前后分别能对上多少条,写入 /root/enc/join_report.json。
  7. 增加 scan,让它按码点统计看不见的字符,并让 keys 把它们清除,然后写入 /root/enc/invisible_report.json。
  8. 一次性处理接收目录中的七个文件,生成 /root/enc/enc_report.json 和 /root/enc/enc_report.md。

参考

生成七种状态的同一份名单

创建并运行 /root/enc/gen_inbox.py,在 /root/enc/inbox 下生成七个文件。内容是同样的 24 人,只有保存下来的字节不同。

先创建 /root/enc,然后在其中用 python3 写文件。做法只是先生成文本,再用不同的 encode 保存成字节。打开文件时,要用二进制模式("wb")而不是文本模式,这样才不会被编码两次。

不靠猜测,而是判定

创建 /root/enc/enc_probe.py,让 detect <파일>(占位符为文件)按 BOM、能否以 UTF-8 解码、候选编码、样本核对的顺序判定,并把结果以一整块 JSON 输出。响应中必须包含 path、bom、utf8_ok、encoding、verdict。

顺序就是判定。先看开头三个字节是不是 EF BB BF,不是就尝试严格的 UTF-8 解码,仍然失败就用 CP949 读一遍。最后用完成型韩文音节的比例来确认读出来的字符是否说得通。光凭“解码没有报错”是不够的。

按判定结果读取并统一成 UTF-8

增加 decode <파일>(占位符为文件),让它把用判定出的编码读取的正文,以 UTF-8 输出到标准输出(BOM 要去掉再输出)。并把结果以相同的名称保存到 /root/enc/norm 下,作为统一后的版本。

直接复用判定结果即可。带 BOM 的文件要用 utf-8-sig 读取,第一个列名里才不会留下看不见的字符。原始文件保持原样,统一后的版本放到另一个目录:判定一旦出错,必须有可以回去的地方。

还原曾被错误读取并固定下来的字符

增加 repair <파일>(占位符为文件),让它还原双重编码并输出到标准输出,同时给 detect 的响应增加 mojibake,把这样的文件的 verdict 设为 mojibake。无法还原时,退出码为 3。

把 UTF-8 字节按 latin-1 读,一个韩文字符会变成三个字母。字节一个也没有丢,所以沿着同样的路还原回去即可。判定为已还原的依据,是还原后一侧的完成型韩文音节比例变高了:缺了这项检查,连完好的文件也会被动到。

区分能恢复的和已经丢失的

给 detect 的响应增加 lossy,把留有 U+FFFD 的文件的 verdict 设为 lost。并遍历接收目录,在 /root/enc/lost.json 中写入 lost、recoverable 两个列表和 reason。

错误读取的一方过于宽松时,读不出来的字节会被揉成一个替换字符。多个不同的字节变成了同一个字符,所以没有可以还原的信息留下。这不是努力的问题,而是判定:这个文件必须重新获取。lost 和 recoverable 是只放文件名的列表。

看起来一样却对不上的姓名

增加 keys <파일>(占位符为文件),让它把第二列(姓名)经 NFC 规范化后,作为比较用键数组输出。并在 /root/enc/join_report.json 中以 rows、matched_raw、matched_nfc 写明,NFD 版与 UTF-8 版的姓名在规范化前能对上多少条、规范化后能对上多少条。

一个韩文字符既可以保存成一个完整的音节,也可以保存成三个字母。屏幕上看起来相同,却是不同的字符串。规范化是为了比较,而不是为了修改原始文件:原始文件保持原样。matched_raw 是未经规范化的 NFD 姓名,原样出现在 UTF-8 版姓名集合中的条数。

看不见的字符混进了键

增加 scan <파일>(占位符为文件),让它按码点统计看不见的字符,并让 keys 把 NBSP 换成普通空格、清除零宽字符。结果以 counts、rows_affected、keys_fixed 写入 /root/enc/invisible_report.json。

零宽字符用 strip() 是去不掉的。要把需要去掉的字符确定成码点清单,再逐个字符扫描过滤。rows_affected 是含有至少一个看不见的字符的行数,keys_fixed 是整理之后与 UTF-8 版姓名对上的键的数量。

用一份接收目录报告说明情况

一次性处理接收目录中的七个文件,在 /root/enc/enc_report.json 中写入 files、ok、mojibake、lost,并在 /root/enc/enc_report.md 中分 ## 무엇을 받았나 ## 어떻게 판정했나 ## 되살린 것과 잃은 것 ## 보내는 쪽에 요청할 것 四节书写(韩文标题,依次意为“收到了什么”“如何判定”“已恢复的与已丢失的”“需要向发送方请求的内容”)。

files 是以文件名为键的对象,其中包含 encoding 和 verdict。报告里要用数字写明已恢复的文件数和已丢失的文件数:这份文档的目的,就是让客户知道需要重新发送什么。调用前面步骤中做好的判定函数即可。