2012-02-07 40 views
8

Tôi có tệp CSV UTF-16 mà tôi phải đọc. Mô-đun csv Python dường như không hỗ trợ UTF-16.Đầu đọc CSV UTF-16 của Python

Tôi đang sử dụng trăn 2.7.2. Tệp CSV tôi cần phân tích là kích thước lớn chạy vào một số GB dữ liệu.

Answers cho John Machin câu hỏi dưới đây

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

Output với test.csv có chỉ abc như nội dung

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

Tôi nghĩ rằng tập tin csv Đã tạo trên cửa sổ máy tại Mỹ. Tôi đang sử dụng Mac OSX Lion.

Nếu tôi sử dụng mã được cung cấp bởi phihag và test.csv chứa một bản ghi.

nội dung mẫu test.csv được sử dụng. Dưới đây là in repr (open ('test.csv', 'rb'). Đọc (1000)) đầu ra

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

Mã bởi 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 

Output của các mã trên

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

sản lượng dự kiến ​​là

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

Trả lời

28

Tại momen t, mô-đun csv không hỗ trợ UTF-16.

Trong Python 3.x, csv hy vọng một file văn bản-mode và bạn chỉ có thể sử dụng tham số mã hóa của open để buộc khác mã hóa:

# 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 

Trong Python 2.x, bạn có thể mã hóa lại các đầu vào :

# 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 yêu cầu các tập tin bắt đầu với một BOM. Nếu không (hoặc bạn đang ở trên Python 2.x), bạn vẫn có thể chuyển đổi nó trong bộ nhớ, như thế này:

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

Cảm ơn @phihag vì phản hồi của bạn. Có cách nào để làm điều này mà không cần tải tập tin vào bộ nhớ? Tệp csv của tôi rất lớn. – venky

+0

@venky Được cập nhật bằng một bản hack sẽ hoạt động trong phiên bản 2.x. – phihag

+0

Tôi làm cách nào để biết tệp có bắt đầu bằng BOM khô[email protected] – venky

-1

Chỉ cần mở tập tin của bạn với codecs.open như trong

import codecs, csv 

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

Và làm việc thông qua chương trình của bạn bằng các chuỗi unicode, như bạn should do anyway if you are processing text

+0

cho bản ghi trong csv.reader (luồng): dòng ném ngoại lệ UnicodeEncodeError: 'codec ascii' không thể mã hóa ký tự u '\ xed' ở vị trí 77: thứ tự không nằm trong phạm vi (128) – venky

+0

Điều này hoạt động tốt trong Python 3.x (mặc dù người ta chỉ có thể viết 'mở' thay vì' codecs.open'), nhưng không thành công trong 2.x vì 'csv' cố gắng mã hóa lại các ký tự unicode mà nó đọc từ luồng. – phihag

3

Tôi thực sự khuyên bạn nên mã lại (các) tệp của mình thành UTF-8. Trong điều kiện rất có thể là bạn không có bất kỳ ký tự Unicode nào bên ngoài BMP, bạn có thể tận dụng thực tế là UTF-16 là mã hóa có độ dài cố định để đọc các khối có độ dài cố định từ tệp đầu vào của bạn mà không phải lo lắng về khối ranh giới.

Bước 1: Xác định loại mã hóa bạn thực sự có.Kiểm tra vài byte đầu tiên của tập tin của bạn:

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

Bốn cách có thể mã hóa 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 

Nếu bạn có bất kỳ rắc rối với bước này, chỉnh sửa câu hỏi của bạn để bao gồm các kết quả của các bên trên print repr()

Bước 2: Dưới đây là một Python 2.x recode-UTF-16 * -to-UTF-8 kịch bản:

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() 

Các vấn đề khác:

Bạn nói rằng các file của bạn quá lớn để đọc các tập tin toàn bộ, recode và viết lại, nhưng bạn có thể mở nó trong vi. Vui lòng giải thích.

Các < 85> được coi là kết thúc kỷ lục là một chút lo lắng. Có vẻ như 0x85 đang được nhận dạng là NEL (mã kiểm soát C1, NEWLINE). Có khả năng mạnh mẽ rằng dữ liệu ban đầu được mã hóa trong một số mã hóa byte đơn kế thừa, trong đó 0x85 có ý nghĩa nhưng đã được chuyển mã sang UTF-16 theo giả định sai rằng mã hóa ban đầu là ISO-8859-1 aka latin1. Tệp đã bắt nguồn từ đâu? Một máy tính lớn của IBM? Windows/Unix/Mac cổ điển? Quốc gia, ngôn ngữ, ngôn ngữ nào? Bạn rõ ràng nghĩ rằng < 85> không có nghĩa là một dòng mới; bạn nghĩ điều đó nghĩa là gì?

Xin vui lòng gửi một bản sao của một tập tin cắt xuống (trong đó bao gồm một số các < 85> công cụ) để sjmachin at lexicon dot net

Cập nhật dựa trên dữ liệu mẫu 1-line cung cấp.

Điều này xác nhận sự nghi ngờ của tôi. Đọc this. Dưới đây là một trích dẫn từ nó:

... 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

Mã này:

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 

sản xuất như sau (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 

Mà bạn có nghĩ là một giải thích hợp lý hơn về \x96:

SPA tức là Bắt đầu khu vực được bảo vệ (Được sử dụng bởi block-ori thiết bị đầu cuối được giao.)
hoặc
EN DASH
?

Dường như việc phân tích kỹ lưỡng mẫu dữ liệu lớn hơn nhiều được bảo đảm. Vui vẻ giúp đỡ.

+0

Cập nhật câu hỏi chi tiết hơn – venky

+0

@venky: trả lời cập nhật. –

4

Python 2.x csv tài liệu mô-đun example cho biết cách xử lý các mã hóa khác.

+1

Những gì tài liệu thực sự nói là: "Vì vậy, bạn có thể viết các hàm hoặc các lớp xử lý mã hóa và giải mã cho bạn miễn là bạn tránh mã hóa như UTF-16 sử dụng NUL." –

+0

@Antony bạn đã đọc ví dụ cuối cùng chưa? Nó recodes để BẤT CỨ mã hóa như UTF-8 trước khi chuyển nó vào mô-đun csv. –

+0

Đúng, vấn đề được giải quyết chỉ trong một vài dòng làm tương tự như mã từ câu trả lời của @ phihag. Tôi sẽ trích dẫn ví dụ một cách rõ ràng mặc dù - để làm cho cuộc sống của người đọc dễ dàng hơn :) Downvote loại bỏ. –