使用Python 3稳健地解析文件

3
我有一个日志文件需要逐行处理,但是该文件包含一些“错误字节”(bad bytes)。我收到以下类似的错误消息:
UnicodeDecodeError: 'utf-8'编解码器无法解码位置9处的0xb0字节:起始字节无效
我已经将问题简化为一个名为“log.test”的文件,其中包含以下行:
Message: \260

(至少在我的Emacs中是这样显示的。)
我有一个名为“demo_error.py”的文件,内容如下:
import sys
with open(sys.argv[1], 'r') as lf:
    for i, l in enumerate(lf):
        print(i, l.strip())

我随后在命令行中运行:

$ python3 demo_error.py log.test

完整的跟踪回溯如下:

Traceback (most recent call last):
  File "demo_error.py", line 5, in <module>
    for i, l in enumerate(lf):
  File     "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/codecs.py", line 313, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb0 in position 13: invalid start byte

我的猜测是我需要指定一个更通用的编解码器(例如“原始ASCII”)-但我不太确定如何做到这一点。
请注意,这在Python 2.7中并不是真正的问题。
为了使我的观点清晰:我并不介意针对该行获取异常-然后我可以简单地丢弃该行。问题是异常似乎发生在“for”循环本身上,这使得对该特定行的特殊处理变得不可能。
3个回答

4

显然,您的文件不包含有效的UTF-8编码(这是默认编码方式)。 如果您知道所使用的编码方式(例如iso-8859-1,这是Python2的默认方式),则可以在打开时指定。

open(sys.argv[1], mode='r', encoding='iso-8859-1')

如果编码未知或无效,您可以将文件以二进制形式打开。
open(sys.argv[1], mode='rb')

这将使内容可作为字节访问,而不是尝试将其解释为字符。

指定编码就可以了,谢谢。以二进制方式打开也可以,但是后处理会更加棘手,因为大部分行都包含有效文本。(实际上,我认为它应该包含ASCII...但是它已经损坏了。) - ukrutt
0x0b 不是有效的 ASCII 字符。但它是有效的 ISO-8859-1,表示度数符号(°)。 如果文件确实已损坏,则使用二进制更加健壮,因为某些字节可能无效的 ISO-8859-1。 - wonce
Python的默认编码不一定是UTF-8。根据Python 3.4标准库文档默认编码取决于平台(即locale.getpreferredencoding()返回的内容) - Serge Ballesta

4

您还可以使用codecs模块。当您使用codecs.open()函数时,可以使用errors参数指定如何处理错误:

codecs.open(filename, mode[, encoding[, errors[, buffering]]])
errors参数可以是几个不同的关键字,用于指定Python在尝试解码当前编码无效字符时的行为方式。您可能最感兴趣的是codecs.ignore_errorscodecs.replace_errors,分别导致无效字符被忽略或替换为默认字符。

当您知道数据已损坏,即使指定正确的编码也会导致UnicodeDecodeError被引发时,此方法可能是一个不错的选择。

示例:

with codecs.open('file.txt', mode='r', errors='ignore'):
    # ...stuff...
    # Even if there is corrupt data and invalid characters for the default
    # encoding, this open() will still succeed

1
使用“编解码器”的好处是,代码可以与Python 2.x兼容。 - ukrutt

3
在Python <=2.7中,字符串(str)是8位字符的数组。因此,当读取由8位字符或字节组成的文件时,无论实际编码是什么,您都可以获得字节而不会出现任何问题。简单地说,您可能会使用错误的表示来读取它们,但它永远不会抛出任何异常。
在Python >=3中,字符串是Unicode字符串(每个字符16位)。因此,在读取文件时,Python必须对文件进行解码,并默认使用系统编码 - 不一定是UTF-8。在您的情况下,它似乎假定UTF-8编码,而您的日志文件不是UTF-8编码,所以会出现异常。
如果不确定编码,您可以合理地尝试使用ISO-8859-1。
open(sys.argv[1], 'r', encoding='iso-8859-1')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接