Преобразование UTF-8 с спецификацией в UTF-8 без спецификации в Python

Два вопроса здесь. У меня есть набор файлов, которые обычно UTF-8 с спецификацией. Я хотел бы преобразовать их (в идеале на месте) в UTF-8 без спецификации. Это похоже на codecs.StreamRecoder(stream, encode, decode, Reader, Writer, errors) бы справиться с этим. Но я не вижу хороших примеров использования. Будет ли это лучший способ справиться с этим?

source files:
Tue Jan 17$ file brh-m-157.json 
brh-m-157.json: UTF-8 Unicode (with BOM) text

Кроме того, было бы идеально, если бы мы могли обрабатывать различные входные кодировки без явного знания (видели ASCII и UTF-16). Кажется, что все это должно быть осуществимо. Есть ли решение, которое может принимать любую известную кодировку и вывод Python как UTF-8 без спецификации?

изменить 1 предложенный sol'n снизу (спасибо!)

fp = open('brh-m-157.json','rw')
s = fp.read()
u = s.decode('utf-8-sig')
s = u.encode('utf-8')
print fp.encoding  
fp.write(s)

Это дает мне следующую ошибку:

IOError: [Errno 9] Bad file descriptor

Newsflash

В комментариях мне говорят, что ошибка в том, что я открываю файл с режимом "rw" вместо "r +" / "r + b", поэтому я должен в конечном итоге отредактировать свой вопрос и удалить решенную часть.

7 ответов

Решение

Просто используйте кодек "utf-8-sig":

fp = open("file.txt")
s = fp.read()
u = s.decode("utf-8-sig")

Это дает вам unicode Строка без спецификации. Вы можете использовать

s = u.encode("utf-8")

чтобы вернуть нормальную строку в кодировке UTF-8 обратно в s, Если ваши файлы большие, вам следует избегать их чтения в память. BOM - это просто три байта в начале файла, так что вы можете использовать этот код для удаления их из файла:

import os, sys, codecs

BUFSIZE = 4096
BOMLEN = len(codecs.BOM_UTF8)

path = sys.argv[1]
with open(path, "r+b") as fp:
    chunk = fp.read(BUFSIZE)
    if chunk.startswith(codecs.BOM_UTF8):
        i = 0
        chunk = chunk[BOMLEN:]
        while chunk:
            fp.seek(i)
            fp.write(chunk)
            i += len(chunk)
            fp.seek(BOMLEN, os.SEEK_CUR)
            chunk = fp.read(BUFSIZE)
        fp.seek(-BOMLEN, os.SEEK_CUR)
        fp.truncate()

Он открывает файл, читает фрагмент и записывает его в файл на 3 байта раньше, чем там, где он его прочитал. Файл переписан на месте. Как более простое решение - записать более короткий файл в новый файл, например , ответ на новый запрос. Это было бы проще, но за короткий промежуток времени удвоить дисковое пространство.

Что касается угадывания кодировки, то вы можете просто перебрать кодировку от самой к наименее определенной:

def decode(s):
    for encoding in "utf-8-sig", "utf-16":
        try:
            return s.decode(encoding)
        except UnicodeDecodeError:
            continue
    return s.decode("latin-1") # will always work

Файл в кодировке UTF-16 не будет декодироваться как UTF-8, поэтому сначала мы попробуем использовать UTF-8. Если это не удастся, то мы попробуем с UTF-16. Наконец, мы используем Latin-1 - это всегда будет работать, поскольку все 256 байтов являются допустимыми значениями в Latin-1. Вы можете захотеть вернуться None вместо этого в данном случае, поскольку это действительно запасной вариант, и ваш код может захотеть обработать это более осторожно (если это возможно).

В Python 3 это довольно просто: прочитать файл и переписать его utf-8 кодирование:

s = open(bom_file, mode='r', encoding='utf-8-sig').read()
open(bom_file, mode='w', encoding='utf-8').write(s)
import codecs
import shutil
import sys

s = sys.stdin.read(3)
if s != codecs.BOM_UTF8:
    sys.stdout.write(s)

shutil.copyfileobj(sys.stdin, sys.stdout)

Я нашел этот вопрос, потому что возникли проблемы с configparser.ConfigParser().read(fp) при открытии файлов с заголовком UTF8 BOM.

Для тех, кто ищет решение для удаления заголовка, чтобы ConfigPhaser мог открыть файл конфигурации вместо сообщения об ошибке:File contains no section headersПожалуйста, откройте файл, как показано ниже:

        configparser.ConfigParser().read(config_file_path, encoding="utf-8-sig")

Это может сэкономить массу усилий, сделав ненужным удаление заголовка спецификации файла.

(Я знаю, это звучит несвязанно, но, надеюсь, это может помочь людям, борющимся как я.)

Это моя реализация для преобразования любого вида кодирования в UTF-8 без спецификации и замены оконных рамок универсальным форматом:

def utf8_converter(file_path, universal_endline=True):
    '''
    Convert any type of file to UTF-8 without BOM
    and using universal endline by default.

    Parameters
    ----------
    file_path : string, file path.
    universal_endline : boolean (True),
                        by default convert endlines to universal format.
    '''

    # Fix file path
    file_path = os.path.realpath(os.path.expanduser(file_path))

    # Read from file
    file_open = open(file_path)
    raw = file_open.read()
    file_open.close()

    # Decode
    raw = raw.decode(chardet.detect(raw)['encoding'])
    # Remove windows end line
    if universal_endline:
        raw = raw.replace('\r\n', '\n')
    # Encode to UTF-8
    raw = raw.encode('utf8')
    # Remove BOM
    if raw.startswith(codecs.BOM_UTF8):
        raw = raw.replace(codecs.BOM_UTF8, '', 1)

    # Write to file
    file_open = open(file_path, 'w')
    file_open.write(raw)
    file_open.close()
    return 0

Вы можете использовать кодеки.

import codecs
with open("test.txt",'r') as filehandle:
    content = filehandle.read()
if content[:3] == codecs.BOM_UTF8:
    content = content[3:]
print content.decode("utf-8")

В python3 вы должны добавитьencoding='utf-8-sig':

      with open(file_name, mode='a', encoding='utf-8-sig') as csvfile:
    csvfile.writelines(rows)

вот и все.

Другие вопросы по тегам