Jak ustalić kodowanie tekstu?

Odpowiedzi:

225

Prawidłowe wykrycie kodowania przez cały czas jest niemożliwe .

(Z FAQ chardet :)

Jednak niektóre kodowania są zoptymalizowane dla określonych języków, a języki nie są losowe. Niektóre sekwencje postaci pojawiają się cały czas, podczas gdy inne sekwencje nie mają sensu. Osoba biegła w języku angielskim, która otworzy gazetę i znajdzie „txzqJv 2! Dasd0a QqdKjvz”, natychmiast rozpozna, że ​​to nie jest angielski (nawet jeśli składa się wyłącznie z angielskich liter). Studiując wiele „typowych” tekstów, algorytm komputerowy może symulować tego rodzaju płynność i zgrabnie zgadywać o języku tekstu.

Istnieje biblioteka chardet, która wykorzystuje to badanie do próby wykrycia kodowania. chardet to port kodu automatycznego wykrywania w Mozilli.

Możesz także użyć UnicodeDammit . Spróbuje następujących metod:

  • Kodowanie odkryte w samym dokumencie: na przykład w deklaracji XML lub (w przypadku dokumentów HTML) znacznik META http-equiv. Jeśli Beautiful Soup znajdzie tego rodzaju kodowanie w dokumencie, ponownie analizuje dokument od początku i wypróbowuje nowe kodowanie. Jedynym wyjątkiem jest to, że jawnie określiłeś kodowanie, a kodowanie faktycznie zadziałało: wówczas zignoruje wszelkie kodowania znalezione w dokumencie.
  • Kodowanie powąchało, patrząc na kilka pierwszych bajtów pliku. Jeśli na tym etapie zostanie wykryte kodowanie, będzie to jedno z kodowań UTF- *, EBCDIC lub ASCII.
  • Kodowanie wąchane przez bibliotekę chardet , jeśli jest już zainstalowane.
  • UTF-8
  • Windows-1252
nosklo
źródło
1
Dzięki za chardetreferencje. Wydaje się być dobry, choć nieco powolny.
Craig McQueen
17
@Geomorillo: Nie ma czegoś takiego jak „standard kodowania”. Kodowanie tekstu jest czymś tak starym jak informatyka, rozwijało się organicznie z czasem i potrzebami, nie było planowane. „Unicode” jest próbą naprawienia tego.
nosklo
1
I nieźle, zważywszy na wszystko. Chciałbym się dowiedzieć, w jaki sposób mogę się dowiedzieć, z jakim kodowaniem został otwarty otwarty plik tekstowy?
holdenweb
2
@dumbledad powiedziałem, że prawidłowe wykrywanie go przez cały czas jest niemożliwe. Wszystko, co możesz zrobić, to zgadnąć, ale czasami może się nie powieść, nie będzie działać za każdym razem, ponieważ kodowanie nie jest tak naprawdę wykrywalne. Aby odgadnąć, możesz użyć jednego z narzędzi, które zasugerowałem w odpowiedzi
nosklo,
1
@ LasseKärkkäinen, celem tej odpowiedzi jest wykazanie, że niemożliwe jest dokładne wykrycie kodowania ; funkcja, którą udostępniasz, może odgadnąć odpowiednią dla twojej sprawy, ale jest niepoprawna w wielu przypadkach.
nosklo
67

Inną opcją do opracowania kodowania jest użycie libmagic (czyli kodu stojącego za poleceniem file ). Dostępnych jest wiele powiązań Pythona.

Powiązania Pythona, które istnieją w drzewie źródeł plików, są dostępne jako pakiet debian python-magic (lub python3-magic ). Może określić kodowanie pliku, wykonując:

import magic

blob = open('unknown-file', 'rb').read()
m = magic.open(magic.MAGIC_MIME_ENCODING)
m.load()
encoding = m.buffer(blob)  # "utf-8" "us-ascii" etc

Istnieje pipi o identycznej nazwie, ale niekompatybilny, python-magic pip, który również używa libmagic. Może również uzyskać kodowanie, wykonując:

import magic

blob = open('unknown-file', 'rb').read()
m = magic.Magic(mime_encoding=True)
encoding = m.from_buffer(blob)
Hamish Downer
źródło
5
libmagicjest rzeczywiście realną alternatywą dla chardet. I świetne informacje o nazwanych pakietach python-magic! Jestem pewien, że ta dwuznaczność gryzie wielu ludzi
MestreLion
1
filenie jest szczególnie dobry w rozpoznawaniu ludzkiego języka w plikach tekstowych. Doskonale nadaje się do identyfikowania różnych formatów kontenerów, chociaż czasami musisz wiedzieć, co to znaczy („dokument Microsoft Office” może oznaczać wiadomość programu Outlook itp.).
tripleee
Szukając sposobu na zarządzanie tajemnicą kodowania plików, znalazłem ten post. Niestety, stosując kod przykładowy, nie mogę ominąć open(): UnicodeDecodeError: 'utf-8' codec can't decode byte 0xfc in position 169799: invalid start byte. Kodowanie plików według vima :set fileencodingto latin1.
xtian
Jeśli użyję opcjonalnego argumentu errors='ignore', wynik kodu przykładowego jest mniej pomocny binary.
xtian
2
@xtian Musisz otworzyć w trybie binarnym, tzn. otworzyć („filename.txt”, „rb”).
L. Kärkkäinen
31

