¿Manejar nuevas líneas adicionales (retornos de carro) en archivos csv analizados con Python?

Tengo un archivo CSV que tiene campos que contienen nuevas líneas, por ejemplo:

A, B, C, D, E, F 123, 456, tree , very, bla, indigo 

(En este caso, el tercer campo de la segunda fila es “tree \ n”

Intenté lo siguiente:

 import csv catalog = csv.reader(open('test.csv', 'rU'), delimiter=",", dialect=csv.excel_tab) for row in catalog: print "Length: ", len(row), row 

y el resultado que obtuve fue este:

 Length: 6 ['A', ' B', ' C', ' D', ' E', ' F'] Length: 3 ['123', ' 456', ' tree'] Length: 4 [' ', ' very', ' bla', ' indigo'] 

¿Alguien tiene alguna idea de cómo puedo eliminar rápidamente nuevas líneas extrañas?

¡Gracias!

Supongamos que tiene esta hoja de cálculo de Excel:

'Gottchas' comunes en un archivo de Excel

Nota:

  1. la celda multilínea en C2;
  2. coma incrustada en C1 y D3;
  3. celdas en blanco, y celdas con un espacio en D4.

Guardando eso como CSV en Excel, obtendrá este archivo csv:

 A1,B1,"C1,+comma",D1 ,B2,"line 1 line 2",D2 ,,C3,"D3,+comma" ,,,D4 space 

Supuestamente, querrá leer eso en Python con las celdas en blanco que aún tienen significado y la coma incrustada tratada correctamente.

Así que esto:

 with open("test.csv", 'rU') as csvIN: outCSV=(line for line in csv.reader(csvIN, dialect='excel')) for row in outCSV: print("Length: ", len(row), row) 

produce correctamente la matriz de Lista de Lista 4×4 representada en Excel:

 Length: 4 ['A1', 'B1', 'C1,+comma', 'D1'] Length: 4 ['', 'B2', 'line 1\nline 2', 'D2'] Length: 4 ['', '', 'C3', 'D3,+comma'] Length: 4 ['', '', '', 'D4 space'] 

El archivo CSV de ejemplo que publicó carece de citas en el campo con una ‘nueva línea adicional’ que hace que el significado de esa nueva línea sea ambiguo. ¿Es una nueva fila o un campo multilínea?

Por lo tanto, solo puedes interpretar este archivo csv:

 A, B, C, D, E, F 123, 456, tree , very, bla, indigo 

como una lista de una dimensión como tal:

 with open("test.csv", 'rU') as csvIN: outCSV=[field.strip() for row in csv.reader(csvIN, delimiter=',') for field in row if field] 

Lo que produce esta lista unidimensional:

 ['A', 'B', 'C', 'D', 'E', 'F', '123', '456', 'tree', 'very', 'bla', 'indigo'] 

Esto se puede interpretar y reagrupar en cualquier subgrupo que desee.

El método de reagrupamiento idiomático en python usa zip así:

 >>> zip(*[iter(outCSV)]*6) [('A', 'B', 'C', 'D', 'E', 'F'), ('123', '456', 'tree', 'very', 'bla', 'indigo')] 

O, si quieres una lista de listas, esto también es idiomático:

 >>> [outCSV[i:i+6] for i in range(0, len(outCSV),6)] [['A', 'B', 'C', 'D', 'E', 'F'], ['123', '456', 'tree', 'very', 'bla', 'indigo']] 

Si puede cambiar la forma en que se crea su archivo CSV, será menos ambiguo interpretar.

Esto funcionará si tienes celdas que no sean espacios en blanco.

 data = [['A', ' B', ' C', ' D', ' E', ' F'], ['123', ' 456', ' tree'], [' ', ' very', ' bla', ' indigo']] flat_list = chain.from_iterable(data) flat_list = [cell for cell in flat_list if cell.strip() != ''] # remove blank cells rows = [flat_list[i:i+6] for i in range(0, len(flat_list), 6)] # chunk into groups of 6 print rows 

Salida:

 [['A', ' B', ' C', ' D', ' E', ' F'], ['123', ' 456', ' tree', ' very', ' bla', ' indigo']] 

Si tiene celdas en blanco en la entrada, esto funcionará la mayor parte del tiempo:

 data = [['A', ' B', ' C', ' D', ' E', ' F'], ['123', ' 456', ' tree'], [' ', ' very', ' bla', ' indigo']] clean_rows = [] saved_row = [] for row in data: if len(saved_row): row_tail = saved_row.pop() row[0] = row_tail + row[0] # reconstitute field broken by newline row = saved_row + row # and reassemble the row (possibly only partially) if len(row) >= 6: clean_rows.append(row) saved_row = [] else: saved_row = row print clean_rows 

Salida:

 [['A', ' B', ' C', ' D', ' E', ' F'], ['123', ' 456', ' tree ', ' very', ' bla', ' indigo']] 

Sin embargo, incluso la segunda solución fallará con la entrada tal

 A,B,C,D,E,F\nG 1,2,3,4,5,6 

En este caso, la entrada es ambigua y ningún algoritmo podrá adivinar si se refería a:

 A,B,C,D,E,F G\n1,2,3,4,5,6 

(o la entrada que se da arriba)

Si este es su caso, tendrá que volver con la persona que está guardando los datos y hacer que los guarde en un formato más limpio (por cierto, la oficina abierta cita las líneas nuevas en archivos CSV mucho mejor que Excel).

Esto debería funcionar. (Advertencia: código comstackdo de cerebro)

 with open('test.csv', 'rU') as infile: data = [] for line in infile: temp_data = line.split(',') try: while len(temp_data) < 6: #column length temp_data.extend(infile.next()) except StopIteration: pass data.append(temp_data) 

Si el número de campos en cada fila es el mismo y los campos no pueden estar vacíos:

 from itertools import izip_longest nfields = 6 with open(filename) as f: fields = (field.strip() for line in f for field in line.split(',') if field) for row in izip_longest(*[iter(fields)]*nfields): # grouper recipe* print(row) 

* receta de mero

Salida

 ('A', 'B', 'C', 'D', 'E', 'F') ('123', '456', 'tree', 'very', 'bla', 'indigo') 

Esto funciona con el módulo CSV y limpia campos y líneas en blanco:

 import csv import StringIO data="""A, B, C, D, E, F 123, 456, tree ,, , very, bla, indigo""" f=StringIO.StringIO(data) #used just to simulate a file. Use your file here... reader = csv.reader(f) out=[] for line in reader: line=[x.strip() for x in line if x] # remove 'if x' if you want blank fields if len(line): out.append(line) print out 

Huellas dactilares:

 [['A', ' B', ' C', ' D', ' E', ' F'], ['123', '456', 'tree'], ['very', 'bla', 'indigo']] 

Si quieres eso en 6 porciones:

 cols=6 out=[i for sl in out for i in sl] # flatten out out=[out[i:i+cols] for i in range(0, len(out), cols)] # rechunk into 'cols' 

Huellas dactilares:

 [['A', 'B', 'C', 'D', 'E', 'F'], ['123', '456', 'tree', 'very', 'bla', 'indigo']] 

Si conoce el número de columnas, la mejor manera es ignorar el final de las líneas y luego dividir.

Algo como esto

 with open(filename, 'rU') as fp: data = ''.join(fp.readlines()) data = data.split(',') for n in range(0, len(data), 6) print(data[n:n+6]) 

Puedes convertirlo fácilmente en un generador si prefieres:

 def read_ugly_file(filename, delimiter=',', columns=6): with open(filename, 'rU') as fp: data = ''.join(fp.readlines()) data = data.split(delimiter) for n in range(0, len(data), columns) yield data[n:n+columns] for row in read_ugly_file('myfile.csv'): print(row)