58

我正在使用 Python 阅读一系列源代码文件并遇到 unicode BOM 错误。这是我的代码:

bytes = min(32, os.path.getsize(filename))
raw = open(filename, 'rb').read(bytes)
result = chardet.detect(raw)
encoding = result['encoding']

infile = open(filename, mode, encoding=encoding)
data = infile.read()
infile.close()

print(data)

如您所见,我正在使用 检测编码chardet,然后读取内存中的文件并尝试打印它。print 语句在包含 BOM 的 Unicode 文件上失败,并出现以下错误:

UnicodeEncodeError:“charmap”编解码器无法对位置 0-2 中的字符进行编码:
字符映射到 <undefined>

我猜它正在尝试使用默认字符集解码 BOM,但它失败了。如何从字符串中删除 BOM 以防止这种情况发生?

4

7 回答 7

86

没有理由检查 BOM 是否存在utf-8-sig,为您管理它并且行为utf-8与 BOM 不存在完全一样:

# Standard UTF-8 without BOM
>>> b'hello'.decode('utf-8')
'hello'
>>> b'hello'.decode('utf-8-sig')
'hello'

# BOM encoded UTF-8
>>> b'\xef\xbb\xbfhello'.decode('utf-8')
'\ufeffhello'
>>> b'\xef\xbb\xbfhello'.decode('utf-8-sig')
'hello'

在上面的示例中,您可以看到utf-8-sig无论 BOM 是否存在,都可以正确解码给定的字符串。如果您认为您正在阅读的文件中可能存在 BOM 字符的可能性很小,请使用utf-8-sig而不用担心它

于 2017-06-15T17:50:28.097 回答
62

解码 UTF-16 而非 UTF-8 时应自动去除 BOM 字符,除非您明确使用utf-8-sig编码。你可以尝试这样的事情:

import io
import chardet
import codecs

bytes = min(32, os.path.getsize(filename))
raw = open(filename, 'rb').read(bytes)

if raw.startswith(codecs.BOM_UTF8):
    encoding = 'utf-8-sig'
else:
    result = chardet.detect(raw)
    encoding = result['encoding']

infile = io.open(filename, mode, encoding=encoding)
data = infile.read()
infile.close()

print(data)
于 2012-11-27T19:16:19.647 回答
28

我根据 Chewie 的回答编写了一个漂亮的基于 BOM 的检测器。在数据可以是已知的本地编码或带有 BOM 的 Unicode(这是文本编辑器通常产生的)的常见用例中就足够了。更重要的是,不像chardet,它不做任何随机猜测,所以它给出了可预测的结果:

def detect_by_bom(path, default):
    with open(path, 'rb') as f:
        raw = f.read(4)    # will read less if the file is smaller
    # BOM_UTF32_LE's start is equal to BOM_UTF16_LE so need to try the former first
    for enc, boms in \
            ('utf-8-sig', (codecs.BOM_UTF8,)), \
            ('utf-32', (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE)), \
            ('utf-16', (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE)):
        if any(raw.startswith(bom) for bom in boms):
            return enc
    return default
于 2014-06-23T16:14:18.873 回答
11

chardet2014 年 10 月 7 日发布的 2.3.0 版本开始自动检测 BOM_UTF8

#!/usr/bin/env python
import chardet # $ pip install chardet

# detect file encoding
with open(filename, 'rb') as file:
    raw = file.read(32) # at most 32 bytes are returned
    encoding = chardet.detect(raw)['encoding']

with open(filename, encoding=encoding) as file:
    text = file.read()
print(text)

注意:chardet可能会返回'UTF-XXLE','UTF-XXBE'将 BOM 留在文本中的编码。'LE','BE'应该被剥离以避免它 - 尽管此时更容易自己检测 BOM,例如,如@ivan_pozdeev 的回答

要避免UnicodeEncodeError将 Unicode 文本打印到 Windows 控制台,请参阅Python、Unicode 和 Windows 控制台

于 2015-09-25T04:18:07.910 回答
9

我发现其他答案过于复杂。有一种更简单的方法,不需要下降到二进制文件 I/O 的低级习惯用法,不依赖chardet不属于 Python 标准库的字符集启发式 ( ),也不需要一种罕见的替代编码签名(utf-8-sig与 common 相比utf-8),似乎在 UTF-16 系列中没有类似物。

我发现的最简单的方法是处理 Unicode 中的 BOM 字符,并让编解码器完成繁重的工作。只有一个 Unicode字节顺序标记,因此一旦将数据转换为 Unicode 字符,确定它是否存在和/或添加/删除它很容易。要读取具有可能 BOM 的文件:

BOM = '\ufeff'
with open(filepath, mode='r', encoding='utf-8') as f:
    text = f.read()
    if text.startswith(BOM):
        text = text[1:]

这适用于所有有趣的 UTF 编解码器(例如utf-8, utf-16le, utf-16be, ...),不需要额外的模块,也不需要进入二进制文件处理或特定codec常量。

要编写 BOM:

text_with_BOM = text if text.startswith(BOM) else BOM + text
with open(filepath, mode='w', encoding='utf-16be') as f:
    f.write(text_with_BOM)

这适用于任何编码。UTF-16 大端只是一个例子。

顺便说一句,这不是解雇chardet。当您不知道文件使用什么编码时,它会有所帮助。添加/删除 BOM 不需要它。

于 2018-04-29T23:41:55.087 回答
0

@ivan_pozdeev 对字符串/异常(而不是文件)的回答的变体。我正在处理填充在 python 异常中的 unicode HTML 内容(请参阅http://bugs.python.org/issue2517

def detect_encoding(bytes_str):
  for enc, boms in \
      ('utf-8-sig',(codecs.BOM_UTF8,)),\
      ('utf-16',(codecs.BOM_UTF16_LE,codecs.BOM_UTF16_BE)),\
      ('utf-32',(codecs.BOM_UTF32_LE,codecs.BOM_UTF32_BE)):
    if (any(bytes_str.startswith(bom) for bom in boms): return enc
  return 'utf-8' # default

def safe_exc_to_str(exc):
  try:
    return str(exc)
  except UnicodeEncodeError:
    return unicode(exc).encode(detect_encoding(exc.content))

或者,这个更简单的代码能够毫不费力地删除非 ascii 字符:

def just_ascii(str):
  return unicode(str).encode('ascii', 'ignore')
于 2015-09-24T22:28:56.617 回答
0

如果您想编辑文件,您将想知道使用了哪个 BOM。此版本的@ivan_pozdeev 答案返回编码和可选 BOM:

def encoding_by_bom(path, default='utf-8') -> Tuple[str, Optional[bytes]]:
    """Adapted from https://stackoverflow.com/questions/13590749/reading-unicode-file-data-with-bom-chars-in-python/24370596#24370596 """

    with open(path, 'rb') as f:
        raw = f.read(4)    # will read less if the file is smaller
    # BOM_UTF32_LE's start is equal to BOM_UTF16_LE so need to try the former first
    for enc, boms in \
            ('utf-8-sig', (codecs.BOM_UTF8,)), \
            ('utf-32', (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE)), \
            ('utf-16', (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE)):
        for bom in boms:
            if raw.startswith(bom):
                return enc, bom
    return default, None

于 2021-08-16T11:12:20.653 回答