Подтвердить что ты не робот

Чтение данных файла Unicode с символами спецификации в Python

Я читаю серию файлов исходного кода, используя Python, и запускаю ошибку спецификации Unicode. Здесь мой код:

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, затем читаю файл в памяти и пытаюсь его распечатать. Заявление о печати выходит из строя в файлах Unicode, содержащих спецификацию с ошибкой:

UnicodeEncodeError: кодек charmap не может кодировать символы в позиции 0-2:
    символьные карты в <undefined>

Я предполагаю, что он пытается декодировать спецификацию с использованием набора символов по умолчанию, и он не работает. Как удалить спецификацию из строки, чтобы предотвратить это?

4b9b3361

Ответ 1

Символы BOM должны быть автоматически удалены при декодировании UTF-16, но не UTF-8, если вы явно не используете кодировку 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)

Ответ 2

Я написал отличный детектив на основе спецификации на основе ответа Chewie. Это достаточно в обычном случае, когда данные могут быть либо в известной локальной кодировке, либо в Unicode с спецификацией. (что обычно печатают текстовые редакторы):

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

chardet автоматически обнаруживает BOM_UTF8, поскольку версия 2.3.0 выпущен 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' кодировки, которые оставляют спецификацию в тексте. 'LE', 'BE' следует удалить, чтобы избежать этого, хотя в данный момент легче определить спецификацию, например, как в @ivan_pozdeev answer.

Чтобы избежать UnicodeEncodeError при печати текста Unicode в консоли Windows, см. Python, Unicode и консоль Windows.

Ответ 4

Нет причин проверять, существует ли спецификация или нет, utf-8-sig управляет этим для вас и ведет себя точно как 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'

Ответ 5

Вариант ответа @ivan_pozdeev для строк/исключений (а не файлов). Я имею дело с unicode HTML-контентом, который был добавлен в исключение python (см. 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')