首页 文章

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

提问于
浏览
1

我有一个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 回答

  • 2

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

    • 您从用 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 类型具有字符串的共同特征,但它们不兼容 . 这就是为什么你必须在 select_book_matching_keyword 函数中使用 decode 每个元素(在你的代码片段中没有使用,顺便说一句 . ),这样就可以在字符串和字符串之间完成成员资格测试,而不是字符串和字节 .

    这两种类型之间的区别之一是 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流周围的新TextIOWrap替换它 . )

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

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

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

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

相关问题