2012-02-07 30 views
8

Tengo un archivo CSV UTF-16 que tengo que leer. El módulo Python csv no parece ser compatible con UTF-16.Lector Python UTF-16 CSV

Estoy usando python 2.7.2. Los archivos CSV que necesito analizar son de gran tamaño y se ejecutan en varios GB de datos.

respuestas para preguntas John Machin debajo

print repr(open('test.csv', 'rb').read(100)) 

salida con test.csv tener sólo abc como contenido

'\xff\xfea\x00b\x00c\x00' 

creo archivo csv consiguió creado en la máquina ventanas en EE.UU.. Estoy usando Mac OSX Lion.

Si utilizo el código provisto por phihag y test.csv que contiene un registro.

muestra contenido de prueba.csv utilizado. A continuación se repr impresión (abierta ('test.csv', 'rb'). Leer (1000)) de salida

'\xff\xfe1\x00,\x002\x00,\x00G\x00,\x00S\x00,\x00H\x00 \x00f\x00\xfc\x00r\x00 \x00e\x00 \x00\x96\x00 \x00m\x00 \x00\x85\x00,\x00,\x00I\x00\r\x00\n\x00' 

Código de phihag

import codecs 
import csv 
with open('test.csv','rb') as f: 
     sr = codecs.StreamRecoder(f,codecs.getencoder('utf-8'),codecs.getdecoder('utf-8'),codecs.getreader('utf-16'),codecs.getwriter('utf-16'))  
     for row in csv.reader(sr): 
     print row 

salida del código anterior

['1', '2', 'G', 'S', 'H f\xc3\xbcr e \xc2\x96 m \xc2\x85'] 
['', '', 'I'] 

salida esperada es

['1', '2', 'G', 'S', 'H f\xc3\xbcr e \xc2\x96 m \xc2\x85','','I'] 

Respuesta

28

En los momen t, el módulo csv no es compatible con UTF-16.

En Python 3.x, csv espera un archivo en modo texto y se puede simplemente utilizar el parámetro de codificación de open para obligar a otro sistema de codificación:

# Python 3.x only 
import csv 
with open('utf16.csv', 'r', encoding='utf16') as csvf: 
    for line in csv.reader(csvf): 
     print(line) # do something with the line 

En Python 2.x, puede recodificar la entrada :

# Python 2.x only 
import codecs 
import csv 

class Recoder(object): 
    def __init__(self, stream, decoder, encoder, eol='\r\n'): 
     self._stream = stream 
     self._decoder = decoder if isinstance(decoder, codecs.IncrementalDecoder) else codecs.getincrementaldecoder(decoder)() 
     self._encoder = encoder if isinstance(encoder, codecs.IncrementalEncoder) else codecs.getincrementalencoder(encoder)() 
     self._buf = '' 
     self._eol = eol 
     self._reachedEof = False 

    def read(self, size=None): 
     r = self._stream.read(size) 
     raw = self._decoder.decode(r, size is None) 
     return self._encoder.encode(raw) 

    def __iter__(self): 
     return self 

    def __next__(self): 
     if self._reachedEof: 
      raise StopIteration() 
     while True: 
      line,eol,rest = self._buf.partition(self._eol) 
      if eol == self._eol: 
       self._buf = rest 
       return self._encoder.encode(line + eol) 
      raw = self._stream.read(1024) 
      if raw == '': 
       self._decoder.decode(b'', True) 
       self._reachedEof = True 
       return self._encoder.encode(self._buf) 
      self._buf += self._decoder.decode(raw) 
    next = __next__ 

    def close(self): 
     return self._stream.close() 

with open('test.csv','rb') as f: 
    sr = Recoder(f, 'utf-16', 'utf-8') 

    for row in csv.reader(sr): 
     print (row) 

open y codecs.open requieren el archivo para comenzar con una lista de materiales. Si no lo hace (o estás en Python 2.x), todavía se puede convertir en la memoria, así:

try: 
    from io import BytesIO 
except ImportError: # Python < 2.6 
    from StringIO import StringIO as BytesIO 
import csv 
with open('utf16.csv', 'rb') as binf: 
    c = binf.read().decode('utf-16').encode('utf-8') 
for line in csv.reader(BytesIO(c)): 
    print(line) # do something with the line 
+0

Gracias @phihag por su respuesta. ¿Hay alguna manera de hacer esto sin cargar el archivo en la memoria? Mi archivo csv es enorme. – venky

+0

@venky Actualizado con un truco que debería funcionar en 2.x. – phihag

+0

¿cómo sé si el archivo está comenzando con una lista de [email protected] – venky

-1

Sólo tiene que abrir su archivo con codecs.open como en

import codecs, csv 

stream = codecs.open(<yourfile.csv>, encoding="utf-16") 
reader = csv.reader(stream) 

Y trabaje a través de su programa con cadenas de Unicode, como should do anyway if you are processing text

+0

para registro en csv.reader (stream): excepción de tiro de línea UnicodeEncodeError: códec 'ascii' no puede codificar el carácter u '\ xed' en la posición 77: ordinal no en rango (128) – venky

+0

Esto funciona bien en Python 3.x (aunque uno podría simplemente escribir 'open' en lugar de' codecs.open'), pero falla en 2.x porque 'csv' intenta volver a codificar los caracteres unicode que lee de la transmisión. – phihag

3

Le sugiero que recodifique sus archivos a UTF-8. En la condición muy probable de que no tenga ningún carácter Unicode fuera del BMP, puede aprovechar el hecho de que UTF-16 es una codificación de longitud fija para leer bloques de longitud fija desde su archivo de entrada sin preocuparse por el bloqueo a horcajadas límites.

Paso 1: Determine qué codificación tiene en realidad.Examinar los primeros bytes del archivo:

