¿Una implementación CSV resistente y en funcionamiento para no ascii?

[Actualización] Aprecie las respuestas e ingrese todo alrededor, pero el código de trabajo sería muy bienvenido. Si puede proporcionar un código que pueda leer los archivos de muestra, usted es el rey (o reina).

[Actualización 2] Gracias por las excelentes respuestas y discusión. Lo que debo hacer con estos es leerlos, analizarlos y guardar partes de ellos en instancias del modelo Django. Creo que eso significa convertirlos de su encoding nativa a Unicode para que Django pueda lidiar con ellos, ¿verdad?

Hay varias preguntas sobre Stackoverflow que ya están relacionadas con la lectura CSV de python sin ascii, pero las soluciones que se muestran allí y en la documentación de python no funcionan con los archivos de entrada que estoy intentando.

La esencia de la solución parece ser codificar (‘utf-8’) la entrada al lector CSV y unicode (elemento, ‘utf-8’) la salida del lector. Sin embargo, esto se ejecuta en problemas de UnicodeDecodeError (consulte las preguntas anteriores):

UnicodeDecodeError: 'utf8' codec can't decode byte 0xa3 in position 8: unexpected 

El archivo de entrada no está necesariamente en utf8; Puede ser ISO-8859-1, cp1251, o cualquier otra cosa.

Entonces, la pregunta: ¿cuál es una forma resistente y de encoding cruzada para leer archivos CSV en Python?

La raíz del problema parece ser que el módulo CSV es una extensión C; ¿Existe un módulo de lectura CSV de python puro?

Si no es así, ¿hay una manera de detectar con confianza la encoding del archivo de entrada para que pueda procesarse?

Básicamente, estoy buscando una forma a prueba de balas para leer (y con suerte escribir) archivos CSV en cualquier encoding.

Aquí hay dos archivos de muestra: europeo , ruso .

Y aquí está la solución recomendada en su defecto:

 Python 2.6.4 (r264:75821M, Oct 27 2009, 19:48:32) [GCC 4.0.1 (Apple Inc. build 5493)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import csv >>> def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs): ... # csv.py doesn't do Unicode; encode temporarily as UTF-8: ... csv_reader = csv.reader(utf_8_encoder(unicode_csv_data), ... dialect=dialect, **kwargs) ... for row in csv_reader: ... # decode UTF-8 back to Unicode, cell by cell: ... yield [unicode(cell, 'utf-8') for cell in row] ... >>> def utf_8_encoder(unicode_csv_data): ... for line in unicode_csv_data: ... yield line.encode('utf-8') ... >>> r = unicode_csv_reader(file('sample-euro.csv').read().split('\n')) >>> line = r.next() Traceback (most recent call last): File "", line 1, in  File "", line 5, in unicode_csv_reader File "", line 3, in utf_8_encoder UnicodeDecodeError: 'ascii' codec can't decode byte 0xf8 in position 14: ordinal not in range(128) >>> r = unicode_csv_reader(file('sample-russian.csv').read().split('\n')) >>> line = r.next() Traceback (most recent call last): File "", line 1, in  File "", line 5, in unicode_csv_reader File "", line 3, in utf_8_encoder UnicodeDecodeError: 'ascii' codec can't decode byte 0xa0 in position 28: ordinal not in range(128) 

Está intentando aplicar una solución a un problema diferente . Note esto:

def utf_8_encoder ( unicode_csv_data )

Lo estás alimentando con objetos.

Los problemas con la lectura de sus archivos CSV no ASCII es que no conoce la encoding y no conoce el delimitador. Si conoce la encoding (y es una encoding basada en ASCII (por ejemplo, cp125x, cualquier encoding de Asia Oriental, UTF-8, no UTF-16, no UTF-32)) y el delimitador, esto funcionará:

 for row in csv.reader("foo.csv", delimiter=known_delimiter): row = [item.decode(encoding) for item in row] 

Su sample_euro.csv se parece a cp1252 con un delimitador de coma. El ruso se parece a cp1251 con delimitador de punto y coma. A propósito, parece que de los contenidos también necesitará determinar qué formato de fecha se está utilizando y quizás también la moneda: el ejemplo ruso tiene cantidades de dinero seguidas de un espacio y la abreviatura cirílica para “rublos”.

Tenga en cuenta cuidadosamente: Resista todos los bashs de persuadirlo de que tiene archivos codificados en ISO-8859-1. Están codificados en cp1252.

Actualice en respuesta al comentario “” “Si entiendo lo que dice, debo saber la encoding para que esto funcione. En el caso general, no sabré la encoding y, según la otra respuesta, supongo que la encoding es muy Difícil, ¿así que estoy fuera de suerte? “” ”

Debe conocer la encoding para que CUALQUIER ejercicio de lectura de archivos funcione.

Adivinar la encoding correctamente todo el tiempo para cualquier encoding en un archivo de cualquier tamaño no es muy difícil, es imposible. Sin embargo, restringir el scope a los archivos csv guardados desde Excel o Open Office en la encoding predeterminada del entorno local del usuario, y de un tamaño razonable, no es una tarea tan grande. Yo sugeriría darle una oportunidad a Chardet ; adivina windows-1252 para su archivo euro y windows-1251 para su archivo ruso, un logro fantástico dado su pequeño tamaño.

