使用Python CSV DictReader处理UTF-8格式数据

40

据我所知,Python (v2.6) 的 csv 模块默认无法处理 Unicode 数据,对吗?在 Python 文档中,有一个例子介绍如何从 UTF-8 编码的文件中读取。但这个例子只返回 CSV 行作为一个列表。

我想按名称访问行列,就像使用csv.DictReader一样,但带有 UTF-8 编码的 CSV 输入文件。

有人能告诉我如何以有效的方式做到这一点吗?我需要处理数百兆大小的 CSV 文件。

7个回答

54

我自己想出了一个答案:

def UnicodeDictReader(utf8_data, **kwargs):
    csv_reader = csv.DictReader(utf8_data, **kwargs)
    for row in csv_reader:
        yield {unicode(key, 'utf-8'):unicode(value, 'utf-8') for key, value in row.iteritems()}

注:根据评论建议,现已更新以便解码密钥。


1
选择它作为答案吧 :) - Uku Loskit
9
这并不会解码文件第一行中的字典键。 - John Machin
请注意,它不处理csv.DictReader的restkey标志功能。 - Ivan Klass
3
不需要为回答自己的问题而道歉。这是Stack Overflow预期的使用方式之一。现在其他人可以分享你自学的内容了! - dinosaur
3
如约翰·马钦所提到的那样,这不会解码键;yield 行应该是:yield {unicode(key, 'utf-8'):unicode(value, 'utf-8') for key, value in row.iteritems()}。 - Giacomo
显示剩余2条评论

38

对我来说,关键不在于操纵 CSV DictReader 的参数,而是文件打开器本身。这个方法解决了问题:

with open(filepath, mode="r", encoding="utf-8-sig") as csv_file:
    csv_reader = csv.DictReader(csv_file)

不需要特殊的类。现在我可以打开带或不带BOM的文件而不会崩溃。


1
类型错误:对于此函数,'encoding' 是无效的关键字参数。 - ATX
@ATX 奇怪 - 我想知道你是否在使用 Python2 而不是 3? - shacker
是的,确实是p2。 - ATX

1

基于类的方法来处理@LMatter的答案,使用这种方法,您仍然可以获得DictReader的所有好处,例如获取字段名称和行号,同时它还处理UTF-8。

import csv

class UnicodeDictReader(csv.DictReader, object):

    def next(self):
        row = super(UnicodeDictReader, self).next()
        return {unicode(key, 'utf-8'): unicode(value, 'utf-8') for key, value in row.iteritems()}

1
首先,请使用2.6版本的文档。它可能会随每个版本发布而有所变化。明确说明它不支持Unicode,但支持UTF-8。从技术上讲,这两者并不相同。正如文档所说:
引用: csv模块没有直接支持读写Unicode,但它是8位干净的,除了一些ASCII NUL字符问题之外。因此,只要避免使用使用NUL的编码,例如UTF-16,您就可以编写处理编码和解码的函数或类。推荐使用UTF-8。
下面的示例(来自文档)显示了如何创建两个能够正确将文本作为UTF-8读取为CSV的函数。您应该知道,csv.reader()始终返回DictReader对象。
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.DictReader(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]

2
csv.reader()在我的测试中没有返回DictReader对象。您确定吗?另外,您示例中的yield语句只返回具有值的列表而不是字典。 - LMatter
我想你关于DictReader是正确的。我改变了示例以调用csv.DictReader而不是csv.reader。请注意,除此之外,这直接来自文档。 - kelloti
1
我认为你的读取器仍然没有返回一个字典,而只是一列行值的列表(请参阅yield语句)。但无论如何感谢您的答复,在重新阅读您提到的文档后,我终于自己找到了解决方案(终于 :)))。 - LMatter
@LMatter,你介意分享一下你找到的解决方案吗? - Matthias

1

只需使用unicodecsv包就可以轻松实现。

# pip install unicodecsv
import unicodecsv as csv

with open('your_file.csv') as csvfile:
    reader = csv.DictReader(csvfile)
    for row in reader:
        print(row)

0

csvw还有其他功能(用于元数据丰富的Web CSV),但它定义了一个UnicodeDictReader类,该类包装其UnicodeReader类,其核心正是这样:

class UnicodeReader(Iterator):
    """Read Unicode data from a csv file."""
    […]

    def _next_row(self):
        self.lineno += 1
        return [
            s if isinstance(s, text_type) else s.decode(self._reader_encoding)
            for s in next(self.reader)]

它有时确实会让我感到困惑,但是csvw.UnicodeDictReader真的需要在with块中使用,否则就会出错。除此之外,该模块非常通用,并且与py2和py3兼容。


0

答案中没有DictWriter方法,因此这是更新后的类:

class DictUnicodeWriter(object):

    def __init__(self, f, fieldnames, dialect=csv.excel, encoding="utf-8", **kwds):
        self.fieldnames = fieldnames    # list of keys for the dict
        # Redirect output to a queue
        self.queue = cStringIO.StringIO()
        self.writer = csv.DictWriter(self.queue, fieldnames, dialect=dialect, **kwds)
        self.stream = f
        self.encoder = codecs.getincrementalencoder(encoding)()

    def writerow(self, row):
        self.writer.writerow({k: v.encode("utf-8") for k, v in row.items()})
        # Fetch UTF-8 output from the queue ...
        data = self.queue.getvalue()
        data = data.decode("utf-8")
        # ... and reencode it into the target encoding
        data = self.encoder.encode(data)
        # write to the target stream
        self.stream.write(data)
        # empty queue
        self.queue.truncate(0)

    def writerows(self, rows):
        for row in rows:
            self.writerow(row)

    def writeheader(self):
        header = dict(zip(self.fieldnames, self.fieldnames))
        self.writerow(header)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接