Устойчивая, фактически работающая реализация CSV для non-ascii?

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

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

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

Суть решения состоит в том, чтобы кодировать ('utf-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 с чистым питоном?

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

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

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

И здесь не рекомендованное решение:

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)

Ответ 1

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

def utf_8_encoder ( unicode_csv_data​​strong > )

Вы загружаете объекты str.

Проблемы с чтением файлов CSV, отличных от ASCII, - это то, что вы не знаете кодировку, и вы не знаете разделителя. Если вы знаете кодировку (и это кодирование на основе 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.

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

Для работы необходимо знать кодировку для ЛЮБОГО упражнения чтения файлов.

Угадать кодировку правильно все время для любой кодировки в любом файле размера не очень сложно - это невозможно. Однако, ограничивая область действия на файлы csv, сохраненные из Excel или Open Office, в кодировке по умолчанию локали пользователя и разумного размера, это не такая большая задача. Я предложил бы предложить chardet попробовать; он полагает windows-1252 для вашего файла euro и 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]: Я автор xlrd.

Ответ 2

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

Правильное обнаружение кодировки файла представляется очень сложной проблемой. Вы можете прочитать обсуждение в fooobar.com/questions/46789/....

Ответ 3

Вы делаете неправильную вещь в своем коде, пытаясь .encode('utf-8'), вы должны ее декодировать. И btw, 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" - из-за своеобразного способа его реализации.

Ответ 4

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

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

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

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