¿Cómo determinar la codificación del texto?

Respuestas:

225

Detectar correctamente la codificación en todo momento es imposible .

(De chardet FAQ :)

Sin embargo, algunas codificaciones están optimizadas para idiomas específicos, y los idiomas no son aleatorios. Algunas secuencias de personajes aparecen todo el tiempo, mientras que otras secuencias no tienen sentido. Una persona con fluidez en inglés que abre un periódico y encuentra "txzqJv 2! Dasd0a QqdKjvz" reconocerá instantáneamente que eso no es inglés (aunque esté compuesto completamente de letras en inglés). Al estudiar muchos textos "típicos", un algoritmo informático puede simular este tipo de fluidez y hacer una suposición educada sobre el lenguaje de un texto.

Existe la biblioteca de chardet que utiliza ese estudio para intentar detectar la codificación. chardet es un puerto del código de autodetección en Mozilla.

También puede usar UnicodeDammit . Intentará los siguientes métodos:

  • Una codificación descubierta en el propio documento: por ejemplo, en una declaración XML o (para documentos HTML) una etiqueta META http-equiv. Si Beautiful Soup encuentra este tipo de codificación dentro del documento, analiza el documento nuevamente desde el principio y prueba la nueva codificación. La única excepción es si especificó explícitamente una codificación, y esa codificación realmente funcionó: entonces ignorará cualquier codificación que encuentre en el documento.
  • Una codificación detectada al observar los primeros bytes del archivo. Si se detecta una codificación en esta etapa, será una de las codificaciones UTF- *, EBCDIC o ASCII.
  • Una codificación detectada por la biblioteca de chardet , si la tiene instalada.
  • UTF-8
  • Windows-1252
nosklo
fuente
1
Gracias por la chardetreferencia Parece bueno, aunque un poco lento.
Craig McQueen
17
@ Geomorillo: No existe el "estándar de codificación". La codificación de texto es algo tan antiguo como la informática, creció orgánicamente con el tiempo y las necesidades, no fue planeado. "Unicode" es un intento de arreglar esto.
nosklo
1
Y no es malo, considerando todas las cosas. Lo que me gustaría saber es, ¿cómo puedo saber con qué codificación se abrió un archivo de texto abierto?
holdenweb
2
@dumbledad lo que dije es que detectarlo correctamente todo el tiempo es imposible. Todo lo que puede hacer es adivinar, pero a veces puede fallar, no funcionará siempre, debido a que las codificaciones no son realmente detectables. Para adivinar, puede usar una de las herramientas que sugerí en la respuesta
nosklo
1
@ LasseKärkkäinen el punto de esa respuesta es mostrar que es imposible detectar la codificación correctamente ; la función que proporciona puede acertar para su caso, pero es incorrecta en muchos casos.
nosklo
67

Otra opción para resolver la codificación es usar libmagic (que es el código detrás del comando de archivo ). Hay una gran cantidad de enlaces de python disponibles.

Los enlaces de python que viven en el árbol de origen de archivos están disponibles como el paquete debian python-magic (o python3-magic ). Puede determinar la codificación de un archivo haciendo:

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

Hay un paquete pip -python-magic idénticamente nombrado, pero incompatible, en pypi que también usa libmagic. También puede obtener la codificación, haciendo:

import magic

blob = open('unknown-file', 'rb').read()
m = magic.Magic(mime_encoding=True)
encoding = m.from_buffer(blob)
Hamish Downer
fuente
55
libmagices de hecho una alternativa viable para chardet. ¡Y gran información sobre los distintos paquetes nombrados python-magic! Estoy seguro de que esta ambigüedad muerde a muchas personas
MestreLion
1
fileno es particularmente bueno para identificar el lenguaje humano en archivos de texto. Es excelente para identificar varios formatos de contenedor, aunque a veces tiene que saber lo que significa ("documento de Microsoft Office" podría significar un mensaje de Outlook, etc.).
tripleee
Buscando una manera de administrar el misterio de codificación de archivos, encontré esta publicación. Por desgracia, utilizando el código de ejemplo, no puedo conseguir más allá open(): UnicodeDecodeError: 'utf-8' codec can't decode byte 0xfc in position 169799: invalid start byte. La codificación del archivo de acuerdo con vim :set fileencodinges latin1.
xtian
Si uso el argumento opcional errors='ignore', la salida del código de ejemplo es menos útil binary.
xtian
2
@xtian Debe abrir en modo binario, es decir, abrir ("filename.txt", "rb").
L. Kärkkäinen
31

