Konwertuj UTF-8 z BOM na UTF-8 bez BOM w Pythonie


82

Tutaj dwa pytania. Mam zestaw plików, które zwykle są w formacie UTF-8 z BOM. Chciałbym je przekonwertować (najlepiej na miejscu) do UTF-8 bez BOM. Wygląda na to, codecs.StreamRecoder(stream, encode, decode, Reader, Writer, errors)że poradzi sobie z tym. Ale tak naprawdę nie widzę dobrych przykładów użycia. Czy byłby to najlepszy sposób na rozwiązanie tego problemu?

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

Ponadto byłoby idealnie, gdybyśmy mogli obsłużyć różne kodowanie danych wejściowych bez wyraźnej wiedzy (patrz ASCII i UTF-16). Wydaje się, że to wszystko powinno być wykonalne. Czy istnieje rozwiązanie, które może przyjąć dowolne znane kodowanie w Pythonie i wyprowadzać jako UTF-8 bez BOM?

edytuj 1 proponowane rozwiązanie od dołu (dzięki!)

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)

To daje mi następujący błąd:

IOError: [Errno 9] Bad file descriptor

wiadomosci

W komentarzach powiedziano mi, że błąd polega na tym, że otwieram plik w trybie „rw” zamiast „r +” / „r + b”, więc powinienem w końcu ponownie edytować moje pytanie i usunąć rozwiązaną część.


2
Musisz otworzyć plik do odczytu i aktualizacji, tj r+. W trybie. Dodaj bteż, aby działał również w systemie Windows bez żadnych zabawnych zakończeń linii. Na koniec zechcesz wrócić do początku pliku i skrócić go na końcu - zobacz moją zaktualizowaną odpowiedź.
Martin Geisler

Odpowiedzi:


125

Po prostu użyj kodeka „utf-8-sig” :

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

To daje ci unicodeciąg bez BOM. Następnie możesz użyć

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

aby odzyskać normalny łańcuch zakodowany w UTF-8 s. Jeśli twoje pliki są duże, powinieneś unikać wczytywania ich wszystkich do pamięci. BOM ma po prostu trzy bajty na początku pliku, więc możesz użyć tego kodu, aby usunąć je z pliku:

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

Otwiera plik, odczytuje fragment i zapisuje go do pliku 3 bajty wcześniej niż miejsce, w którym go odczytuje. Plik jest przepisywany w miejscu. Łatwiejszym rozwiązaniem jest zapisanie krótszego pliku do nowego pliku, takiego jak odpowiedź newtover . Byłoby to prostsze, ale przez krótki czas zajmowałoby dwukrotnie więcej miejsca na dysku.

Jeśli chodzi o zgadywanie kodowania, możesz po prostu zapętlić kodowanie od najbardziej do najmniej konkretnego:

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

Plik zakodowany w UTF-16 nie zostanie zdekodowany jako UTF-8, więc najpierw spróbujemy z UTF-8. Jeśli to się nie powiedzie, próbujemy z UTF-16. Na koniec używamy Latin-1 - to zawsze będzie działać, ponieważ wszystkie 256 bajtów to legalne wartości w Latin-1. W Nonetym przypadku możesz chcieć zwrócić zamiast tego, ponieważ jest to naprawdę rezerwa i Twój kod może chcieć obsługiwać to ostrożniej (jeśli to możliwe).


hmm, zaktualizowałem pytanie w edycji nr 1 przykładowym kodem, ale otrzymałem zły deskryptor pliku. dzięki za wszelką pomoc. Próbuję to rozgryźć.
timpone

64

W Pythonie 3 jest to całkiem proste: przeczytaj plik i przepisz go z utf-8kodowaniem:

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

3
Najlepsza odpowiedź w sieci na ten temat. Po prostu użyj 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)

czy możesz wyjaśnić, jak działa ten kod? $ remove_bom.py <input.txt> output.txt Czy mam rację?
guneysus

@guneysus, tak, dokładnie
newtover

1
właśnie dodałemheader = header[3:] if header[0:3] == codecs.BOM_UTF8 else header
chinmayv

5

Oto moja implementacja do konwersji dowolnego rodzaju kodowania do UTF-8 bez BOM i zastąpienia powiększania okien przez format uniwersalny:

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

Znalazłem to pytanie, ponieważ mam problem z configparser.ConfigParser().read(fp)otwieraniem plików z nagłówkiem BOM UTF8.

Dla tych, którzy szukają rozwiązania w celu usunięcia nagłówka, aby ConfigPhaser mógł otworzyć plik konfiguracyjny zamiast zgłaszać błąd:, File contains no section headersproszę otworzyć plik w następujący sposób:

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

Może to zaoszczędzić mnóstwo wysiłku, sprawiając, że usunięcie nagłówka BOM pliku będzie niepotrzebne.

(Wiem, że to brzmi niepowiązane, ale mam nadzieję, że może to pomóc ludziom walczącym tak jak ja).


1
ponieważ pierwszy raz pracowałem z try - z wyjątkiem -> to również otwiera pliki zakodowane w UTF-8 "nie BOM" bez problemów
flipSTAR

2

Możesz używać kodeków.

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

w ogóle nieużywalny fragment kodu (uchwyt pliku? także kodeki, BOM_UTF8 zwraca błąd składni)
maks.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.