Перетворити UTF-8 із специфікацією на UTF-8 без специфікації на Python


82

Два запитання тут. У мене є набір файлів, які зазвичай є 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

Екстрене повідомлення

У коментарях мені кажуть, що помилка полягає в тому, що я відкриваю файл у режимі 'rw' замість 'r +' / 'r + b', тому зрештою я повинен перередагувати своє запитання та видалити вирішену частину.


2
Вам потрібно відкрити файл для читання плюс оновлення, тобто в r+режимі. Додайте bзанадто, щоб він працював і в Windows, без жодних смішних рядків, що закінчують бізнес. Нарешті, вам захочеться повернутися до початку файлу та скоротити його в кінці - див. Мою оновлену відповідь.
Мартін Гейслер

Відповіді:


125

Просто використовуйте кодек "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. Якщо ваші файли великі, то вам слід уникати їх читання в пам’ять. Специфікація - це просто три байти на початку файлу, тому ви можете використовувати цей код, щоб видалити їх із файлу:

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 . Це було б простіше, але використовуйте вдвічі більше місця на диску протягом короткого періоду.

Що стосується вгадування кодування, то ви можете просто прокрутити кодування від найбільш до найменш конкретного:

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


хм, я оновив запитання в редакції №1 зразком коду, але отримав поганий дескриптор файлу. thx за будь-яку допомогу. Намагаючись зрозуміти це.
timpone

64

У 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)

3
Найкраща відповідь в Інтернеті на цю тему. Просто використовуйте utf-8-sig.
QtRoS

6
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)

Ви можете пояснити, як працює цей код? $ remove_bom.py <input.txt> output.txt Я правильно?
guneysus

@guneysus, так, саме
newtover

1
я щойно додавheader = header[3:] if header[0:3] == codecs.BOM_UTF8 else header
chinmayv

5

Це моя реалізація для перетворення будь-якого виду кодування в UTF-8 без специфікації та заміни ліній Windows на універсальний формат:

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

3

Я знайшов це запитання, оскільки маю проблеми з configparser.ConfigParser().read(fp)відкриттям файлів із заголовком специфікації UTF8.

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

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

Це може заощадити багато зусиль, зробивши непотрібним видалення заголовка специфікації файлу.

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


1
оскільки я вперше працював із try - за винятком -> це також без проблем
відкриває

2

Ви можете використовувати кодеки.

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

взагалі непридатний для використання фрагмент (handhandle? також кодеки.BOM_UTF8 повертають синтаксичну помилку)
Макс.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.