Algunas estrategias de codificación, por favor descomente al gusto:

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

Es posible que desee verificar la codificación abriendo y leyendo el archivo en forma de bucle ... pero es posible que primero deba verificar el tamaño del archivo:

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
fuente
También puede usar io, como io.open(filepath, 'r', encoding='utf-8'), que es más conveniente, porque codecsno se convierte \nautomáticamente en lectura y escritura. Más sobre AQUÍ
Searene
23

Aquí hay un ejemplo de leer y tomar al pie de la letra una chardetpredicción de codificación, leer n_linesdel archivo en caso de que sea grande.

chardettambién le da una probabilidad (es decir confidence) de que está prediciendo la codificación (no he visto cómo se les ocurre), que se devuelve con su predicción chardet.predict(), por lo que podría trabajar de alguna manera si lo desea.

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
fuente
Mirando esto después de obtener un voto positivo y ahora ver que esta solución podría disminuir si hubiera una gran cantidad de datos en la primera línea. En algunos casos, sería mejor leer los datos de manera diferente.
ryanjdillon
2
He modificado esta función de esta manera: def predict_encoding(file_path, n=20): ... skip ... and then rawdata = b''.join([f.read() for _ in range(n)]) he probado esta función en Python 3.6, funcionó perfectamente con codificaciones "ascii", "cp1252", "utf-8", "unicode". Así que esto definitivamente es un voto positivo.
n158
1
Esto es muy bueno para manejar pequeños conjuntos de datos con una variedad de formatos. Probé esto de forma recursiva en mi directorio raíz y funcionó como una delicia. Gracias amigo.
Datanovice el
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
fuente
2

Dependiendo de su plataforma, simplemente opto por usar el filecomando de shell de Linux . Esto funciona para mí ya que lo estoy usando en un script que se ejecuta exclusivamente en una de nuestras máquinas Linux.

Obviamente, esta no es una solución o respuesta ideal, pero podría modificarse para satisfacer sus necesidades. En mi caso, solo necesito determinar si un archivo es UTF-8 o no.

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
fuente
Bifurcar un nuevo proceso no es necesario. El código de Python ya se ejecuta dentro de un proceso y puede llamar a las funciones del sistema adecuadas sin la sobrecarga de cargar un nuevo proceso.
vdboor
2

Esto puede 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
fuente
1

En principio, es imposible determinar la codificación de un archivo de texto, en el caso general. Entonces, no, no hay una biblioteca estándar de Python para hacer eso por usted.

Si tiene un conocimiento más específico sobre el archivo de texto (por ejemplo, que es XML), puede haber funciones de biblioteca.

Martin v. Löwis
fuente
1

Si conoce el contenido del archivo, puede intentar decodificarlo con varias codificaciones y ver cuál falta. En general no hay forma ya que un archivo de texto es un archivo de texto y esos son estúpidos;)

Martin Thurau
fuente
1

Este sitio tiene un código python para reconocer ascii, codificar con boms y utf8 no bom: https://unicodebook.readthedocs.io/guess_encoding.html . Lea el archivo en la matriz de bytes (datos): http://www.codecodex.com/wiki/Read_a_file_into_a_byte_array . Aquí hay un ejemplo. Estoy en 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
fuente
Un enlace a una solución es bienvenido, pero asegúrese de que su respuesta sea útil sin él: agregue contexto alrededor del enlace para que sus otros usuarios tengan una idea de qué es y por qué está allí, luego cite la parte más relevante de la página volver a vincular en caso de que la página de destino no esté disponible. Se pueden eliminar las respuestas que son poco más que un enlace.
doble pitido