Error de encoding en Python con caracteres chinos

Soy un principiante que tiene problemas para decodificar varias docenas de archivos CSV con números + caracteres chinos (simplificados) a UTF-8 en Python 2.7.

No conozco la encoding de los archivos de entrada, así que he probado todas las codificaciones posibles que conozco: GB18030, UTF-7, UTF-8, UTF-16 y UTF-32 (LE & BE). Además, en buena medida, GBK y GB3212, aunque estos deben ser un subconjunto de GB18030. Todos los UTF se detienen cuando llegan a los primeros caracteres chinos. Las otras codificaciones se detienen en algún lugar de la primera línea, excepto GB18030. Pensé que esta sería la solución porque leía los primeros archivos y los decodificaba bien. Parte de mi código, leyendo línea por línea, es:

line = line.decode("GB18030") 

Los 2 primeros archivos que intenté descifrar funcionaron bien. A mitad del tercer archivo , Python escupe

 UnicodeDecodeError: 'gb18030' codec can't decode bytes in position 168-169: illegal multibyte sequence 

En este archivo, hay aproximadamente 5 errores de este tipo en aproximadamente un millón de líneas.

Abrí el archivo de entrada en un editor de texto y comprobé qué caracteres estaban dando los errores de deencoding, y los primeros pocos tenían signos de euro en una columna particular de los archivos CSV. Estoy bastante seguro de que estos son errores tipográficos, por lo que me gustaría eliminar los caracteres del euro. Me gustaría examinar los tipos de errores de encoding uno por uno; Me gustaría deshacerme de todos los errores del euro, pero no quiero ignorar a los demás hasta que los mire primero.

Edición: utilicé chardet que dio GB2312 como la encoding con .99 de confianza para todos los archivos. Intenté usar GB2312 para decodificar lo que dio:

 UnicodeDecodeError: 'gb2312' codec can't decode bytes in position 108-109: illegal multibyte sequence 

“” “… GB18030. Pensé que esta sería la solución porque leía los primeros archivos y los decodificaba bien.” “” – explique a qué se refiere. Para mí, hay DOS criterios para una deencoding exitosa: en primer lugar, que raw_bytes.decode (‘some_encoding’) no falló, en segundo lugar, que cuando se muestra unicode resultante tiene sentido en un idioma en particular. Cada archivo en el universo pasará la primera prueba cuando se decodifique con iso_8859_1 aka iso_8859_1 . Muchos archivos en idiomas de Asia oriental pasan la primera prueba con gb18030 , porque la mayoría de los caracteres utilizados con frecuencia en chino, japonés y coreano se codifican utilizando los mismos bloques de secuencias de dos bytes. ¿Cuánto de la segunda prueba has hecho?

No se preocupe por mirar los datos en un IDE o editor de texto. Míralo en un navegador web; Por lo general, hacen un mejor trabajo de detección de codificaciones.

¿Cómo sabes que es un personaje del euro? ¿Mirando la pantalla de un editor de texto que está decodificando los bytes en bruto usando qué encoding? cp1252?

¿Cómo sabes que contiene caracteres chinos? ¿Estás seguro de que no es japonés? ¿Coreano? ¿De donde lo sacaste?

Los archivos chinos creados en Hong Kong, Taiwán, tal vez Macao y otros lugares fuera del continente utilizan la encoding big5 o big5_hkscs : inténtelo.

En cualquier caso, chardet los consejos de Mark y chardet a ello; chardet generalmente hace un buen trabajo de detección de la encoding utilizada si el archivo es lo suficientemente grande y está correctamente codificado en chino / japonés / coreano. Sin embargo, si alguien ha estado editando el archivo manualmente en un editor de texto con un conjunto de caracteres de un solo byte, algunos los caracteres ilegales pueden hacer que la encoding utilizada para el otro 99.9% de los caracteres no se detecte.

Puede que print repr(line) en, por ejemplo, 5 líneas desde el archivo y editar la salida en su pregunta.

Si el archivo no es confidencial, le recomendamos que lo pueda descargar.

¿El archivo fue creado en Windows? ¿Cómo lo estás leyendo en Python? (mostrar código)

Actualizar después de los comentarios OP:

El bloc de notas, etc., no intenta adivinar la encoding; “ANSI” es el valor predeterminado. Tienes que decirle qué hacer. Lo que llama el carácter del euro es el byte en bruto “\ x80” descodificado por su editor utilizando la encoding predeterminada para su entorno, el sospechoso habitual es “cp1252”. No utilice un editor de este tipo para editar su archivo.

Antes hablabas de los “primeros errores”. Ahora dices que tienes 5 errores en total. Por favor explique.

Si el archivo es de hecho gb18030 casi correcto, debería poder decodificar línea por línea y, cuando se produce un error de este tipo, interceptarlo, imprimir el mensaje de error, extraer las compensaciones de bytes del mensaje, imprimir repr (two_bad_bytes) , y seguir adelante. Estoy muy interesado en cuál de los dos bytes aparece el \x80 . Si no aparece, el “carácter del euro” no es parte de su problema. Tenga en cuenta que \x80 puede aparecer válidamente en un archivo gb18030, pero solo como el segundo byte de una secuencia de 2 bytes que comienza con \x81 a \xfe .

Es una buena idea saber cuál es su problema antes de intentar solucionarlo. No es una buena idea tratar de solucionarlo mediante el uso del Bloc de notas, etc. en el modo “ANSI”.

Ha sido muy tímido acerca de cómo decidió que los resultados de la deencoding gb18030 tenían sentido. En particular, estaría examinando atentamente las líneas donde gbk falla pero gb18030 “funciona”; debe haber algunos caracteres chinos extremadamente raros allí, o tal vez algunos caracteres no chinos no ASCII …