Niektóre strategie kodowania, proszę odkomentować do smaku:

#!/bin/bash
#
tmpfile=$1
echo '-- info about file file ........'
file -i $tmpfile
enca -g $tmpfile
echo 'recoding ........'
#iconv -f iso-8859-2 -t utf-8 back_test.xml > $tmpfile
#enca -x utf-8 $tmpfile
#enca -g $tmpfile
recode CP1250..UTF-8 $tmpfile

Może chcesz sprawdzić kodowanie, otwierając i odczytując plik w formie pętli ... ale może być konieczne najpierw sprawdzenie rozmiaru pliku:

encodings = ['utf-8', 'windows-1250', 'windows-1252' ...etc]
            for e in encodings:
                try:
                    fh = codecs.open('file.txt', 'r', encoding=e)
                    fh.readlines()
                    fh.seek(0)
                except UnicodeDecodeError:
                    print('got unicode error with %s , trying different encoding' % e)
                else:
                    print('opening the file with encoding:  %s ' % e)
                    break              
zzart
źródło
Możesz także użyć io, jak io.open(filepath, 'r', encoding='utf-8'), co jest wygodniejsze, ponieważ codecsnie konwertuje się \nautomatycznie podczas czytania i pisania. Więcej na TUTAJ
Searene
23

Oto przykład odczytu i wzięcia pod uwagę chardetprognozy kodowania, czytania n_linesz pliku w przypadku, gdy jest on duży.

chardetdaje również prawdopodobieństwo (tj. confidence) przewidywania kodowania (nie spojrzałem, jak oni to wymyślili), które jest zwracane wraz z jego przewidywaniem chardet.predict(), więc możesz to w jakiś sposób wykorzystać, jeśli chcesz.

def predict_encoding(file_path, n_lines=20):
    '''Predict a file's encoding using chardet'''
    import chardet

    # Open the file as binary data
    with open(file_path, 'rb') as f:
        # Join binary lines for specified number of lines
        rawdata = b''.join([f.readline() for _ in range(n_lines)])

    return chardet.detect(rawdata)['encoding']
ryanjdillon
źródło
Patrząc na to po uzyskaniu większego głosu, a teraz widzimy, że to rozwiązanie może spowolnić, jeśli w pierwszej linii będzie dużo danych. W niektórych przypadkach lepiej byłoby odczytać dane w inny sposób.
ryanjdillon
2
Zmodyfikowałem tę funkcję w ten sposób: def predict_encoding(file_path, n=20): ... skip ... and then rawdata = b''.join([f.read() for _ in range(n)]) wypróbowałem tę funkcję w Pythonie 3.6, działał doskonale z kodowaniem „ascii”, „cp1252”, „utf-8”, „Unicode”. To zdecydowanie jest pozytywne.
n158,
1
jest to bardzo dobre do obsługi małych zestawów danych o różnych formatach. Przetestowałem to rekurencyjnie na moim katalogu głównym i działało jak uczta. Dzięki stary.
Datanovice,
4
# Function: OpenRead(file)

# A text file can be encoded using:
#   (1) The default operating system code page, Or
#   (2) utf8 with a BOM header
#
#  If a text file is encoded with utf8, and does not have a BOM header,
#  the user can manually add a BOM header to the text file
#  using a text editor such as notepad++, and rerun the python script,
#  otherwise the file is read as a codepage file with the 
#  invalid codepage characters removed

import sys
if int(sys.version[0]) != 3:
    print('Aborted: Python 3.x required')
    sys.exit(1)

def bomType(file):
    """
    returns file encoding string for open() function

    EXAMPLE:
        bom = bomtype(file)
        open(file, encoding=bom, errors='ignore')
    """

    f = open(file, 'rb')
    b = f.read(4)
    f.close()

    if (b[0:3] == b'\xef\xbb\xbf'):
        return "utf8"

    # Python automatically detects endianess if utf-16 bom is present
    # write endianess generally determined by endianess of CPU
    if ((b[0:2] == b'\xfe\xff') or (b[0:2] == b'\xff\xfe')):
        return "utf16"

    if ((b[0:5] == b'\xfe\xff\x00\x00') 
              or (b[0:5] == b'\x00\x00\xff\xfe')):
        return "utf32"

    # If BOM is not provided, then assume its the codepage
    #     used by your operating system
    return "cp1252"
    # For the United States its: cp1252