La actualización 2 en respuesta al “” ” código de trabajo sería bienvenida” “”

Código de trabajo (Python 2.x):

 from chardet.universaldetector import UniversalDetector chardet_detector = UniversalDetector() def charset_detect(f, chunk_size=4096): global chardet_detector chardet_detector.reset() while 1: chunk = f.read(chunk_size) if not chunk: break chardet_detector.feed(chunk) if chardet_detector.done: break chardet_detector.close() return chardet_detector.result # Exercise for the reader: replace the above with a class import csv import sys from pprint import pprint pathname = sys.argv[1] delim = sys.argv[2] # allegedly known print "delim=%r pathname=%r" % (delim, pathname) with open(pathname, 'rb') as f: cd_result = charset_detect(f) encoding = cd_result['encoding'] confidence = cd_result['confidence'] print "chardet: encoding=%s confidence=%.3f" % (encoding, confidence) # insert actions contingent on encoding and confidence here f.seek(0) csv_reader = csv.reader(f, delimiter=delim) for bytes_row in csv_reader: unicode_row = [x.decode(encoding) for x in bytes_row] pprint(unicode_row) 

Salida 1:

 delim=',' pathname='sample-euro.csv' chardet: encoding=windows-1252 confidence=0.500 [u'31-01-11', u'Overf\xf8rsel utland', u'UTLBET; ID 9710032001647082', u'1990.00', u''] [u'31-01-11', u'Overf\xf8ring', u'OVERF\xd8RING MELLOM EGNE KONTI', u'5750.00', u';'] 

Salida 2:

 delim=';' pathname='sample-russian.csv' chardet: encoding=windows-1251 confidence=0.602 [u'-', u'04.02.2011 23:20', u'300,00\xa0\u0440\u0443\u0431.', u'', u'\u041c\u0422\u0421', u''] [u'-', u'04.02.2011 23:15', u'450,00\xa0\u0440\u0443\u0431.', u'', u'\u041e\u043f\u043b\u0430\u0442\u0430 Interzet', u''] [u'-', u'13.01.2011 02:05', u'100,00\xa0\u0440\u0443\u0431.', u'', u'\u041c\u0422\u0421 kolombina', u''] 

Actualización 3 ¿Cuál es la fuente de estos archivos? Si se están “guardando como CSV” desde Excel o OpenOffice Calc o Gnumeric, podría evitar todo el dtwig de encoding guardándolos como “Libro de Excel 97-2003 (* .xls)” y usar xlrd para leerlos. Esto también ahorraría la molestia de tener que inspeccionar cada archivo csv para determinar el delimitador (coma vs punto y coma), formato de fecha (31-01-11 vs 04.02.2011) y “punto decimal” (5750.00 vs 450,00) – – todas esas diferencias probablemente se crean al guardar como CSV . [Dis] reclamante: Soy el autor de xlrd .

No sé si ya has probado esto, pero en la sección de ejemplo de la documentación oficial de Python para el módulo csv, encontrarás un par de clases; UnicodeReader y UnicodeWriter . Trabajaron bien para mí hasta ahora.

Detectar correctamente la encoding de un archivo parece ser un problema muy difícil. Puedes leer la discusión en este hilo de StackOverflow .

Usted está haciendo lo incorrecto en su código al intentar .encode('utf-8') , debería decodificarlo en su lugar. Y por cierto, unicode(bytestr, 'utf-8') == bytestr.decode('utf-8')

Pero lo más importante, ¿POR QUÉ estás tratando de decodificar las cadenas?

Suena un poco absurdo, pero puedes trabajar con esos CSV sin importar si son cp1251, cp1252 o utf-8. La belleza de todo esto es que los caracteres regionales son> 0x7F y utf-8 también, usa secuencias de> 0x7F para representar símbolos que no son ASCII.

Dado que los separadores a los que CSV se preocupa (ya sea, o; o \ n) están dentro de ASCII, su trabajo no se verá afectado por la encoding utilizada (siempre que sea de un byte o utf-8!).

Lo importante a tener en cuenta es que debe entregar a Python 2.x los archivos del módulo csv abiertos en modo binary , es decir, ‘rb’ o ‘wb’, debido a la peculiar forma en que se implementó.

Lo que pides es imposible. No hay forma de escribir un progtwig en ningún idioma que acepte entradas en una encoding desconocida y las convierta correctamente a la representación interna de Unicode.

Tienes que encontrar una manera de decirle a la aplicación qué encoding usar.

Es posible reconocer muchos, pero no todos, los códigos de encoding, pero realmente depende de cuál es el contenido de los archivos y si hay suficientes puntos de datos. Esto es similar al problema de decodificar correctamente los nombres de archivos en los servidores de red. Cuando se crea un archivo en un servidor de red, no hay manera de decirle al servidor qué encoding se utiliza, por lo que si tiene una carpeta con nombres en varias codificaciones, se garantiza que parezcan impares para algunos, si no todos, usuarios y diferentes. Los archivos parecerán impares.

Sin embargo, no te rindas. Pruebe el detector de encoding Chardet mencionado en esta pregunta: https://serverfault.com/questions/82821/how-to-tell-the-language-encoding-of-a-nombre_de_archivo-on-linux y si tiene suerte, puede No obtendrá muchos fracasos.