Устойчивая, реально работающая реализация CSV для non-ascii? - PullRequest
13 голосов
/ 16 февраля 2011

[Обновить] Цените ответы и вводите все вокруг, но рабочий код будет приветствоваться.Если вы можете предоставить код, который может читать примеры файлов, вы король (или королева).

[Обновление 2] Спасибо за отличные ответы и обсуждение.Что мне нужно сделать, это прочитать их, проанализировать и сохранить их части в экземплярах модели Django.Я считаю, что это означает преобразование их из их родной кодировки в Unicode, чтобы Django мог с ними справиться, верно?

На Stackoverflow уже есть несколько вопросов по теме Stackoverflowчтение не в формате ascii Python CSV, но решения, показанные там и в документации по python, не работают с входными файлами, которые я пробую.

Суть решения заключается в том, чтобы закодировать (8 ') вход для считывателя CSV и Unicode (item,' utf-8 ') вывод считывателя.Однако это сталкивается с проблемами UnicodeDecodeError (см. Вопросы выше):

UnicodeDecodeError: 'utf8' codec can't decode byte 0xa3 in position 8: unexpected

Входной файл не обязательно находится в utf8;это может быть ISO-8859-1, cp1251 или что-то еще.

Итак, вопрос: что такое гибкий, способный к перекрестному кодированию способ чтения файлов CSV в Python?

Корень проблемы, кажется, в том, что модуль CSV является расширением C;есть ли модуль чтения CSV на чистом python?

Если нет, есть ли способ уверенно определить кодировку входного файла, чтобы его можно было обработать?

В основном я смотрюдля пуленепробиваемого способа чтения (и, надеюсь, записи) файлов CSV в любой кодировке.

Вот два примера файлов: Европейский , Русский .

А вот рекомендуемое решение не сработало:

Python 2.6.4 (r264:75821M, Oct 27 2009, 19:48:32)
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 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')
...
>>> r = unicode_csv_reader(file('sample-euro.csv').read().split('\n'))
>>> line = r.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in unicode_csv_reader
  File "<stdin>", line 3, in utf_8_encoder
UnicodeDecodeError: 'ascii' codec can't decode byte 0xf8 in position 14: ordinal not in range(128)
>>> r = unicode_csv_reader(file('sample-russian.csv').read().split('\n'))
>>> line = r.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in unicode_csv_reader
  File "<stdin>", line 3, in utf_8_encoder
UnicodeDecodeError: 'ascii' codec can't decode byte 0xa0 in position 28: ordinal not in range(128)

Ответы [ 4 ]

16 голосов
/ 16 февраля 2011

Вы пытаетесь применить решение к другой проблеме.Обратите внимание:

def utf_8_encoder ( unicode_csv_data )

Вы кормите его str объектами.

Проблемы с чтением не-ASCII CSVФайлы - это то, что вы не знаете кодировку и не знаете разделитель.Если вы знаете кодировку (а это кодировка на основе ASCII (например, cp125x, любая восточноазиатская кодировка, UTF-8, не UTF-16, , а не UTF-32))и разделитель, это будет работать:

for row in csv.reader("foo.csv", delimiter=known_delimiter):
   row = [item.decode(encoding) for item in row]

Ваш sample_euro.csv выглядит как cp1252 с разделителем запятых.Русский выглядит как cp1251 с разделителем точек с запятой.Между прочим, из содержимого видно, что вам также необходимо определить, какой формат даты используется и, возможно, также и валюту - в примере на русском языке указаны суммы денег, за которыми следует пробел и аббревиатура от кириллицы для «рублей».

Обратите внимание: сопротивляйтесь всем попыткам убедить вас, что у вас есть файлы, закодированные в ISO-8859-1.Они закодированы в cp1252.

Обновление в ответ на комментарий "" "Если я понимаю, что вы говорите, я должен знать кодировку, чтобы это работало? В общем случаеЯ не буду знать кодировку и на основании другого ответа предположить, что кодировка очень сложная, так что мне не повезло? "" "

Вы должны знать кодировку для ANY упражнение по чтению файлов для работы.

Все время правильно угадывать кодировку для любой кодировки в файле любого размера не очень сложно - это невозможно.Однако ограничение области действия csv-файлами, сохраненными из Excel или Open Office в кодировке по умолчанию для локали пользователя, и разумного размера, это не такая большая задача.Я бы посоветовал попробовать chardet ;он угадывает windows-1252 для вашего евро-файла и windows-1251 для вашего русского файла - фантастическое достижение, учитывая их крошечный размер.

Обновление 2 в ответ на "" " рабочий код будет приветствоваться" ""

Рабочий код (Python 2.x):

from chardet.universaldetector import UniversalDetector
chardet_detector = UniversalDetector()

def charset_detect(f, chunk_size=4096):
    global chardet_detector
    chardet_detector.reset()
    while 1:
        chunk = f.read(chunk_size)
        if not chunk: break
        chardet_detector.feed(chunk)
        if chardet_detector.done: break
    chardet_detector.close()
    return chardet_detector.result

# Exercise for the reader: replace the above with a class

import csv    
import sys
from pprint import pprint

pathname = sys.argv[1]
delim = sys.argv[2] # allegedly known
print "delim=%r pathname=%r" % (delim, pathname)

with open(pathname, 'rb') as f:
    cd_result = charset_detect(f)
    encoding = cd_result['encoding']
    confidence = cd_result['confidence']
    print "chardet: encoding=%s confidence=%.3f" % (encoding, confidence)
    # insert actions contingent on encoding and confidence here
    f.seek(0)
    csv_reader = csv.reader(f, delimiter=delim)
    for bytes_row in csv_reader:
        unicode_row = [x.decode(encoding) for x in bytes_row]
        pprint(unicode_row)

Выход 1:

delim=',' pathname='sample-euro.csv'
chardet: encoding=windows-1252 confidence=0.500
[u'31-01-11',
 u'Overf\xf8rsel utland',
 u'UTLBET; ID 9710032001647082',
 u'1990.00',
 u'']
[u'31-01-11',
 u'Overf\xf8ring',
 u'OVERF\xd8RING MELLOM EGNE KONTI',
 u'5750.00',
 u';']

Выход 2:

delim=';' pathname='sample-russian.csv'
chardet: encoding=windows-1251 confidence=0.602
[u'-',
 u'04.02.2011 23:20',
 u'300,00\xa0\u0440\u0443\u0431.',
 u'',
 u'\u041c\u0422\u0421',
 u'']
[u'-',
 u'04.02.2011 23:15',
 u'450,00\xa0\u0440\u0443\u0431.',
 u'',
 u'\u041e\u043f\u043b\u0430\u0442\u0430 Interzet',
 u'']
[u'-',
 u'13.01.2011 02:05',
 u'100,00\xa0\u0440\u0443\u0431.',
 u'',
 u'\u041c\u0422\u0421 kolombina',
 u'']

Обновление 3 Каков источник этих файлов?Если они «сохраняются как CSV» из Excel, OpenOffice Calc или Gnumeric, вы можете избежать всей драмы кодирования, сохранив их как «Excel 97-2003 Workbook (* .xls)» и используя xlrd читать их.Это также избавило бы от необходимости проверять каждый CSV-файл для определения разделителя (запятая против точки с запятой), формата даты (31-01-11 против 04.02.2011) и «десятичной точки» (5750,00 против 450,00) -- все эти различия предположительно создаются путем сохранения как CSV .[Dis] claimer: я автор xlrd.

2 голосов
/ 16 февраля 2011

Не знаю, пробовали ли вы это уже, но в разделе пример для официальной документации Python для модуля csv вы найдете пару классов; UnicodeReader и UnicodeWriter. Пока они работали нормально для меня.

Правильное определение кодировки файла представляется очень сложной проблемой. Вы можете прочитать обсуждение в этой теме StackOverflow .

0 голосов
/ 17 февраля 2011

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

Вы должны найти способ сообщить приложению, какую кодировку использовать.

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

Однако не сдавайтесь.Попробуйте детектор кодировки chardet, упомянутый в этом вопросе: https://serverfault.com/questions/82821/how-to-tell-the-language-encoding-of-a-filename-on-linux, и если вам повезет, вы не получите много ошибок.

0 голосов
/ 17 февраля 2011

Вы делаете неправильную вещь в своем коде, пытаясь .encode('utf-8'), вы должны вместо этого расшифровать его. И, кстати, unicode(bytestr, 'utf-8') == bytestr.decode('utf-8')

Но самое главное, ПОЧЕМУ вы пытаетесь декодировать строки?

Звучит немного абсурдно, но вы можете работать с этими CSV, не обращая внимания на то, являются ли они cp1251, cp1252 или utf-8. Прелесть всего этого в том, что региональные символы> 0x7F, а также utf-8, использует последовательности символов> 0x7F для представления не-ASCII символов.

Поскольку разделители, о которых заботится CSV (будь то, или; или \ n), находятся внутри ASCII, его работа не будет зависеть от используемой кодировки (если она однобайтовая или utf-8!).

Важно отметить, что вы должны передавать в Python 2.x csv файлы модулей, открытые в режиме binary - то есть «rb» или «wb» - из-за своеобразного способа реализации.

...