2009-12-04 8 views
43

Il modulo csv in Python non funziona correttamente quando è coinvolto UTF-8/Unicode. Ho trovato, nello Python documentation e in altre pagine web, frammenti che funzionano per casi specifici ma devi capire bene quale codifica stai gestendo e utilizzare lo snippet appropriato.Supporto Unicode/UTF-8 generale per i file csv in Python 2.6

Come posso leggere e scrivere stringhe e stringhe Unicode da file .csv che "funziona correttamente" in Python 2.6? O si tratta di una limitazione di Python 2.6 che non ha una soluzione semplice?

risposta

7

Esiste già l'utilizzo dell'esempio Unicode in quello doc, perché è ancora necessario trovarne un altro o reinventare la ruota?

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') 
+9

Non funziona per me su linux: r = unicode_csv_reader (file ('/ tmp/csv-unicode.csv'). Read(). Split ('\ n')); r.next(). Gives: UnicodeDecodeError: il codec 'ascii' non può decodificare il byte 0xf8 in posizione 14: ordinale non nel range (128) – Parand

+2

neanche per me. Lo stesso errore. –

+1

Lo stesso qui, la risposta di Maxmin qui sotto funziona però:) – bjunix

52

Il codice di esempio di come leggere Unicode somministrato http://docs.python.org/library/csv.html#examples sembra essere obsoleto, in quanto non funziona con Python 2.6 e 2.7.

Segue il UnicodeDictReader che funziona con utf-8 e potrebbe essere con altre codifiche, ma l'ho testato solo su utf-8 input.

L'idea, in breve, è decodificare Unicode solo dopo che una riga csv è stata suddivisa in campi da csv.reader.

class UnicodeCsvReader(object): 
    def __init__(self, f, encoding="utf-8", **kwargs): 
     self.csv_reader = csv.reader(f, **kwargs) 
     self.encoding = encoding 

    def __iter__(self): 
     return self 

    def next(self): 
     # read and split the csv row into fields 
     row = self.csv_reader.next() 
     # now decode 
     return [unicode(cell, self.encoding) for cell in row] 

    @property 
    def line_num(self): 
     return self.csv_reader.line_num 

class UnicodeDictReader(csv.DictReader): 
    def __init__(self, f, encoding="utf-8", fieldnames=None, **kwds): 
     csv.DictReader.__init__(self, f, fieldnames=fieldnames, **kwds) 
     self.reader = UnicodeCsvReader(f, encoding=encoding, **kwds) 

Usage (codifica sorgente è utf-8):

csv_lines = (
    "абв,123", 
    "где,456", 
) 

for row in UnicodeCsvReader(csv_lines): 
    for col in row: 
     print(type(col), col) 

uscita:

$ python test.py 
<type 'unicode'> абв 
<type 'unicode'> 123 
<type 'unicode'> где 
<type 'unicode'> 456 
+0

Ha funzionato molto bene per me. Ho aggiunto BOM ('codecs.BOM_UTF8') all'inizio dello streaming durante il salvataggio per Excel. (L'unico problema residuo con cui sto combattendo è il motivo per cui sto perdendo alcune interruzioni di riga quando salvo e poi ri-caricamento, ma potrebbe essere colpa mia.) – Soferio

3

L'involucro unicode_csv_reader menzionato nella python documentation accetta stringhe Unicode. Questo perché csv non accetta le stringhe Unicode. cvs probabilmente non è a conoscenza della codifica o delle impostazioni internazionali e considera solo le stringhe che ottiene come byte. Quindi, ciò che accade è che il wrapper codifica le stringhe Unicode, il che significa che crea una stringa di byte. Quindi, quando il wrapper restituisce i risultati da csv, decodifica nuovamente i byte, ovvero converte le sequenze di byte UTF-8 nei caratteri Unicode corretti.

Se si fornisce al wrapper una stringa di byte semplice, ad es. usando f.readlines() darà un UnicodeDecodeError su byte con valore> 127. Si utilizzerà il wrapper nel caso in cui si abbiano stringhe Unicode nel proprio programma che sono nel formato CSV.

Posso immaginare che il wrapper abbia ancora una limitazione: poiché cvs non accetta unicode, e non accetta anche delimitatori multi-byte, non è possibile analizzare i file che hanno un carattere unicode come delimitatore.

22

Il modulo fornito here, si presenta come una sostituzione semplice, semplice, di sostituzione per il modulo csv che consente di lavorare con utf-8 csv.

import ucsv as csv 
with open('some.csv', 'rb') as f: 
    reader = csv.reader(f) 
    for row in reader: 
     print row 
+1

Grazie a questo, include anche l'utilissima interfaccia 'DictReader', che ti permette di trattare ogni riga del file CSV come un dizionario in cui le chiavi sono prese dai nomi di campo nella prima riga: –

+1

