Python读取文件时发生UnicodeDecodeError,如何忽略错误并跳到下一行?

53 投票
1 回答
93190 浏览
提问于 2025-04-18 12:23

我需要把一个文本文件读入到Python中。这个文件的编码是:

file -bi test.csv 
text/plain; charset=us-ascii

这个文件是第三方提供的,我每天都会收到一个新的,所以我不想去改动它。文件里有一些非ASCII字符,比如Ö。我需要用Python读取这些行,如果有行包含非ASCII字符,我可以选择忽略它。

我的问题是,当我在Python中读取文件时,一旦遇到包含非ASCII字符的行,就会出现UnicodeDecodeError错误,这样我就无法继续读取文件的其余部分。

有没有办法避免这个问题?如果我尝试这样做:

fileHandle = codecs.open("test.csv", encoding='utf-8');
try:
    for line in companiesFile:
        print(line, end="");
except UnicodeDecodeError:
    pass;

那么当遇到错误时,循环就会结束,我就无法读取文件的剩余部分。我想跳过导致错误的那一行,继续往下读。如果可以的话,我希望不对输入文件做任何更改。

有没有什么办法可以做到这一点?非常感谢。

1 个回答

104

你的文件似乎没有使用UTF-8编码。打开文件时使用正确的编码方式是很重要的。

你可以通过 open() 函数告诉它如何处理解码错误,使用 errors 这个参数:

errors 是一个可选的字符串,用来指定如何处理编码和解码错误——在二进制模式下不能使用。这里有几种标准的错误处理方式,另外任何通过 codecs.register_error() 注册的错误处理名称也可以用。标准的名称包括:

  • 'strict':如果有编码错误,会抛出一个 ValueError 异常。默认值 None 的效果和这个一样。
  • 'ignore':忽略错误。注意,忽略编码错误可能会导致数据丢失。
  • 'replace':在出现格式错误的数据地方插入一个替代标记(比如 '?')。
  • 'surrogateescape':将任何错误的字节表示为Unicode私人使用区的代码点,范围是 U+DC80 到 U+DCFF。当使用 surrogateescape 错误处理器写入数据时,这些私人代码点会被转换回原来的字节。这在处理未知编码的文件时很有用。
  • 'xmlcharrefreplace':仅在写入文件时支持。编码不支持的字符会被替换为相应的XML字符引用 &#nnn;
  • 'backslashreplace':同样仅在写入时支持,用Python的反斜杠转义序列替换不支持的字符。

如果你用除了 'strict' 以外的方式(比如 'ignore''replace' 等)打开文件,就可以在不抛出异常的情况下读取文件。

需要注意的是,解码是按缓冲的数据块进行的,而不是按文本行进行的。如果你必须逐行检测错误,可以使用 surrogateescape 处理器,并测试每一行读取的代码点是否在替代范围内:

import re

_surrogates = re.compile(r"[\uDC80-\uDCFF]")

def detect_decoding_errors_line(l, _s=_surrogates.finditer):
    """Return decoding errors in a line of text

    Works with text lines decoded with the surrogateescape
    error handler.

    Returns a list of (pos, byte) tuples

    """
    # DC80 - DCFF encode bad bytes 80-FF
    return [(m.start(), bytes([ord(m.group()) - 0xDC00]))
            for m in _s(l)]

例如:

with open("test.csv", encoding="utf8", errors="surrogateescape") as f:
    for i, line in enumerate(f, 1):
        errors = detect_decoding_errors_line(line)
        if errors:
            print(f"Found errors on line {i}:")
            for (col, b) in errors:
                print(f" {col + 1:2d}: {b[0]:02x}")

要考虑的是,并不是所有的解码错误都能优雅地恢复。虽然UTF-8设计得比较健壮,可以应对小错误,但其他多字节编码,比如UTF-16和UTF-32,无法处理丢失或多余的字节,这会影响行分隔符的准确定位。上述方法可能会导致文件的其余部分被视为一长行。如果文件足够大,这可能会导致 MemoryError 异常,尤其是当这“行”非常大的时候。

撰写回答