Как очистить большой деформированный файл CSV с помощью Python

Я пытаюсь использовать Python 2.7.5 для очистки неправильного файла CSV. Файл CSV довольно большой (более 1 ГБ). В первой строке файла правильно указаны заголовки столбцов, но после этого каждое поле находится на новой строке (если оно не пустое), а некоторые поля являются многострочными. Многострочные поля не заключены в кавычки, но должны быть заключены в кавычки на выходе. Количество столбцов статично и известно. Шаблон в представленном образце ввода повторяется по всей длине файла.

Входной файл (образец):

Hostname,Username,IP Addresses,Timestamp,Test1,Test2,Test3
my_hostname
,my_username
,10.0.0.1
192.168.1.1
,2015-02-11 13:41:54 -0600
,,true
,false
my_2nd_hostname
,my_2nd_username
,10.0.0.2
192.168.1.2
,2015-02-11 14:04:41 -0600
,true
,,false

Желаемый результат:

Hostname,Username,IP Addresses,Timestamp,Test1,Test2,Test3
my_hostname,my_username,"10.0.0.1 192.168.1.1",2015-02-11 13:41:54 -0600,,true,false
my_2nd_hostname,my_2nd_username,"10.0.0.2 192.168.1.2",2015-02-11 14:04:41 -0600,true,,false

Я пошел по нескольким путям, которые решают одну из проблем, только чтобы понять, что он не обрабатывает другой аспект искаженных данных. Я был бы признателен, если бы кто-нибудь помог мне определить эффективный способ очистки этого файла.

Спасибо

ИЗМЕНИТЬ

У меня есть несколько фрагментов кода из разных путей, но вот текущая итерация. Это не красиво, просто куча хаков, чтобы попытаться понять это.

import csv

inputfile = open('input.csv', 'r')
outputfile_1 = open('output.csv', 'w')

counter = 1
for line in inputfile:
    #Skip header row
    if counter == 1:
        outputfile_1.write(line)
        counter = counter + 1
    else:
        line = line.replace('\r', '').replace('\n', '')
        outputfile_1.write(line)

inputfile.close()
outputfile_1.close()

with open('output.csv', 'r') as f:
    text = f.read()

    comma_count = text.count(',') #comma_count/6 = total number of rows

    #need to insert a newline after the field contents after every 6th comma
    #unfortunately the last field of the row and the first field of the next row are now rammed up together becaue of the newline replaces above...
    #then process as normal CSV

    #one path I started to go down... but this isn't even functional
    groups = text.split(',')

    counter2 = 1
    while (counter2 <= comma_count/6):
        line = ','.join(groups[:(6*counter2)]), ','.join(groups[(6*counter2):])
        print line
        counter2 = counter2 + 1

ИЗМЕНИТЬ 2

Спасибо @DSM и @Ryan Vincent за то, что направили меня на правильный путь. Используя их идеи, я сделал следующий код, который, кажется, исправляет мой искаженный CSV. Я уверен, что есть много мест для улучшения, которые я бы с радостью принял.

import csv
import re

outputfile_1 = open('output.csv', 'wb')
wr = csv.writer(outputfile_1, quoting=csv.QUOTE_ALL)

with open('input.csv', 'r') as f:
    text = f.read()
    comma_indices = [m.start() for m in re.finditer(',', text)] #Find all the commas - the fields are between them

    cursor = 0
    field_counter = 1
    row_count = 0
    csv_row = []

    for index in comma_indices:
        newrowflag = False

        if "\r" in text[cursor:index]:
            #This chunk has two fields, the last of one row and first of the next
            next_field=text[cursor:index].split('\r')
            next_field_trimmed = next_field[0].replace('\n',' ').rstrip().lstrip()
            csv_row.extend([next_field_trimmed]) #Add the last field of this row

            #Reset the cursor to be in the middle of the chuck (after the last field and before the next)
            #And set a flag that we need to start the next csvrow before we move on to the next comma index
            cursor = cursor+text[cursor:index].index('\r')+1
            newrowflag = True
        else:
            next_field_trimmed = text[cursor:index].replace('\n',' ').rstrip().lstrip()
            csv_row.extend([next_field_trimmed])

            #Advance the cursor to the character after the comma to start the next field
            cursor = index + 1

        #If we've done 7 fields then we've finished the row
        if field_counter%7==0:
            row_count = row_count + 1
            wr.writerow(csv_row)

            #Reset
            csv_row = []

            #If the last chunk had 2 fields in it...
            if newrowflag:
                next_field_trimmed = next_field[1].replace('\n',' ').rstrip().lstrip()
                csv_row.extend([next_field_trimmed])
                field_counter = field_counter + 1

        field_counter = field_counter + 1
    #Write the last row
    wr.writerow(csv_row)

