Удаление BOM из CSV в gzip в Python

Я использую следующий код, чтобы распаковать и сохранить файл CSV:

with gzip.open(filename_gz) as f:
    file = open(filename, "w");
    output = csv.writer(file, delimiter = ',')
    output.writerows(csv.reader(f, dialect='excel', delimiter = ';'))

Кажется, все работает, за исключением того, что первые символы в файле являются неожиданными. Поиск в Google, кажется, указывает на то, что это связано с спецификацией в файле.

Я читал, что кодирование содержимого в utf-8-sig должно решить проблему. Тем не менее, добавив:

.read().encoding('utf-8-sig')

к f в csv.reader не удается:

File "ckan_gz_datastore.py", line 16, in <module>
    output.writerows(csv.reader(f.read().encode('utf-8-sig'), dialect='excel', delimiter = ';'))
File "/usr/lib/python2.7/encodings/utf_8_sig.py", line 15, in encode
    return (codecs.BOM_UTF8 + codecs.utf_8_encode(input, errors)[0], len(input))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xef in position 0: ordinal not in range(128)

Как я могу удалить спецификацию и просто сохранить содержимое в правильном формате utf-8?

1 ответ

Решение

Во-первых, вам нужно декодировать содержимое файла, а не кодировать его.

Во-вторых, csv Модуль не любит юникодные строки в Python 2.7, поэтому, расшифровав свои данные, вам нужно преобразовать их обратно в utf-8.

В заключение, csv.reader передается итерация по строкам файла, а не по большой строке с переносами строк.

Так:

csv.reader(f.read().decode('utf-8-sig').encode('utf-8').splitlines())

Однако вы можете посчитать более простым / более эффективным просто удалить спецификацию вручную:

def remove_bom(line):
    return line[3:] if line.startswith(codecs.BOM_UTF8) else line

csv.reader((remove_bom(line) for line in f), dialect = 'excel', delimiter = ';')

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

def remove_bom_from_first(iterable):
    f = iter(iterable)
    firstline = next(f, None)
    if firstline is not None:
        yield remove_bom(firstline)
        for line in f:
            yield f
Другие вопросы по тегам