【问题标题】:Python UTF-16 CSV readerPython UTF-16 CSV 阅读器
【发布时间】:2012-02-28 23:21:30
【问题描述】:

我有一个必须阅读的 UTF-16 CSV 文件。 Python csv 模块似乎不支持 UTF-16。

我正在使用 python 2.7.2。我需要解析的 CSV 文件非常大,包含数 GB 的数据。

以下 John Machin 问题的答案

print repr(open('test.csv', 'rb').read(100))

test.csv 的输出只有 abc 作为内容

'\xff\xfea\x00b\x00c\x00'

我认为 csv 文件是在美国的 Windows 机器上创建的。我正在使用 Mac OSX Lion。

如果我使用 phihag 提供的代码和包含一条记录的 test.csv。

使用的示例 test.csv 内容。下面是 print repr(open('test.csv', 'rb').read(1000)) 输出

'\xff\xfe1\x00,\x002\x00,\x00G\x00,\x00S\x00,\x00H\x00 \x00f\x00\xfc\x00r\x00 \x00e\x00 \x00\x96\x00 \x00m\x00 \x00\x85\x00,\x00,\x00I\x00\r\x00\n\x00'

由 phihag 编写的代码

import codecs
import csv
with open('test.csv','rb') as f:
      sr = codecs.StreamRecoder(f,codecs.getencoder('utf-8'),codecs.getdecoder('utf-8'),codecs.getreader('utf-16'),codecs.getwriter('utf-16'))      
      for row in csv.reader(sr):
         print row

以上代码的输出

['1', '2', 'G', 'S', 'H f\xc3\xbcr e \xc2\x96 m \xc2\x85']
['', '', 'I']

预期输出是

['1', '2', 'G', 'S', 'H f\xc3\xbcr e \xc2\x96 m \xc2\x85','','I']