def OpenRead(file):
    bom = bomType(file)
    return open(file, 'r', encoding=bom, errors='ignore')


#######################
# Testing it
#######################
fout = open("myfile1.txt", "w", encoding="cp1252")
fout.write("* hi there (cp1252)")
fout.close()

fout = open("myfile2.txt", "w", encoding="utf8")
fout.write("\u2022 hi there (utf8)")
fout.close()

# this case is still treated like codepage cp1252
#   (User responsible for making sure that all utf8 files
#   have a BOM header)
fout = open("badboy.txt", "wb")
fout.write(b"hi there.  barf(\x81\x8D\x90\x9D)")
fout.close()

# Read Example file with Bom Detection
fin = OpenRead("myfile1.txt")
L = fin.readline()
print(L)
fin.close()

# Read Example file with Bom Detection
fin = OpenRead("myfile2.txt")
L =fin.readline() 
print(L) #requires QtConsole to view, Cmd.exe is cp1252
fin.close()

# Read CP1252 with a few undefined chars without barfing
fin = OpenRead("badboy.txt")
L =fin.readline() 
print(L)
fin.close()

# Check that bad characters are still in badboy codepage file
fin = open("badboy.txt", "rb")
fin.read(20)
fin.close()
Bill Moore
źródło
2

W zależności od twojej platformy, po prostu wybrałem filepolecenie powłoki Linux . Działa to dla mnie, ponieważ używam go w skrypcie, który działa wyłącznie na jednym z naszych komputerów z systemem Linux.

Oczywiście nie jest to idealne rozwiązanie ani odpowiedź, ale można je zmodyfikować w celu dopasowania do Twoich potrzeb. W moim przypadku muszę tylko ustalić, czy plik to UTF-8, czy nie.

import subprocess
file_cmd = ['file', 'test.txt']
p = subprocess.Popen(file_cmd, stdout=subprocess.PIPE)
cmd_output = p.stdout.readlines()
# x will begin with the file type output as is observed using 'file' command
x = cmd_output[0].split(": ")[1]
return x.startswith('UTF-8')
MikeD
źródło
Rozwidlenie nowego procesu nie jest konieczne. Kod Pythona jest już uruchomiony w procesie i może wywoływać odpowiednie funkcje systemowe bez konieczności ładowania nowego procesu.
vdboor
2

To może być pomocne

from bs4 import UnicodeDammit
with open('automate_data/billboard.csv', 'rb') as file:
   content = file.read()

suggestion = UnicodeDammit(content)
suggestion.original_encoding
#'iso-8859-1'
richinex
źródło
1

Zasadniczo niemożliwe jest określenie kodowania pliku tekstowego, w ogólnym przypadku. Więc nie, nie ma standardowej biblioteki Pythona, która mogłaby to dla Ciebie zrobić.

Jeśli masz bardziej szczegółową wiedzę na temat pliku tekstowego (np. Że jest to XML), mogą istnieć funkcje biblioteczne.

Martin v. Löwis
źródło
1

Jeśli znasz pewną zawartość pliku, możesz spróbować go zdekodować za pomocą kilku kodowań i zobaczyć, którego brakuje. W ogóle nie ma mowy, ponieważ plik tekstowy jest plikiem tekstowym, a te są głupie;)

Martin Thurau
źródło
1

Ta strona ma kod Pythona do rozpoznawania ascii, kodowania za pomocą boms i utf8 no bom: https://unicodebook.readthedocs.io/guess_encoding.html . Wczytaj plik do tablicy bajtów (dane): http://www.codecodex.com/wiki/Read_a_file_into_a_byte_array . Oto przykład. Jestem w OSX.

#!/usr/bin/python                                                                                                  

import sys

def isUTF8(data):
    try:
        decoded = data.decode('UTF-8')
    except UnicodeDecodeError:
        return False
    else:
        for ch in decoded:
            if 0xD800 <= ord(ch) <= 0xDFFF:
                return False
        return True

def get_bytes_from_file(filename):
    return open(filename, "rb").read()

filename = sys.argv[1]
data = get_bytes_from_file(filename)
result = isUTF8(data)
print(result)


PS /Users/js> ./isutf8.py hi.txt                                                                                     
True
js2010
źródło
Link do rozwiązania jest mile widziany, ale upewnij się, że Twoja odpowiedź jest przydatna bez niego: dodaj kontekst wokół linku, aby inni użytkownicy mieli pojęcie o tym, co to jest i dlaczego tam jest, a następnie przytocz najbardziej odpowiednią część strony, którą „ ponowne linkowanie na wypadek, gdyby strona docelowa była niedostępna. Odpowiedzi, które są niewiele więcej niż link, mogą zostać usunięte.
podwójny sygnał dźwiękowy