Per alcuni usi, devi stare attento con 'ucsv', dal momento che cerca di convertire" dall'aspetto numerico "dati per te. (È abbastanza semplice modificare 'ucsv' per non farlo, ma devi solo essere consapevole che è lì.) –

+0

Ho ancora errori di unicode con questo modulo del modulo' UnicodeDecodeError: 'utf8' codec non può decodificare il byte 0xc9 in posizione 179: byte di continuazione non valido ' – Mittenchops

32

Un po 'in ritardo risposta, ma ho usato unicodecsv con grande successo.

+2

Non l'ho testato troppo, ma questo pacchetto ha un paio di vantaggi rispetto a 'ucsv' menzionato nella risposta di @ itsadok: (1) È completamente impacchettato per uso di produzione, inclusi test; e (2) cerca veramente ** solo ** di aggiungere la consapevolezza di Unicode al modulo 'csv', invece di convertire silenziosamente qualsiasi valore che può in numeri (un po 'come Excel). Certo, alcune persone possono * gradire * la conversione automatica, ma non è qualcosa che il modulo 'csv' di Python abbia mai inteso. –

+2

nota che 'unicodecsv' può essere installato con' pip' – drevicko

+1

unicodecsv.DictReader ha un enorme trucco. È necessario aprire il file come binario affinché funzioni come previsto con unicode. In caso contrario, sarà ancora ottenere un UnicodeEncodeError quando si accede a una determinata riga. filehandle = io.open (file_path, "rb") my_unicode_dictionary = unicodecsv.DictReader (FILEHANDLE) fila = my_unicode_dictionary.next() –

4

Confermo, unicodecsv è un ottimo sostituto per il modulo csv, ho appena sostituito csv da unicodecsv nel mio codice sorgente, e funziona come un fascino.

+1

Prova ad aggiungere un commento alla risposta che stai accettando invece di creare una nuova risposta (una volta che avrai più di 50rip) –

+0

Lo so, ma non ho abbastanza reputazione per rispondere sotto una risposta :-(Tuttavia, dovresti votare per la mia risposta per guadagnare reputazione ;-) – GMLudo

2

Si dovrebbe considerare tablib, che ha un approccio completamente diverso, ma dovrebbe essere considerato sotto il requisito "solo funziona".

with open('some.csv', 'rb') as f: 
    csv = f.read().decode("utf-8") 

import tablib 
ds = tablib.Dataset() 
ds.csv = csv 
for row in ds.dict: 
    print row["First name"] 

Attenzione: tablib rifiuterà il tuo csv se non ha lo stesso numero di elementi su ogni riga.

2

Forse questo è palesemente ovvio, ma per ragioni di principianti ne parlerò.

In python 3.X csv modulo supports any encoding out of the box, quindi se si utilizza questa versione è possibile attenersi al modulo standard.

with open("foo.csv", encoding="utf-8") as f: 
    r = csv.reader(f, delimiter=";") 
    for row in r: 
    print(row) 

Per discussione supplementare si prega di vedere: Does python 3.1.3 support unicode in csv module?

0

vorrei aggiungere alla risposta di itsadok. Di default, excel salva i file csv come latin-1 (che ucsv non supporta). Si può facilmente risolvere questo problema:

with codecs.open(csv_path, 'rb', 'latin-1') as f: 
    f = StringIO.StringIO(f.read().encode('utf-8')) 

reader = ucsv.UnicodeReader(f) 
# etc. 
1

Ecco una versione leggermente migliorata del Maxim's answer, che può anche saltare il BOM UTF-8:

import csv 
import codecs 

class UnicodeCsvReader(object): 
    def __init__(self, csv_file, encoding='utf-8', **kwargs): 
     if encoding == 'utf-8-sig': 
      # convert from utf-8-sig (= UTF8 with BOM) to plain utf-8 (without BOM): 
      self.csv_file = codecs.EncodedFile(csv_file, 'utf-8', 'utf-8-sig') 
      encoding = 'utf-8' 
     else: 
      self.csv_file = csv_file 
     self.csv_reader = csv.reader(self.csv_file, **kwargs) 
     self.encoding = encoding 

    def __iter__(self): 
     return self 

    def next(self): 
     # read and split the csv row into fields 
     row = self.csv_reader.next() 
     # now decode 
     return [unicode(cell, self.encoding) for cell in row] 

    @property 
    def line_num(self): 
     return self.csv_reader.line_num 

class UnicodeDictReader(csv.DictReader): 
    def __init__(self, csv_file, encoding='utf-8', fieldnames=None, **kwds): 
     reader = UnicodeCsvReader(csv_file, encoding=encoding, **kwds) 
     csv.DictReader.__init__(self, reader.csv_file, fieldnames=fieldnames, **kwds) 
     self.reader = reader 

nota che la presenza del BOM non è rilevato automaticamente. È necessario segnalare che è lì passando l'argomento encoding='utf-8-sig' al costruttore di UnicodeCsvReader o UnicodeDictReader. La codifica utf-8-sig è utf-8 con una distinta base.