用Python 3稳健地解析文件
我有一个日志文件,需要逐行查看,结果发现里面有一些“坏字节”。我收到了这样的错误信息:
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 个回答
在Python 2.7及之前的版本中,字符串(str)实际上是由8位字符组成的数组。所以当你读取一个由8位字符或字节组成的文件时,不管文件的实际编码是什么,你都能顺利读取到字节。简单来说,你可能会用错误的方式来表示这些字节,但程序不会报错。
而在Python 3及之后的版本中,字符串是Unicode字符串(每个字符占16位)。所以当你读取一个文件时,Python需要对文件进行解码,默认情况下,它会使用系统的编码方式——这不一定是UTF-8。在你的情况下,Python似乎假设你的日志文件是UTF-8编码,但实际上你的日志文件并不是UTF-8编码,这就导致了错误。
如果你不确定文件的编码,可以尝试使用ISO-8859-1编码,方法是:
open(sys.argv[1], 'r', encoding='iso-8859-1')
看起来你的文件里没有有效的UTF-8编码(UTF-8是默认的编码方式)。
如果你知道文件使用了什么编码(比如iso-8859-1,这个好像是Python2的默认编码),你可以在打开文件的时候指定这个编码,方法是:
open(sys.argv[1], mode='r', encoding='iso-8859-1')
如果你不知道编码是什么,或者编码根本不合法,你可以以二进制的方式打开文件。
open(sys.argv[1], mode='rb')
这样的话,内容就会以字节的形式被读取,而不是试图把它们当作字符来理解。
你还可以使用 codecs 模块。当你使用 codecs.open() 函数时,可以通过 errors 参数来指定如何处理错误:
codecs.open(filename, mode[, encoding[, errors[, buffering]]])
errors 参数可以是几个不同的关键词,告诉 Python 当它尝试解码一个当前编码下无效的字符时该怎么做。你可能最关心的是 codecs.ignore_errors 或 codecs.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