处理包含utf-8文本的csv文件

时间:2017-04-10 21:15:26

标签: python csv utf-8

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

在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 个答案:

答案 0 :(得分:2)

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

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

在此之间,你应该总是使用解码的字符串,即。输入str(Python 2' s 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类型具有字符串的共同特征,但它们不兼容。 这就是为什么你必须在decode函数中使用select_book_matching_keyword每个元素的原因(在你的代码片段中没有使用,顺便说一句。),以便在字符串和字符串之间进行成员资格测试,不是字符串和字节。

这两种类型之间的区别之一是print()使用repr表单来显示bytes,因此输出将包含引号和b前缀:< / p>

>>> 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流周围的新TextIOWrap替换它。)

我希望第一种可能性是显而易见的。 对于第二行,您需要一行额外的代码(假设导入了sys):

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

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

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