Aquí hay una sugerencia para una mejor manera de inspeccionar el daño: descodifique cada archivo con raw_bytes.decode(encoding, 'replace') y escriba el resultado (codificado en utf8) en otro archivo. Cuente los errores por result.count(u'\ufffd') . Vea el archivo de salida con lo que haya usado para decidir que la deencoding gb18030 tiene sentido. El carácter U + FFFD debe aparecer como un signo de interrogación blanco dentro de un diamante negro.

Si decides que las piezas no decodificables se pueden descartar, la forma más fácil es raw_bytes.decode(encoding, 'ignore')

Actualizar después de más información

Todos esos son confusos. Parece que “obtener los bytes” implica repr(repr(bytes)) lugar de solo repr(bytes) … en el indicador interactivo, haga cualquiera de los bytes (obtendrá un repr implícito) () o print repr(bytes) (que no obtendrá el repr implícito)

El espacio en blanco: Supongo que quiere decir que '\xf8\xf8'.decode('gb18030') es lo que interpreta como una especie de espacio de ancho completo, y que la interpretación se realiza mediante inspección visual utilizando un software de visor no numerable. . ¿Es eso correcto?

En realidad, '\xf8\xf8'.decode('gb18030') -> u'\e28b' . U + E28B se encuentra en Unicode PUA (Área de uso privado). El “espacio en blanco” presumiblemente significa que el software del espectador no tiene un glifo para U + E28B en la fuente que está usando.

Quizás la fuente de los archivos está utilizando deliberadamente la PUA para los caracteres que no están en gb18030 estándar, o para la anotación, o para transmitir información de pseudosecret. Si es así, deberá recurrir a la pandereta de deencoding, una twig de la investigación rusa reciente que se informa aquí .

Alternativa: la teoría cp939-HKSCS. Según el gobierno de HK, el código HE5CS big5 FE57 se asignó una vez a U + E28B pero ahora se asigna a U + 28804.

El “euro”: Usted dijo “” “Debido a los datos no puedo compartir toda la línea, pero lo que llamaba el euro char está en: \ xcb \ xbe \ x80 \ x80” [Estoy asumiendo que \ se omitió desde el principio, y el " es literal]. El” carácter del euro “, cuando aparece, está siempre en la misma columna que no necesito, por lo que esperaba usar” ignorar “. Desafortunadamente , dado que el “euro char” está justo al lado de las comillas en el archivo, a veces “ignorar” elimina tanto el carácter del euro como [comillas], lo que plantea un problema para el módulo csv para determinar las columnas “” ”

Sería de gran ayuda si pudiera mostrar los patrones de dónde aparecen estos \x80 bytes en relación con las comillas y los caracteres chinos. Manténgalo legible con solo mostrar el hex y oculte sus datos confidenciales, por ejemplo, utilizando C1 C2 para representar ” Dos bytes que estoy seguro representan un carácter chino “. Por ejemplo:

 C1 C2 C1 C2 cb be 80 80 22 # `\x22` is the quote character 

Proporcione ejemplos de (1) donde “no se pierde por ‘reemplazar’ o ‘ignorar’ (2) donde se pierde la cotización. En su único ejemplo hasta la fecha, el” no se pierde:

 >>> '\xcb\xbe\x80\x80\x22'.decode('gb18030', 'ignore') u'\u53f8"' 

Y la oferta para enviarle algún código de depuración (consulte el ejemplo de salida a continuación) todavía está abierta.

 >>> import decode_debug as de >>> def logger(s): ... sys.stderr.write('*** ' + s + '\n') ... >>> import sys >>> de.decode_debug('\xcb\xbe\x80\x80\x22', 'gb18030', 'replace', logger) *** input[2:5] ('\x80\x80"') doesn't start with a plausible code sequence *** input[3:5] ('\x80"') doesn't start with a plausible code sequence u'\u53f8\ufffd\ufffd"' >>> de.decode_debug('\xcb\xbe\x80\x80\x22', 'gb18030', 'ignore', logger) *** input[2:5] ('\x80\x80"') doesn't start with a plausible code sequence *** input[3:5] ('\x80"') doesn't start with a plausible code sequence u'\u53f8"' >>> 

Eureka: – Causa probable de perder a veces el carácter de cita –

Parece que hay un error en el gb18030 reemplazar / ignorar del decodificador gb18030 : \x80 no es un byte de plomo gb18030 válido; cuando se detecta, el decodificador debe intentar sincronizar con el byte SIGUIENTE. Sin embargo, parece estar ignorando tanto el \x80 Y el siguiente byte:

 >>> '\x80abcd'.decode('gb18030', 'replace') u'\ufffdbcd' # the 'a' is lost >>> de.decode_debug('\x80abcd', 'gb18030', 'replace', logger) *** input[0:4] ('\x80abc') doesn't start with a plausible code sequence u'\ufffdabcd' >>> '\x80\x80abcd'.decode('gb18030', 'replace') u'\ufffdabcd' # the second '\x80' is lost >>> de.decode_debug('\x80\x80abcd', 'gb18030', 'replace', logger) *** input[0:4] ('\x80\x80ab') doesn't start with a plausible code sequence *** input[1:5] ('\x80abc') doesn't start with a plausible code sequence u'\ufffd\ufffdabcd' >>> 

Podrías probar el chardet .

Prueba esto:

 codecs.open(file, encoding='gb18030', errors='replace') 

No olvide los errors parámetros, también puede configurarlo en ‘ignorar’ .