【问题标题】:errors=surrogateescape vs errors=replace错误=代理转义 vs 错误=替换
【发布时间】:2019-06-04 09:45:01
【问题描述】:

我正在尝试打开这样的文件:

with open("myfile.txt", encoding="utf-8") as f:

但是myfile.txt 来自我的应用程序的用户。 90% 的情况下,该文件以非 UTF-8 格式出现,这会导致应用程序退出,因为它无法正确读取它。错误类似于'utf-8' codec can't decode byte 0x9c

我在 Google 上搜索了一下,发现一些 Stackoverflow 答案说要像这样打开我的文件:

with open("myfile.txt", encoding="utf-8", errors="surrogateescape") as f:

但据说可以使用其他答案:

with open("myfile.txt", encoding="utf-8", errors="replace") as f:

那么errors="replace"errors="surrogateescape" 有什么区别,哪一个会修复文件中的非UTF-8 字节?

【问题讨论】:

  • 阅读thisthis
  • @Sheldore - 我不想忽略错误,我想替换损坏的字节。这不是重复的。
  • “而且 90% 的情况下,该文件以非 UTF-8 格式出现”——那么您不应该尝试将其读取为 UTF-8。 errors 参数的任何值都无法解决此问题。你只会得到一堆废话。
  • @user2357112 - 我已经做了encoding="utf-8",但同样的问题发生了。
  • 为非 UTF-8 文件指定 encoding="utf-8" 没有意义。您需要指定文件实际编码的编码。

标签: python file encoding utf-8


【解决方案1】:

文档说:

“替换”: 用合适的替换标记替换; Python 将使用官方的 U+FFFD REPLACEMENT CHARACTER 作为内置编解码器进行解码,使用“?”进行编码。在 replace_errors() 中实现。
...
'surrogateescape':解码时,将字节替换为从 U+DC80 到 U+DCFF 的各个代理代码。然后,当编码数据时使用“surrogateescape”错误处理程序时,此代码将转回相同的字节。 (有关更多信息,请参阅 PEP 383。)

这意味着对于replace,任何有问题的字节都将替换为相同的U+FFFD 替换字符,而对于surrogateescape,每个字节都将替换为不同的值。例如,'\xe9' 将替换为 '\udce9''\xe8' 将替换为 '\udce8'

因此,使用替换,您可以获得有效的 unicode 字符,但会丢失文件的原始内容,而使用 surrogateescape,您可以知道原始字节(甚至可以使用.encode(errors='surrogateescape') 完全重建它),但您的 unicode 字符串是不正确,因为它包含原始代理代码。

长话短说:如果原始的违规字节无关紧要并且您只想摆脱错误,replace 是一个不错的选择,如果您需要保留它们以供以后处理,surrogateescape 是路要走。


surrogateescape 有一个非常好的功能,当您的文件主要包含 ascii 字符和一些(重音)非 ascii 字符时。而且您也有用户偶尔使用非 UTF8 编辑器修改文件(或未能声明 UTF8 编码)。在这种情况下,您会以一个文件结尾,该文件主要包含 utf8 数据和一些不同编码的字节,对于非英语西欧语言(如法语、葡萄牙语和西班牙语)的 Windows 用户,通常为 CP1252。在这种情况下,可以构建一个转换表,将代理字符映射到 cp1252 字符集中的等效字符:

# first map all surrogates in the range 0xdc80-0xdcff to codes 0x80-0xff
tab0 = str.maketrans(''.join(range(0xdc80, 0xdd00)),
             ''.join(range(0x80, 0x100)))
# then decode all bytes in the range 0x80-0xff as cp1252, and map the undecoded ones
#  to latin1 (using previous transtable)
t = bytes(range(0x80, 0x100)).decode('cp1252', errors='surrogateescape').translate(tab0)
# finally use above string to build a transtable mapping surrogates in the range 0xdc80-0xdcff
#  to their cp1252 equivalent, or latin1 if byte has no value in cp1252 charset
tab = str.maketrans(''.join(chr(i) for i in range(0xdc80, 0xdd00)), t)

然后您可以解码包含 utf8 和 cp1252 的 mojibake 的文件:

with open("myfile.txt", encoding="utf-8", errors="surrogateescape") as f:
    for line in f:                     # ok utf8 has been decoded here
        line = line.translate(tab)     # and cp1252 bytes are recovered here

我已成功使用该方法多次恢复生成为 utf8 并在 Windows 机器上使用 Excel 编辑过的 csv 文件。

同样的方法可以用于从 ascii 派生的其他字符集

【讨论】:

  • 这一切都是假设输入主要是 UTF-8 并带有一些损坏的垃圾。如果是 UTF-16 或 Shift JIS 之类的,则在指定正确的编码之前,您都无法成功读取文件。
  • but lose the original content of the file 这是什么意思?
  • @user2357112:上面将确保所有 ascii 字符都将被保留,对于 UTF16,所有 ascii 字符都表示为它们自己和 null。因此,如果文件主要包含 ascii,它应该是可解释的。当然,如果它是二进制或东方语言字符集,那就是垃圾。
  • @gabugu:读取文件不会破坏它。但是当你找到一个替换字符时,你就无法再猜测它在文件中的原始字节是什么了。
  • ignore 不会忽略 。简单地冒犯(非 utf8)字符会被默默地丢弃。使用replace,您无法知道它们是什么,但至少知道它们在哪里。
【解决方案2】:

我的问题是该文件包含混合编码类型的行。

解决方法是删除 encoding="utf-8" 并添加 errors="replace".. 所以 open() 行最后会是这样:

with open("myfile.txt", errors="replace") as f:

如果可以检测到文件的编码类型,我会将其添加为encoding 参数,但遗憾的是无法检测到它。

【讨论】:

  • 如果你真的有混合编码,ftfy library 可能会帮助你。
猜你喜欢
  • 1970-01-01
  • 2011-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-14
  • 2016-10-14
  • 1970-01-01
相关资源
最近更新 更多