2009-07-27 25 views
18

Estoy tratando de traducir una hoja de cálculo de Excel a CSV utilizando los módulos Python xlrd y csv, pero me estoy colgando de problemas de codificación. Xlrd produce salida desde Excel en Unicode, y el módulo CSV requiere UTF-8.Unicode a UTF8 para archivos CSV - Python a través de xlrd

Imagine que esto no tiene nada que ver con el módulo xlrd: todo funciona bien para salidas stdout u otras salidas que no requieren una codificación específica.

La hoja de trabajo se codifica como UTF-16-LE, de acuerdo con book.encoding

La versión simplificada de lo que estoy haciendo es:

from xlrd import * 
import csv 
b = open_workbook('file.xls') 
s = b.sheet_by_name('Export') 
bc = open('file.csv','w') 
bcw = csv.writer(bc,csv.excel,b.encoding) 
for row in range(s.nrows): 
    this_row = [] 
    for col in range(s.ncols): 
     this_row.append(s.cell_value(row,col)) 
    bcw.writerow(this_row) 

Esto produce el siguiente error, alrededor de 740 líneas en :

UnicodeEncodeError: 'ascii' codec can't encode character u'\xed' in position 5: ordinal not in range(128) 

el valor es parece ser cada vez colgó en que es "516-777316" - el texto en la hoja de Excel original es "516-7.773.167" (con un 7 en el extremo)

seré el primero en admitir que sólo tengo una vaga sensación de cómo funciona la codificación de caracteres, por lo que la mayor parte de lo que he probado hasta ahora son varias permutaciones torpes de .encode y .decode en la s.cell_value(row,col)

Si alguien pudiera sugerir una solución, la agradecería, incluso mejor si pudiera proporcionar una explicación de lo que no funciona y por qué, de modo que pueda depurar estos problemas con mayor facilidad en el futuro.

¡Gracias de antemano!

EDIT:

Gracias por los comentarios hasta el momento.

Cuando utilizo this_row.append(s.cell(row,col)) (por ejemplo, s.cell en lugar de s.cell_value), todo el documento se escribe sin errores.

La salida no es particularmente deseable (text:u'516-7773167'), pero evita el error aunque los caracteres ofensivos aún estén en la salida.

Esto me hace pensar que el desafío podría estar en xlrd después de todo.

¿Pensamientos?

+0

sería útil para ver todo el rastreo, para saber que está tirando el error. – Christopher

+0

No hay mucho más que ver: Archivo "the_script.py ", línea 40, en this_row.append (str (s.cell_value (row, col))) UnicodeEncodeError: el códec 'ascii' no puede codificar el carácter u '\ xed' en la posición 5: ordinal not in range (128) – anschauung

+0

con "salida de Excel en Unicode", parece que el sentido de "salida de Excel en UTF-16". Unicode define un codespace, que está representado por los diferentes sistemas de codificación, como UTF-8 o UTF-16. – Svante

Respuesta

25

espero que el valor de retorno cell_value es la cadena Unicode que te está dando problemas (en letra de imprenta su type() confirmar que), en cuyo caso debe ser capaz de resolverlo cambiando esta línea:

this_row.append(s.cell_value(row,col)) 

a:

this_row.append(s.cell_value(row,col).encode('utf8')) 

Si se cell_value devolver varios tipos diferentes, entonces usted necesita para codificar si y sólo si se trata de devolver una cadena unicode; así que es dividir esta línea en unas pocas líneas:

val = s.cell_value(row, col) 
if isinstance(val, unicode): 
    val = val.encode('utf8') 
this_row.append(val) 
+0

¡Perfecto! Eso lo hizo. Supongo que no esperaba que los diferentes tipos de valores tuvieran que ser tratados de manera diferente. ¡Gracias! – anschauung

0

Parece haber dos posibilidades. Una es que quizás no haya abierto correctamente el archivo de salida:

