Como determinar a codificação do texto?

Respostas:

225

Detectar corretamente a codificação o tempo todo é impossível .

(De chardet FAQ :)

No entanto, algumas codificações são otimizadas para idiomas específicos, e os idiomas não são aleatórios. Algumas seqüências de caracteres aparecem o tempo todo, enquanto outras não fazem sentido. Uma pessoa fluente em inglês que abre um jornal e encontra “txzqJv 2! Dasd0a QqdKjvz” reconhece instantaneamente que isso não é inglês (embora seja composto inteiramente de letras em inglês). Ao estudar muito texto "típico", um algoritmo de computador pode simular esse tipo de fluência e adivinhar o idioma de um texto.

Existe a biblioteca chardet que usa esse estudo para tentar detectar a codificação. chardet é uma porta do código de detecção automática no Mozilla.

Você também pode usar UnicodeDammit . Ele tentará os seguintes métodos:

  • Uma codificação descoberta no próprio documento: por exemplo, em uma declaração XML ou (para documentos HTML) uma tag META http-equiv. Se a Beautiful Soup encontrar esse tipo de codificação no documento, ele analisará o documento novamente desde o início e tentará a nova codificação. A única exceção é se você especificou explicitamente uma codificação, e essa codificação realmente funcionou: ela ignorará qualquer codificação encontrada no documento.
  • Uma codificação foi detectada observando os primeiros bytes do arquivo. Se uma codificação for detectada nesse estágio, ela será uma das codificações UTF- *, EBCDIC ou ASCII.
  • Uma codificação detectada pela biblioteca chardet , se você a tiver instalada.
  • UTF-8
  • Windows-1252
nosklo
fonte
1
Obrigado pela chardetreferência. Parece bom, embora um pouco lento.
Craig McQueen
17
@ Geomorillo: Não existe o "padrão de codificação". A codificação de texto é algo tão antigo quanto a computação, ela cresceu organicamente com o tempo e as necessidades, não foi planejada. "Unicode" é uma tentativa de corrigir isso.
Nosklo 02/12/13
1
E não é ruim, considerando todas as coisas. O que eu gostaria de saber é como descobrir com que codificação um arquivo de texto aberto foi aberto?
holdenweb
2
@ Dumbledad o que eu disse é que detectar corretamente o tempo todo é impossível. Tudo o que você pode fazer é um palpite, mas às vezes pode falhar, não funcionará sempre, porque as codificações não são realmente detectáveis. Para adivinhar, você pode usar uma das ferramentas que sugeri na resposta
nosklo
1
@ LasseKärkkäinen o ponto dessa resposta é mostrar que a detecção correta da codificação é impossível ; a função que você fornece pode ser correta para o seu caso, mas está errada em muitos casos.
Nosklo 12/07/19
67

Outra opção para calcular a codificação é usar libmagic (que é o código por trás do comando file ). Há uma profusão de ligações python disponíveis.

As ligações python que vivem na árvore de origem do arquivo estão disponíveis como o pacote debian python-magic (ou python3-magic ). Pode determinar a codificação de um arquivo, fazendo:

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

Existe um pacote de pip python-magic com nome idêntico, mas incompatível, no pypi que também usa libmagic. Também pode obter a codificação, fazendo:

import magic

blob = open('unknown-file', 'rb').read()
m = magic.Magic(mime_encoding=True)
encoding = m.from_buffer(blob)
Hamish Downer
fonte
5
libmagicé realmente uma alternativa viável para chardet. E ótimas informações sobre os diferentes pacotes nomeados python-magic! Estou certo de que este ambigüidade morde muitas pessoas
MestreLion
1
filenão é particularmente bom em identificar a linguagem humana em arquivos de texto. É excelente para identificar vários formatos de contêiner, embora às vezes você precise saber o que significa ("documento do Microsoft Office" pode significar uma mensagem do Outlook etc.).
Tripleee
Procurando uma maneira de gerenciar o mistério da codificação de arquivos, encontrei este post. Infelizmente, usando o código de exemplo, não posso passar por open(): UnicodeDecodeError: 'utf-8' codec can't decode byte 0xfc in position 169799: invalid start byte. A codificação do arquivo de acordo com o vim :set fileencodingé latin1.
Xtian
Se eu usar o argumento opcional errors='ignore', a saída do código de exemplo é menos útil binary.
Xtian
2
@xtian Você precisa abrir no modo binário, ou seja, aberto ("filename.txt", "rb").
L. Kärkkäinen
31

