Общая поддержка Unicode/UTF-8 для CSV-файлов в Python 2.6
Модуль csv в Python не работает должным образом, когда задействован UTF-8/Unicode. Я нашел в документации по Python и на других веб-страницах фрагменты, которые работают для конкретных случаев, но вы должны хорошо понимать, какую кодировку вы обрабатываете, и использовать соответствующий фрагмент.
Как я могу читать и записывать как строки, так и строки Unicode из файлов.csv, которые "просто работают" в Python 2.6? Или это ограничение Python 2.6, у которого нет простого решения?
10 ответов
Пример кода для чтения Юникода, приведенный по адресу http://docs.python.org/library/csv.html, выглядит устаревшим, поскольку он не работает с Python 2.6 и 2.7.
Здесь следует UnicodeDictReader
который работает с utf-8 и может быть с другими кодировками, но я проверял это только на входах utf-8.
Короче, идея заключается в том, чтобы декодировать Unicode только после того, как строка CSV была разделена на поля csv.reader
,
class UnicodeCsvReader(object):
def __init__(self, f, encoding="utf-8", **kwargs):
self.csv_reader = csv.reader(f, **kwargs)
self.encoding = encoding
def __iter__(self):
return self
def next(self):
# read and split the csv row into fields
row = self.csv_reader.next()
# now decode
return [unicode(cell, self.encoding) for cell in row]
@property
def line_num(self):
return self.csv_reader.line_num
class UnicodeDictReader(csv.DictReader):
def __init__(self, f, encoding="utf-8", fieldnames=None, **kwds):
csv.DictReader.__init__(self, f, fieldnames=fieldnames, **kwds)
self.reader = UnicodeCsvReader(f, encoding=encoding, **kwds)
Использование (кодировка исходного файла - utf-8):
csv_lines = (
"абв,123",
"где,456",
)
for row in UnicodeCsvReader(csv_lines):
for col in row:
print(type(col), col)
Выход:
$ python test.py
<type 'unicode'> абв
<type 'unicode'> 123
<type 'unicode'> где
<type 'unicode'> 456
Немного запоздалый ответ, но я использовал unicodecsv с большим успехом.
Модуль, представленный здесь, выглядит как классная простая замена модуля csv, который позволяет работать с utf-8 csv.
import ucsv as csv
with open('some.csv', 'rb') as f:
reader = csv.reader(f)
for row in reader:
print row
В этом документе уже используется пример Unicode, зачем все-таки искать другой или заново изобретать колесо?
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')
Я подтверждаю, unicodecsv
отличная замена для csv
модуль, я только что заменил csv
от unicodecsv
в моем исходном коде, и это работает как шарм.
Обертка unicode_csv_reader
Упоминается в документации Python принимает строки Unicode. Это потому, что CSV не принимает строки Unicode. cvs, вероятно, не знает о кодировке или локали и просто обрабатывает получаемые строки как байты. Так что получается, что оболочка кодирует строки Unicode, что означает, что она создает строку байтов. Затем, когда оболочка возвращает результаты из csv, она снова декодирует байты, что означает, что она преобразует последовательности байтов UTF-8 в правильные символы Юникода.
Если вы даете оболочке простую байтовую строку, например, используя f.readlines()
это даст UnicodeDecodeError
в байтах со значением> 127. Обертку следует использовать в случае, если в вашей программе есть строки Unicode в формате CSV.
Я могу себе представить, что у оболочки все еще есть одно ограничение: поскольку cvs не принимает юникод, а также не принимает многобайтовые разделители, вы не можете анализировать файлы с символом юникода в качестве разделителя.
Может быть, это очевидно, но ради новичков я упомяну это.
В питоне 3.X csv
Модуль поддерживает любую кодировку из коробки, поэтому, если вы используете эту версию, вы можете придерживаться стандартного модуля.
with open("foo.csv", encoding="utf-8") as f:
r = csv.reader(f, delimiter=";")
for row in r:
print(row)
Дополнительную информацию см. В: Поддерживает ли python 3.1.3 юникод в модуле csv?
Вы должны рассмотреть tablib, который имеет совершенно другой подход, но должен рассматриваться в соответствии с требованием "просто работает".
with open('some.csv', 'rb') as f:
csv = f.read().decode("utf-8")
import tablib
ds = tablib.Dataset()
ds.csv = csv
for row in ds.dict:
print row["First name"]
Предупреждение: tablib отклонит ваш CSV, если в каждой строке не будет одинакового количества элементов.
Вот немного улучшенная версия ответа Максима, которая также может пропустить спецификацию UTF-8:
import csv
import codecs
class UnicodeCsvReader(object):
def __init__(self, csv_file, encoding='utf-8', **kwargs):
if encoding == 'utf-8-sig':
# convert from utf-8-sig (= UTF8 with BOM) to plain utf-8 (without BOM):
self.csv_file = codecs.EncodedFile(csv_file, 'utf-8', 'utf-8-sig')
encoding = 'utf-8'
else:
self.csv_file = csv_file
self.csv_reader = csv.reader(self.csv_file, **kwargs)
self.encoding = encoding
def __iter__(self):
return self
def next(self):
# read and split the csv row into fields
row = self.csv_reader.next()
# now decode
return [unicode(cell, self.encoding) for cell in row]
@property
def line_num(self):
return self.csv_reader.line_num
class UnicodeDictReader(csv.DictReader):
def __init__(self, csv_file, encoding='utf-8', fieldnames=None, **kwds):
reader = UnicodeCsvReader(csv_file, encoding=encoding, **kwds)
csv.DictReader.__init__(self, reader.csv_file, fieldnames=fieldnames, **kwds)
self.reader = reader
Обратите внимание, что наличие спецификации не определяется автоматически. Вы должны сигнализировать, что это там, передавая encoding='utf-8-sig'
аргумент в пользу конструктора UnicodeCsvReader
или же UnicodeDictReader
, кодирование utf-8-sig
является utf-8
с спецификацией.
Я бы добавил к его ответу. По умолчанию Excel сохраняет CSV-файлы как латиница-1 (которую не поддерживает ucsv). Вы можете легко это исправить:
with codecs.open(csv_path, 'rb', 'latin-1') as f:
f = StringIO.StringIO( f.read().encode('utf-8') )
reader = ucsv.UnicodeReader(f)
# etc.