"Si csvfile es un objeto de archivo, debe abrirse con la bandera 'b' en las plataformas donde eso hace la diferencia." (http://docs.python.org/library/csv.html#module-csv)

Si ese no es el problema, entonces otra opción para usted es utilizar codecs.EncodedFile (archivo, entrada [, la salida [errores]]) como un contenedor para dar salida a su .csv:

http://docs.python.org/library/codecs.html#module-codecs

Esto le permitirá tener el filtro objeto de archivo de UTF16 entrante a UTF8. Si bien ambos son técnicamente "unicode", la forma en que codifican es muy diferente.

Algo como esto:

rbc = open('file.csv','w') 
bc = codecs.EncodedFile(rbc, "UTF16", "UTF8") 
bcw = csv.writer(bc,csv.excel) 

puede resolver el problema para usted, asumiendo entendí el problema de la derecha, y suponiendo que el error es lanzada al escribir en el archivo.

+0

Bueno, dio un mensaje de error diferente (esta vez antes de escribir nada en el fichero): UnicodeDecodeError: códec 'utf16' no puede decodificar 0x0a byte en la posición 938: datos truncados – anschauung

0

Parece que tienes 2 problemas.

Hay algo estropeado en esa celda - '7' se codificarán como u'x37' Pienso, ya que es dentro del rango ASCII.

Más importante, sin embargo, el hecho de que usted está recibiendo un mensaje de error que se especifica que el códec ascii no se puede utilizar sugiere que algo anda mal con su codificación en Unicode - que supone que usted está tratando de codificar un valor 0xed que puede No estarás representado en ASCII, pero dijiste que intentabas representarlo en Unicode.

No soy lo suficientemente inteligente como para averiguar qué línea particular está causando el problema. Si edita su pregunta para decirme qué línea está causando ese mensaje de error, podría ayudar un poco más (supongo que es this_row.append(s.cell_value(row,col)) o bcw.writerow(this_row), pero le agradecería que lo confirmara).

+0

Gracias! El error está en bcw.writerow. Todo sale correctamente si, por ejemplo, uso print this_row. Como mejor que puedo decir, no hay nada obviamente mal con el '7' - emite correctamente (como u'516-7773167') al imprimir en la salida estándar. – anschauung

+0

entonces parece que 'bcw.writerow' espera ASCII - ¿estás seguro de que tienes sus argumentos a' csv.writer' correcta (ver http://docs.python.org/library/csv.html#csv. escritor)? Estoy desconcertado de dónde viene el '0xed'. –

9

Usted pidió explicaciones, pero algunos de los fenómenos inexplicables sin su ayuda.

Cuerdas (A) en archivos XLS creados por Excel 97 en adelante se codifican en Latin1 si es posible de otra manera en UTF16LE. Cada cuerda lleva una bandera que indica cuál fue utilizada. Anteriormente supera las cadenas codificadas de acuerdo con la "página de códigos" del usuario. En cualquier caso, xlrd produce objetos Unicode. La codificación del archivo es de interés solo cuando el archivo XLS ha sido creado por un software de terceros que omite la página de códigos o miente al respecto. Consulte la sección Unicode en la parte frontal de los documentos xlrd.

(B) fenómeno inexplicable:

este código:

bcw = csv.writer(bc,csv.excel,b.encoding) 

provoca el siguiente error con Python 2.5, 2.6 y 3.1: TypeError: expected at most 2 arguments, got 3 - esto es sobre lo que cabe esperar dado los documentos en csv.writer; está esperando un objeto similar a un archivo seguido de (1) nada (2) un dialecto o (3) uno o más parámetros de formato. Le diste un dialecto y csv.writer no tiene argumento de codificación, así que splat. ¿Qué versión de Python estás usando? ¿O no copió/pegó el guión que realmente ejecutó?

(C) Fenómenos inexplicables alrededor de rastreo y lo que el infractor de datos real fue:

"the_script.py", line 40, in <module> 
this_row.append(str(s.cell_value(row,col))) 
UnicodeEncodeError: 'ascii' codec can't encode character u'\xed' in position 5: ordinal not in range(128) 

primer lugar, no es un str() en la línea de código erróneo, que no estaba en el guión simplificado - ¿verdad no copiar/pegar la secuencia de comandos que realmente ejecutó? En cualquier caso, no debe usar str en general, no obtendrá la precisión completa en sus flotadores; simplemente deja que el módulo csv los convierta.

En segundo lugar, se dice "" "El valor es parece ser cada vez colgó en que es '516-777316' - el texto en la hoja de Excel original es '516 a 7.773.167' (con un 7 en el extremo) "" "--- es difícil imaginar cómo se pierde el 7 al final. Me gustaría usar algo como esto para saber exactamente cuál era la problemática de datos:

try: 
    str_value = str(s.cell_value(row, col)) 
except: 
    print "row=%d col=%d cell_value=%r" % (row, col, s.cell_value(row, col)) 
    raise 

Eso% r le ahorra escribir cell_value=%s ... repr(s.cell_value(row, col)) ... la repr() produce una representación inequívoca de sus datos. Aprenderlo. Úselo.

¿Cómo llegaste a "516-777316"?

EN TERCER lugar, el mensaje de error en realidad se está quejando sobre un carácter unicode u '\ xed' en el desplazamiento 5 (es decir, el sexto carácter). U + 00ED es LETRA I MINÚSCULA LATINA CON AGUDA, y no hay nada como eso en "516-7773167"

CUARTO lugar, la ubicación del error parece ser un objetivo en movimiento - dijiste en un comentario sobre uno de los soluciones: "El error está en bcw.writerow". ¿Huh?

(D) Por qué aparece ese mensaje de error (con str(): str(a_unicode_object) intenta convertir el objeto Unicode en un objeto str y en ausencia de cualquier información de codificación utiliza ascii, pero tiene datos que no son ascii, así que splat. Tenga en cuenta que su objetivo es producir un archivo csv codificado en utf8, pero su script simplificado no menciona utf8 en ninguna parte.

(E) "" "... en la celda (fila, col)) (egscell en lugar de s.cell_value) todo el documento se escribe sin errores. El resultado no es particularmente deseable (texto: u'516-7773167 ') ''"

Eso está sucediendo porque el escritor csv está llamando el método de tu teléfono objeto __str__, y esto produce <type>:<repr(value)> que puede ser útil para la depuración, pero como no se dice tan grande en su archivo csv.

(F) La solución de Alex Martelli es genial porque te ayudó. Sin embargo, debes leer la sección de la clase Cell en los documentos xlrd: los tipos de celda son texto, número, booleano, fecha, error, en blanco y vacío. yo Tiene fechas, va a querer formatearlas como fechas, no como números, por lo que no puede usar isinstance() (y es posible que no desee que la llamada funcione de todos modos) ... esto es lo que el Cell.ctype atributo y Sheet.cell_type() y Sheet.row_types() métodos son para.

(G) UTF8 no es Unicode. UTF16LE no es Unicode. UTF16 no es Unicode ... y la idea de que cadenas individuales desperdiciarían 2 bytes cada una en una lista de materiales UTF16 es demasiado absurda para que incluso MS la contemple :-)

(H) Lectura adicional (aparte de los documentos xlrd):

http://www.joelonsoftware.com/articles/Unicode.html 
http://www.amk.ca/python/howto/unicode 
+1

+1: Gracias por la gran explicación y los enlaces de fondo. Esto me ha hecho darme cuenta de que no puedo evitar educarme sobre la codificación por más tiempo, y agradezco que haya entrado en detalles incluso después de que se haya resuelto el problema inmediato. – anschauung