Algumas estratégias de codificação, por favor, descomente o gosto:

#!/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

Você pode verificar a codificação abrindo e lendo o arquivo na forma de um loop ... mas pode ser necessário verificar primeiro o tamanho do arquivo:

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
fonte
Você também pode usar io, como io.open(filepath, 'r', encoding='utf-8'), o que é mais conveniente, porque codecsnão converte \nautomaticamente na leitura e gravação. Mais sobre HERE
Searene
23

Aqui está um exemplo de leitura e obtenção pelo valor nominal de uma chardetprevisão de codificação, lendo n_lineso arquivo no caso de ele ser grande.

chardettambém fornece uma probabilidade (ou seja confidence) de sua previsão de codificação (não olhou como eles criam isso), que é retornada com sua previsão de chardet.predict(), para que você possa trabalhar de alguma forma, se quiser.

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
fonte
Olhando para isso depois de obter uma votação antecipada e agora ver que essa solução pode desacelerar se houver muitos dados na primeira linha. Em alguns casos, seria melhor ler os dados de maneira diferente.
Ryanjdillon
2
Modifiquei esta função da seguinte maneira: def predict_encoding(file_path, n=20): ... skip ... and then rawdata = b''.join([f.read() for _ in range(n)]) Já tentei essa função no Python 3.6, funcionou perfeitamente com as codificações "ascii", "cp1252", "utf-8", "unicode". Então isso é definitivamente positivo.
n158 18/10/19
1
isso é muito bom para lidar com pequenos conjuntos de dados com uma variedade de formatos. Testei isso recursivamente no meu diretório raiz e funcionou como um deleite. Obrigado parceiro.
Datanovice 25/11/19
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
fonte
2

Dependendo da sua plataforma, opto por usar o filecomando linux shell . Isso funciona para mim desde que o estou usando em um script que roda exclusivamente em uma de nossas máquinas Linux.

Obviamente, essa não é uma solução ou resposta ideal, mas pode ser modificada para atender às suas necessidades. No meu caso, só preciso determinar se um arquivo é UTF-8 ou não.

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
fonte
A bifurcação de um novo processo não é necessária. O código Python já é executado dentro de um processo e pode chamar as próprias funções do sistema sem a sobrecarga de carregar um novo processo.
vdboor
2

Isso pode ser útil

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
fonte
1

É, em princípio, impossível determinar a codificação de um arquivo de texto, no caso geral. Portanto, não, não existe uma biblioteca Python padrão para fazer isso por você.

Se você tiver conhecimento mais específico sobre o arquivo de texto (por exemplo, que é XML), pode haver funções de biblioteca.

Martin v. Löwis
fonte
1

Se você conhece algum conteúdo do arquivo, pode tentar decodificá-lo com várias codificações e ver qual está faltando. Em geral, não há como, pois um arquivo de texto é um arquivo de texto e esses são estúpidos;)

Martin Thurau
fonte
1

Este site possui código python para reconhecimento de ascii, codificação com boms e utf8 no bom: https://unicodebook.readthedocs.io/guess_encoding.html . Leia o arquivo na matriz de bytes (dados): http://www.codecodex.com/wiki/Read_a_file_into_a_byte_array . Aqui está um exemplo. Eu estou no 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
fonte
Um link para uma solução é bem-vindo, mas garanta que sua resposta seja útil sem ela: adicione contexto ao link para que seus colegas usuários tenham uma idéia do que é e por que está lá, depois cite a parte mais relevante da página que você ' reencaminhando para o caso de a página de destino não estar disponível. Respostas que são pouco mais que um link podem ser excluídas.
double-beep