Ошибка декодирования при чтении очищенных CSV-файлов из памяти - PullRequest
1 голос
/ 07 апреля 2020

У меня есть веб-сканер, созданный в scrapy, который просматривает веб-страницы, загружает несколько файлов CSV / TXT / ZIP и анализирует данные в файлах для элементов scrapy. Эти файлы не сохраняются на диске, они остаются в памяти, так как они мне не нужны после анализа.

Если быть точным, файлы либо .txt, либо .zip с .txt внутри них, однако они разделены запятыми, поэтому я рассматриваю их как csv. Вот как это работает:

import csv
import io
import zipfile

headers = ['list', 'of strings', 'with headers names']

def parse(self, response, ftype):
    if ftype == 'zip':
        zip_file = zipfile.ZipFile(io.BytesIO(response.body))
        file = io.TextIOWrapper(zip_file.open(zip_file.namelist()[0]))
    else: #If file was .txt
        file = io.StringIO(response.text)

    reader = csv.DictReader(file, fieldnames=headers)
    for row in reader:
        yield self.parse_row(row)

Все файлы успешно открываются, однако некоторые выдают UnicodeDecodeError во время итерации чтения . (Они читают до строки перед ошибкой - все проблемы связаны с файлами изначально .zip)

Исключение гласит:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 7123: invalid start byte.

[Также происходит с байтом 0x8a ]

Я не уверен, что с этим делать. Есть ли способ прочитать эти файлы в другой кодировке, используя csv.DictReader или io?

Я ищу решения, которые предпочтительно не включают сторонние зависимости (то есть, не включены в Python Стандартные библиотеки), даже если это значит сложнее.

1 Ответ

1 голос
/ 08 апреля 2020

Проблема в том, что некоторые файлы в ваших zip-файлах имеют кодировку, отличную от UTF-8. Вот упрощенный пример того, что происходит.

>>> # Make a string of csv-like rows.
>>> rows = 'h1,h2\nhello,world\nßäæ,öë\n'
>>> # Encode the data with an encoding that isn't UTF-8
>>> # (cp1252 is common on Windows machines)  
>>> bs = rows.encode('cp1252')
>>> # Load the encoded bytes into a file-like object
>>> bio = io.BytesIO(bs)                  
>>> bio.seek(0)                          
0
>>> # Load the file-like object into a TextIOWrapper
>>> w = io.TextIOWrapper(bio)            
>>> w.seek(0)                            
0
>>> # Pass the TextIOWrapper to a csv reader and read it
>>> reader = csv.reader(w)               
>>> for row in reader:print(row)         
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/kev/virtual-envs/so38/lib/python3.8/codecs.py", line 322, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xdf in position 18: invalid continuation byte

Решение состоит в том, чтобы передать encoding аргумент TextIOWrapper, чтобы данные были правильно декодированы:

>>> bio = io.BytesIO(bs)
>>> bio.seek(0)
0
>>> # Tell TextIOWrapper these bytes are cp1252!
>>> w = io.TextIOWrapper(bio, encoding='cp1252')
>>> w.seek(0)
0
>>> reader = csv.reader(w)
>>> for row in reader:print(row)
... 
['h1', 'h2']
['hello', 'world']
['ßäæ', 'öë']

Существует еще одна проблема - вам нужно знать, какую кодировку передать на TextIOWrapper. К сожалению, нет 100% точного способа определения кодировки файла. Вы можете догадаться (все эти файлы получены от Windows пользователей в Engli sh -оговорящих * стран, поэтому cp1252 является вероятным решением), или вы можете использовать такие инструменты, как chardet угадать для вас.

* Модуль codecs в стандартной библиотеке имеет список кодеков, доступных в Python и человеческих языках, которые они используют. связаны с.

...