print repr(open('thefile.csv', 'rb').read(100))

cuatro posibles formas de codificación u'abc'

\xfe\xff\x00a\x00b\x00c -> utf_16 
\xff\xfea\x00b\x00c\x00 -> utf_16 
\x00a\x00b\x00c -> utf_16_be 
a\x00b\x00c\x00 -> utf_16_le 

Si usted tiene algún problema con este paso, editar su pregunta para incluir los resultados de las anteriores print repr()

Paso 2: Aquí hay un Python 2.X recodificar-UTF-16 * -to-UTF-8 script:

import sys 
infname, outfname, enc = sys.argv[1:4] 
fi = open(infname, 'rb') 
fo = open(outfname, 'wb') 
BUFSIZ = 64 * 1024 * 1024 
first = True 
while 1: 
    buf = fi.read(BUFSIZ) 
    if not buf: break 
    if first and enc == 'utf_16': 
     bom = buf[:2] 
     buf = buf[2:] 
     enc = {'\xfe\xff': 'utf_16_be', '\xff\xfe': 'utf_16_le'}[bom] 
     # KeyError means file doesn't start with a valid BOM 
    first = False 
    fo.write(buf.decode(enc).encode('utf8')) 
fi.close() 
fo.close() 

Otros asuntos:

podría decir que los archivos son demasiado grandes para leer el archivo completo, recodificar y volver a escribir, sin embargo, puede abrirlo en vi. Por favor explique.

El < 85> Ser tratado como fin de registro es un poco preocupante. Parece que 0x85 se reconoce como NEL (código de control C1, NEWLINE). Existe una gran posibilidad de que los datos estuvieran originalmente codificados en alguna codificación heredada de un byte, donde 0x85 tiene un significado pero ha sido transcodificado a UTF-16 bajo la falsa suposición de que la codificación original era ISO-8859-1 también conocida como latin1. ¿De dónde se originó el archivo? ¿Un mainframe de IBM? Windows/Unix/classic Mac? ¿Qué país, lugar, idioma? Usted obviamente piensa que el < 85> no está destinado a ser una nueva línea; ¿Qué piensas que significa?

dude en enviar una copia de un archivo de corte hacia abajo (que incluye algunos de los < 85> cosas) a sjmachin at lexicon dot net

actualización basado en datos de la muestra 1-linea provistos.

Esto confirma mis sospechas. Lee this. He aquí una cita de él:

... the C1 control characters ... are rarely used directly, except on specific platforms such as OpenVMS. When they turn up in documents, Web pages, e-mail messages, etc., which are ostensibly in an ISO-8859-n encoding, their code positions generally refer instead to the characters at that position in a proprietary, system-specific encoding such as Windows-1252 or the Apple Macintosh ("MacRoman") character set that use the codes provided for representation of the C1 set with a single 8-bit byte to instead provide additional graphic characters

este código:

s1 = '\xff\xfe1\x00,\x002\x00,\x00G\x00,\x00S\x00,\x00H\x00 \x00f\x00\xfc\x00r\x00 \x00e\x00 \x00\x96\x00 \x00m\x00 \x00\x85\x00,\x00,\x00I\x00\r\x00\n\x00' 
s2 = s1.decode('utf16') 
print 's2 repr:', repr(s2) 
from unicodedata import name 
from collections import Counter 
non_ascii = Counter(c for c in s2 if c >= u'\x80') 
print 'non_ascii:', non_ascii 
for c in non_ascii: 
    print "from: U+%04X %s" % (ord(c), name(c, "<no name>")) 
    c2 = c.encode('latin1').decode('cp1252') 
    print "to: U+%04X %s" % (ord(c2), name(c2, "<no name>")) 

s3 = u''.join(
    c.encode('latin1').decode('1252') if u'\x80' <= c < u'\xA0' else c 
    for c in s2 
    ) 
print 's3 repr:', repr(s3) 
print 's3:', s3 

produce lo siguiente (Python 2.7.2 IDLE, Windows 7):

s2 repr: u'1,2,G,S,H f\xfcr e \x96 m \x85,,I\r\n' 
non_ascii: Counter({u'\x85': 1, u'\xfc': 1, u'\x96': 1}) 
from: U+0085 <no name> 
to: U+2026 HORIZONTAL ELLIPSIS 
from: U+00FC LATIN SMALL LETTER U WITH DIAERESIS 
to: U+00FC LATIN SMALL LETTER U WITH DIAERESIS 
from: U+0096 <no name> 
to: U+2013 EN DASH 
s3 repr: u'1,2,G,S,H f\xfcr e \u2013 m \u2026,,I\r\n' 
s3: 1,2,G,S,H für e – m …,,I 

¿Qué lo hace pensar es una interpretación más razonable de \x96:

SPA es decir, inicio del área protegida (Utilizado por el bloque-ori Terminales ented.)
o
EN DASH
?

Parece que se justifica un análisis exhaustivo de una muestra de datos mucho mayor. Feliz de ayudar.

+0

Pregunta actualizada más detalles – venky

+0

@venky: respuesta actualizada. –

4

La documentación del módulo Python 2.x csvexample muestra cómo manejar otras codificaciones.

+1

Lo que la documentación realmente dice es: "Así que puedes escribir funciones o clases que manejen la codificación y decodificación por ti siempre que evites codificaciones como UTF-16 que usan NUL". –

+0

@Antony leíste el último ejemplo? Recodifica CUALQUIER codificación como UTF-8 antes de pasarla al módulo csv. –

+0

Sí, el problema se aborda en unas pocas líneas que hacen casi lo mismo que el código de la respuesta de @ phihag. Sin embargo, citaría el ejemplo explícitamente para facilitar la vida del lector :) Se eliminó el voto negativo. –