Преобразование UTF-8 с спецификацией в UTF-8 без спецификации в Python - PullRequest
62 голосов
/ 17 января 2012

Два вопроса здесь.У меня есть набор файлов, которые обычно 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 без спецификации?

edit 1 предлагаемый soln ниже (спасибо!)

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 ', поэтому я должен в конечном итоге отредактировать свой вопрос и удалить решенную часть.

Ответы [ 6 ]

98 голосов
/ 17 января 2012

Просто используйте кодек "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 байта раньше, чем он прочитал.Файл переписан на месте.Как более простое решение - записать более короткий файл в новый файл, например newtover's answer .Это было бы проще, но за короткий промежуток времени использовалось бы вдвое больше дискового пространства.

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

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 в этом случае, поскольку это действительно запасной вариант, и ваш код может захотеть обработать это более осторожно (если это возможно).

36 голосов
/ 23 октября 2015

В 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)
6 голосов
/ 17 января 2012
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)
4 голосов
/ 14 мая 2014

Это моя реализация для преобразования любого вида кодирования в 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
0 голосов
/ 07 сентября 2017

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

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

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

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

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

0 голосов
/ 09 февраля 2015

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

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")
...