outputfile_1.close()

# Process output.csv as normal CSV file...    

person spork_user    schedule 12.02.2015    source источник
comment
Пожалуйста, укажите ваш код здесь   -  person adifire    schedule 12.02.2015
comment
Это не кажется таким уж плохим; в каждой группе вы читаете семь строк, а затем немного переформатируете их, прежде чем распечатать. Есть ли более конкретная проблема с вашим кодом, которая вызывает проблемы?   -  person DSM    schedule 12.02.2015
comment
@DSM Извините, я пытался охватить все предположения в своем вопросе, но пропустил это. Количество строк в группе является переменным. Например, одна система может иметь 3 или более IP-адресов или несколько имен пользователей.   -  person spork_user    schedule 12.02.2015
comment
Хотите полное решение, написанное вручную для вас?   -  person Matt Coubrough    schedule 12.02.2015
comment
Кода для комментариев по-прежнему нет. Но похоже, что вам нужно прочитать столько строк, что вы увидели шесть запятых, не так ли?   -  person DSM    schedule 12.02.2015
comment
@DSM и другие. Мой код даже близко не подходит, поэтому я не думал, что его публикация будет полезной, к тому же это просто куча взломанных попыток ... но я только что опубликовал некоторые из них. Спасибо за комментарии.   -  person spork_user    schedule 12.02.2015


Ответы (2)


Это комментарий о том, как бы я справился с этим.

Для каждой строки:

Я могу легко определить начало и конец определенных групп:

  • Имя хоста - есть только одно
  • имена пользователей — читайте их, пока не встретите что-то, что не похоже на имя пользователя (через запятую)
  • IP-адрес — читайте их, пока не встретите отметку времени, идентифицированную с помощью шаблона — имейте в виду, что они разделены пробелом, а не запятой. Конец группы обозначается запятой в конце.
  • временная метка - легко идентифицировать по совпадению с образцом
  • test1, test2, test3 - наверняка будут там как поля с разделителями-запятыми

Примечания: я бы использовал совпадения «шаблон», чтобы я мог определить, что у меня есть правильные вещи в правильном месте. Это позволяет быстрее обнаруживать ошибки.

person Ryan Vincent    schedule 12.02.2015

Из вашего отрывка данных кажется, что любая строка, начинающаяся с запятой, должна быть присоединена к предыдущей строке, а любая строка, начинающаяся с чего-либо, кроме запятой, отмечает новую строку.

Если это так, вы можете использовать следующий код для очистки CSV-файла, чтобы синтаксический анализатор стандартной библиотеки csv мог его обработать.

#!/usr/bin/python
raw_data = 'somefilename.raw'
csv_data = 'somefilename.csv'
with open(raw_data, 'Ur') as inp, open(csv_data, 'wb') as out:
    row = list()
    for line in inp:
        line.rstrip('\n')
        if line.startswith(','):
            row.append(line)
        else:
            out.write(''.join(row)+'\n')
            row = list()
            row.append(line))
    # Don't forget to write the last row!
    out.write(''.join(row)+'\n')

Это миниатюрный конечный автомат... накапливающий строки в каждой строке, пока не найдем строку, не начинающуюся с запятой, записывающую предыдущую строку и так далее.

person Jim Dennis    schedule 12.02.2015