2012-02-07 236 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