【问题讨论】:

    标签: python csv utf-16


    【解决方案1】:

    目前,csv 模块不支持 UTF-16。

    在 Python 3.x 中,csv 需要一个文本模式文件,您可以简单地使用 open 的编码参数来强制另一种编码:

    # Python 3.x only
    import csv
    with open('utf16.csv', 'r', encoding='utf16') as csvf:
        for line in csv.reader(csvf):
            print(line) # do something with the line
    

    在 Python 2.x 中,您可以重新编码输入:

    # Python 2.x only
    import codecs
    import csv
    
    class Recoder(object):
        def __init__(self, stream, decoder, encoder, eol='\r\n'):
            self._stream = stream
            self._decoder = decoder if isinstance(decoder, codecs.IncrementalDecoder) else codecs.getincrementaldecoder(decoder)()
            self._encoder = encoder if isinstance(encoder, codecs.IncrementalEncoder) else codecs.getincrementalencoder(encoder)()
            self._buf = ''
            self._eol = eol
            self._reachedEof = False
    
        def read(self, size=None):
            r = self._stream.read(size)
            raw = self._decoder.decode(r, size is None)
            return self._encoder.encode(raw)
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self._reachedEof:
                raise StopIteration()
            while True:
                line,eol,rest = self._buf.partition(self._eol)
                if eol == self._eol:
                    self._buf = rest
                    return self._encoder.encode(line + eol)
                raw = self._stream.read(1024)
                if raw == '':
                    self._decoder.decode(b'', True)
                    self._reachedEof = True
                    return self._encoder.encode(self._buf)
                self._buf += self._decoder.decode(raw)
        next = __next__
    
        def close(self):
            return self._stream.close()
    
    with open('test.csv','rb') as f:
        sr = Recoder(f, 'utf-16', 'utf-8')
    
        for row in csv.reader(sr):
            print (row)
    

    opencodecs.open 要求文件以 BOM 开头。如果没有(或者您使用的是 Python 2.x),您仍然可以在内存中转换它,如下所示:

    try:
        from io import BytesIO
    except ImportError: # Python < 2.6
        from StringIO import StringIO as BytesIO
    import csv
    with open('utf16.csv', 'rb') as binf:
        c = binf.read().decode('utf-16').encode('utf-8')
    for line in csv.reader(BytesIO(c)):
        print(line) # do something with the line
    

    【讨论】:

    • 感谢@phihag 的回复。有没有办法在不将文件加载到内存的情况下做到这一点?我的 csv 文件很大。
    • 我如何知道文件是否以 BOM 开头? @phihag
    • 尝试第一种方法;如果流没有,它将以UnicodeError 失败。您还可以检查文件的前两个字节;如果它们是 FE FFFF FE,那就是 BOM。
    • 在尝试 @phihag csv 阅读器的 StreamReader 选项时,有时似乎会部分读取记录。当我在 vi 中打开文件时。我在似乎认为它是记录结尾的行中看到 ,但在 字符之后还有另外两个字段。看起来剩余字段被视为下一条记录
    • 你能上传一个演示文件吗?没有它,我无法重现问题。另外,在演示文件上使用第二种方法是否也会失败?
    【解决方案2】:

    只需使用codecs.open 打开文件

    import codecs, csv
    
    stream = codecs.open(<yourfile.csv>, encoding="utf-16")
    reader = csv.reader(stream)
    

    并使用 unicode 字符串处理您的程序,就像 should do anyway if you are processing text 一样

    【讨论】:

    • 在 csv.reader(stream) 中记录:行抛出异常 UnicodeEncodeError: 'ascii' codec can't encode character u'\xed' in position 77: ordinal not in range(128)跨度>
    • 这在 Python 3.x 中运行良好(尽管可以只写 open 而不是 codecs.open),但在 2.x 中失败,因为 csv 尝试重新编码 unicode 字符它从流中读取。
    【解决方案3】:

    我强烈建议您将文件重新编码为 UTF-8。在 BMP 之外没有任何 Unicode 字符的很可能的情况下,您可以利用 UTF-16 是固定长度编码这一事实从输入文件中读取固定长度的块,而不必担心跨越块边界。

    第 1 步:确定您实际拥有的编码。检查文件的前几个字节:

    print repr(open('thefile.csv', 'rb').read(100))

    编码u'abc'的四种可能方式

    \xfe\xff\x00a\x00b\x00c -> utf_16
    \xff\xfea\x00b\x00c\x00 -> utf_16
    \x00a\x00b\x00c -> utf_16_be
    a\x00b\x00c\x00 -> utf_16_le
    

    如果您对此步骤有任何问题,请编辑您的问题以包含上述print repr()的结果

    第 2 步:这是一个 Python 2.X recode-UTF-16*-to-UTF-8 脚本:

    import sys
    infname, outfname, enc = sys.argv[1:4]
    fi = open(infname, 'rb')
    fo = open(outfname, 'wb')
    BUFSIZ = 64 * 1024 * 1024
    first = True
    while 1:
        buf = fi.read(BUFSIZ)
        if not buf: break
        if first and enc == 'utf_16':
            bom = buf[:2]
            buf = buf[2:]
            enc = {'\xfe\xff': 'utf_16_be', '\xff\xfe': 'utf_16_le'}[bom]
            # KeyError means file doesn't start with a valid BOM
        first = False
        fo.write(buf.decode(enc).encode('utf8'))
    fi.close()
    fo.close()
    

    其他事项:

    您说您的文件太大而无法读取整个文件、重新编码和重写,但您可以在vi 中打开它。请解释一下。

    被视为记录结束有点令人担忧。看起来0x85 被识别为 NEL(C1 控制代码,NEWLINE)。很有可能数据最初是用一些传统的单字节编码编码的,其中 0x85 有意义,但在原始编码是 ISO-8859-1 aka latin1 的错误假设下已被转码为 UTF-16。文件起源于哪里? IBM 大型机? Windows/Unix/经典 Mac?什么国家、地区、语言?您显然认为 不是换行符;你觉得这意味着什么?

    请随时将一份精简文件(包括一些 内容)发送至sjmachin at lexicon dot net

    更新基于提供的 1 行示例数据。

    这证实了我的怀疑。阅读this。这是其中的一段话:

    ... C1 控制字符 ... 很少直接使用,除了 特定平台,例如 OpenVMS。当他们出现在文件中时, 网页、电子邮件等,表面上是在一个 ISO-8859-n 编码,它们的代码位置一般指代 专有的系统特定中该位置的字符 编码,例如 Windows-1252 或 Apple Macintosh(“MacRoman”) 使用为表示 C1 提供的代码的字符集 设置为单个 8 位字节,以提供额外的图形 字符

    这段代码:

    s1 = '\xff\xfe1\x00,\x002\x00,\x00G\x00,\x00S\x00,\x00H\x00 \x00f\x00\xfc\x00r\x00 \x00e\x00 \x00\x96\x00 \x00m\x00 \x00\x85\x00,\x00,\x00I\x00\r\x00\n\x00'
    s2 = s1.decode('utf16')
    print 's2 repr:', repr(s2)
    from unicodedata import name
    from collections import Counter
    non_ascii = Counter(c for c in s2 if c >= u'\x80')
    print 'non_ascii:', non_ascii
    for c in non_ascii:
        print "from: U+%04X %s" % (ord(c), name(c, "<no name>"))
        c2 = c.encode('latin1').decode('cp1252')
        print "to:   U+%04X %s" % (ord(c2), name(c2, "<no name>"))
    
    s3 = u''.join(
        c.encode('latin1').decode('1252') if u'\x80' <= c < u'\xA0' else c
        for c in s2
        )
    print 's3 repr:', repr(s3)
    print 's3:', s3
    

    产生以下内容(Python 2.7.2 IDLE,Windows 7):

    s2 repr: u'1,2,G,S,H f\xfcr e \x96 m \x85,,I\r\n'
    non_ascii: Counter({u'\x85': 1, u'\xfc': 1, u'\x96': 1})
    from: U+0085 <no name>
    to:   U+2026 HORIZONTAL ELLIPSIS
    from: U+00FC LATIN SMALL LETTER U WITH DIAERESIS
    to:   U+00FC LATIN SMALL LETTER U WITH DIAERESIS
    from: U+0096 <no name>
    to:   U+2013 EN DASH
    s3 repr: u'1,2,G,S,H f\xfcr e \u2013 m \u2026,,I\r\n'
    s3: 1,2,G,S,H für e – m …,,I
    

    你认为\x96的解释更合理:

    SPA,即保护区的起点(由面向块的终端使用。)

    中文短跑
    ?

    看起来有必要对更大的数据样本进行彻底分析。乐于助人。

    【讨论】:

    • 更新问题更多详情
    【解决方案4】:

    Python 2.x csv 模块文档example 展示了如何处理其他编码。

    【讨论】:

    • 文档实际上说的是:“因此,只要您避免使用像 UTF-16 这样使用 NUL 的编码,您就可以编写处理编码和解码的函数或类。”
    • @Antony 你读过最后一个例子吗?在将其传递给 csv 模块之前,它会将任何编码重新编码为 UTF-8。
    • 是的,这个问题只用几行就得到了解决,这与@phihag 的答案中的代码几乎相同。不过,我会明确引用这个例子 - 让读者的生活更轻松:) 取消投票。
    • 这是对 phihag 的回答和温和的 RTFM 的补充 :)
    • 很好的补充 :) csv 模块代码写得不好(utf16 不是那么可怕,它是 Excel 输出的默认值之一)和文档(最后一个示例处理这两者并不明显NULs 和 utf16 也是因为 Guido 希望每个人都迁移到 python 3.x,我猜。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-09-12
    • 2012-08-20
    • 1970-01-01
    • 1970-01-01
    • 2011-07-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多