【问题标题】:Processing a csv file with utf-8 text in it处理包含 utf-8 文本的 csv 文件
【发布时间】:2017-04-10 21:15:26
【问题描述】:

我有一个 csv 文件(参见下面的 [1]),其中包含非 ascii 文本(例如,Antonio Melé 之类的名称。该文件包含带有 URL、摘录和 cmets 的书籍列表。

在 Python 3.5 中,我像这样打开并处理文件:

# -*- coding: utf-8 -*-
import codecs
import csv 
import pdb


def select_book_matching_keyword(books, kw):
    """
    Will select the csv rows for which any column has matching keyword in it

    Snippet from csv file:
    `Django By Example,Antonio Melé,Using class-based ...`

        `Antonio Melé`  
           becomes  
        `b'Antonio Mel\xc3\xa9'`
    """
    selected_books = []
    for book in books:
        kw_in_any_column = [column for column in book if kw in column.decode()]
        # >> Without the `column.decode()` above I cannot
        #    run this list comprehension (that is if I 
        #    write `if kw in column` instead of `if kw in column.decode()
        if kw_in_any_column:
            # print(book)
            selected_books.append(book) 
    return selected_books


if __name__=='__main__':
    f = codecs.open('safari-annotations-export-3.csv', 'r', 'utf-8')
    reader = csv.reader(f)
    books = []

    for row in reader:
        book_utf8 = [column.encode("utf-8") for column in row]
        books.append(book_utf8)
        print(book_utf8)

pdb.set_trace()

现在打印 csv 的行(参见上面的 print(book_utf8))会给我如下结果:

[b'Django By Example', b'Antonio Mel\xc3\xa9', b'Using class-based views', b'2017-03-08', b'https://www.safaribooksonline.com/library/view/django-by-example/9781784391911/', b'https://www.safaribooksonline.com/library/view/django-by-example/9781784391911/ch01s09.html', b'https://www.safaribooksonline.com/a/django-by-example/5869158/', b'Using class-based views', b'']

首先,我有一个字节前缀。为什么? (Python 3.x 默认将字符串视为 unicode,Python 2.7 默认将其视为字节。)

然后我有这个:b'Antonio Mel\xc3\xa9' 而不是 Antonio Melé

我知道我还没有完全掌握 Python 中编码的概念。在这里阅读了很多关于 SO 的帖子,但我还是不太明白。

  • 所以如果我的csv文件有特殊字符,我需要以utf-8打开它吗?我做到了。
  • 然后,如果我遍历 csv 阅读器,获取所有行并将它们附加到列表中(不对列进行编码),然后尝试打印它,我得到一个错误(参见下面的 [2])。为什么我不能打印该列表?

[1]csv file with utf-8 text

[2] 尝试打印 csv 文件的行列表而不对行的列进行编码会给我一个错误:

(snip) ['Learning jQuery Deferreds', 'Terry Jones...', '2. The jQuery Deferred API', '2017-04-06', 'https://www.safaribooksonline.com/library/view/learning-jquery-deferreds/9781449369385/', 'https://www.safaribooksonline.com/library/view/learning-jquery-deferreds/9781449369385/ch02.html', 'https://www.safaribooksonline.com/a/learning-jquery-deferreds/6635517/', 'More Terminology: Resolve, Reject and Progress', ''] *** UnicodeEncodeError: 'ascii' codec can't encode character '\u2019' in position 368: ordinal not in range(128)

【问题讨论】:

  • (1) 你在列表中得到字节,因为你 encode() 每一列。不要那样做。 (2) 如果您不进行编码,则会收到 UnicodeEncodeError,因为可能是您的环境(区域设置等)设置为 ASCII,或者可能是因为您的终端无法处理 Unicode。尝试写入打开的文件而不是使用print()

标签: python csv utf-8


【解决方案1】:

通常,所有编码/解码都是在与外部世界通信时完成的。在您的示例中,有两个通信步骤:

  • 您从使用codecs.open() 打开的文件中读取,
  • 您使用内置的print() 写出结果。

在此之间,您应该始终使用解码的字符串,即。输入 str(Python 2 的 unicode)。

从磁盘文件中读取

第一点很顺利,最初:你用正确的编码打开文件,让csv做格式解析。 这可以确保在磁盘上找到的字节被正确解码为字符串,而无需使用decode 方法。 (附带说明一下,您可以在此处省略 codecs,而只使用内置的 open(filename, 'r', encoding='utf-8'),但它实际上可以做同样的事情。)

然后,您使用以下行重新编码字符串:

book_utf8 = [column.encode("utf-8") for column in row]

你不应该这样做。现在您必须处理 bytes 而不是字符串。 注意:

>>> 'Antonio Melé'.encode('utf-8')
b'Antonio Mel\xc3\xa9'

bytes 类型与字符串有共同的特点,但不兼容。 这就是为什么你必须decodeselect_book_matching_keyword 函数中的每个元素(在你的代码 sn-p 中未使用,顺便说一句。),以便成员资格测试在字符串和字符串之间完成,而不是在字符串和字节之间完成。

这两种类型的区别之一是print() 使用repr 形式显示bytes,因此输出将包含引号和b 前缀:

>>> print(b'Antonio Mel\xc3\xa9')
b'Antonio Mel\xc3\xa9'

对比打印字符串:

>>> print('Antonio Melé')
Antonio Melé

将文本或数据写入 STDOUT

这给我们带来了下一个问题:使用print() 将数据写入 STDOUT。 如果您尝试上述行,您可能会遇到异常:

>>> print('Antonio Melé')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character '\xe9' in position 11: ordinal not in range(128)

问题在于,显然使用了'ascii' 编码。 现在,您如何指定编码? 使用open写入磁盘文件时很清楚:

f = open(filename, 'w', encoding='utf8')
f.write('Antonio Melé')
f.close()

但是你不能告诉print 使用什么编码。 原因是它使用了一个已经打开的文件句柄,即。 sys.stdout。就我而言,这是:

>>> sys.stdout
<_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>

但您可能会看到encoding='ascii' 或类似'ANSI_X3.4-1968' 的内容。

你有两种可能:

  • 您将输出写入磁盘文件,根本不使用print
  • 您更改了sys.stdout 的编码。
    (更准确地说,您将其替换为围绕底层基于字节的 STDOUT 流的新 TextIOWrapper。)

我希望第一种可能性是显而易见的。 对于第二个,你需要额外的一行代码(前提是sys被导入):

sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer)

