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

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

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

В Stackru уже есть несколько вопросов по чтению 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 на чистом 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 ответа

Решение

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

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.

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

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

Все время правильно угадывать кодировку для любой кодировки в файле любого размера не очень сложно - это невозможно. Однако ограничение области действия файлами CSV, сохраненными из Excel или Open Office в кодировке по умолчанию для локали пользователя, и разумного размера, это не такая уж большая задача. Я бы предложил попробовать Шарде; это угадывает 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,

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

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

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

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

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

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

Вы делаете неправильные вещи в своем коде, пытаясь .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 mode - это или "rb", или "wb" - из-за своеобразного способа его реализации.

Другие вопросы по тегам