使用Python中的BOM表读取Unicode文件数据

时间:2012-11-27 18:32:29

标签: python unicode

我正在使用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检测编码,然后在内存中读取文件并尝试打印它。包含BOM的Unicode文件的print语句失败,错误为:

  

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

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

6 个答案:

答案 0 :(得分:50)

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

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)

答案 1 :(得分:28)

没有理由检查BOM是否存在,utf-8-sig为您管理,并且如果BOM不存在则行为与utf-8完全相同:

# 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'

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

答案 2 :(得分:18)

我根据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
    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(raw.startswith(bom) for bom in boms): return enc
    return default

答案 3 :(得分:8)

chardet detects BOM_UTF8 automatically以来

2.3.0 version released on Oct 7, 2014

#!/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's answer

要在将Unicode文本打印到Windows控制台时避免UnicodeEncodeError,请参阅Python, Unicode, and the Windows console

答案 4 :(得分:6)

我发现其他答案过于复杂。有一种更简单的方法,不需要深入到二进制文件I / O的低级习语,不依赖于字符集启发式(chardet)不是Python标准库的一部分,并且不需要很少看到的替代编码签名(utf-8-sig与普通utf-8)似乎没有模拟UTF-16系列。

我发现最简单的方法是处理Unicode中的BOM字符,并让编解码器完成繁重工作。只有一个Unicode byte order mark,因此一旦数据转换为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-8utf-16leutf-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即可。

答案 5 :(得分:1)

@ 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')