2012-02-07 132 views
8

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

我正在使用python 2.7.2。我需要解析的CSV文件是巨大的数据大小。

答案下面

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使用的内容。下面是打印再版(开放( 'test.csv', 'RB')。读(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上述代码

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

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'] 

回答

28

在魔门t,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 
+0

感谢@phihag的回复。有没有办法做到这一点,而无需将文件加载到内存中?我的csv文件很大。 – venky 2012-02-07 14:53:27

+0

@venky更新了应该在2.x中工作的黑客。 – phihag 2012-02-07 15:02:45

+0

如何知道文件是否以BOM开头?@phihag – venky 2012-02-07 15:15:32

-1

只要打开与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

+0

用于csv.reader记录(流):线抛出异常UnicodeEncodeError:“ASCII”编解码器无法编码的字符的u“\固定的”在位置77:顺序不在范围内(128) – venky 2012-02-07 15:09:43

+0

能正常工作在Python 3.X (尽管可以只写'open'而不是'codecs.open'),但在2.x中失败了,因为'csv'试图重新编码从流中读取的unicode字符。 – phihag 2012-02-07 15:09:58

3

我强烈建议你重新编码为UTF-8你的文件。在很可能的条件下,您没有任何Unicode字符以外的BMP,您可以利用这个事实,即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重新编码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又名latin1的情况下已被转码为UTF-16。文件来自哪里?一台IBM大型机? Windows/Unix /经典Mac?什么国家,地区,语言?你显然认为这并不意味着是一个换行符;你认为这意味着什么?

请随时切下文件的副本(包括一些< 85>的东西)的基础上提供了1行样本数据发送到sjmachin at lexicon dot net

更新

这证实了我的怀疑。阅读this。下面是它报价:

... the C1 control characters ... are rarely used directly, except on specific platforms such as OpenVMS. When they turn up in documents, Web pages, e-mail messages, etc., which are ostensibly in an ISO-8859-n encoding, their code positions generally refer instead to the characters at that position in a proprietary, system-specific encoding such as Windows-1252 or the Apple Macintosh ("MacRoman") character set that use the codes provided for representation of the C1 set with a single 8-bit byte to instead provide additional graphic characters

此代码:

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即受保护区域的开始(block-ori使用)

EN DASH

看起来像一个更大的数据样本的彻底分析是有保证的。乐于帮助。

+0

更新的问题更多细节 – venky 2012-02-08 04:12:00

+0

@venky:答案已更新。 – 2012-02-08 21:40:41

4

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

+1

文档实际上说的是:“只要避免使用NUL的UTF-16编码,就可以编写处理编码和解码的函数或类。” – 2012-10-22 13:24:51

+0

@Antony你读过最后一个例子吗?在将它传递给csv模块之前,它将任何编码重新编码为UTF-8。 – 2012-10-22 14:25:52

+0

是的,这个问题在几行中解决,这几行与@ phihag的答案中的代码几乎相同。我会明确引用这个例子 - 让读者的生活更轻松:) Downvote被删除。 – 2012-10-22 15:08:37