现在print 将使用 UTF-8 编码字符串。

但是,您可能仍会遇到问题: 您的终端很可能未配置为接受并正确显示 UTF-8 文本,或者它甚至不支持 Unicode。 如果是这种情况,您要么在屏幕上看到乱码,要么可能是另一个例外。 但是这个问题在 Python 之外,你必须通过终端配置来修复它,或者切换到另一个。

【讨论】:

  • 伙计,绝对精彩的答案!感谢一百万次!通过您的示例,我意识到我的 Mac 上的终端仿真器 iTerm2 在打印 unicode 字符时出现问题(即使设置正确设置为 utf-8)。这就是为什么我什至不能在我的终端中输入德语变音符号,也不能在我的 Python REPL 中输入。 - sys.stdout 返回与您的输出相同的输出。 - 我认为终端问题与我对编码的理解不足重叠。这就是为什么我无法调试和理解细节。 - 再次,非常感谢! - 你有关于这个话题的推荐读物吗?
  • 这就是我绝对喜欢 StackOverflow 的原因!还有 Python 社区!
  • 我很高兴它有帮助。不过,我不得不承认,我真的不知道关于编码的全面、易读的介绍。 This section in the codecs doc 提供了有关映射字节字符串的一些详细信息。 This tutorial 在 Unicode 上主持,涵盖了广泛的 Unicode 相关主题,但它实际上是一个幻灯片放映,其中包含供讲师使用的文本。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-01-11
  • 2017-09-13
  • 2017-04-29
  • 2011-11-07
  • 2011-02-23
  • 2015-09-13
  • 2020-09-14
相关资源
最近更新 更多