将Unicode转换为UTF8以处理CSV文件 - Python使用xlrd
我正在尝试用Python的xlrd和csv模块把Excel表格转换成CSV格式,但遇到了编码问题。xlrd从Excel输出的内容是Unicode格式,而csv模块需要的是UTF-8格式。
我想这和xlrd模块没关系:输出到标准输出或其他不需要特定编码的地方时,一切都正常。
根据book.encoding
,这个工作表的编码是UTF-16-LE。
我现在做的简化版是:
from xlrd import *
import csv
b = open_workbook('file.xls')
s = b.sheet_by_name('Export')
bc = open('file.csv','w')
bcw = csv.writer(bc,csv.excel,b.encoding)
for row in range(s.nrows):
this_row = []
for col in range(s.ncols):
this_row.append(s.cell_value(row,col))
bcw.writerow(this_row)
这段代码在大约740行的时候出现了以下错误:
UnicodeEncodeError: 'ascii' codec can't encode character u'\xed' in position 5: ordinal not in range(128)
看起来出问题的值是“516-777316”——而原Excel表格里的文本是“516-7773167”(最后多了一个7)。
我得承认,我对字符编码的理解非常有限,所以到目前为止我尝试的都是对s.cell_value(row,col)
进行各种笨拙的.encode
和.decode
操作。
如果有人能给我建议解决方案,我会非常感激——如果能解释一下为什么不行以及问题出在哪里,那就更好了,这样我以后可以更容易地自己调试这些问题。
提前谢谢大家!
编辑:
感谢大家的评论。
当我使用this_row.append(s.cell(row,col))
(比如用s.cell而不是s.cell_value)时,整个文档都能正常写入,没有错误。
虽然输出的结果并不是特别理想(text:u'516-7773167'
),但它避免了错误,尽管输出中仍然包含了那些有问题的字符。
这让我觉得问题可能还是出在xlrd上。
大家有什么想法吗?
4 个回答
看起来你遇到了两个问题。
第一个问题是你那个单元格里有点问题 - '7' 应该用 u'x37' 这种方式来表示,因为它在 ASCII 范围内。
更重要的是,你收到的错误信息提到 ascii
编码无法使用,这说明你的 unicode 编码有问题 - 它认为你在尝试编码一个值 0xed
,这个值在 ASCII 中是无法表示的,但你说你是想用 unicode 来表示它。
我不太聪明,无法确定是哪一行代码导致了这个问题 - 如果你能编辑一下你的问题,告诉我是哪一行引发了这个错误信息,我可能能帮你更多(我猜是 this_row.append(s.cell_value(row,col))
或者 bcw.writerow(this_row)
,但希望你能确认一下)。
你问了要解释的内容,但有些现象没有你的帮助是无法解释的。
(A) 从Excel 97开始创建的XLS文件中的字符串,如果可能的话,会用Latin1编码,否则会用UTF16LE编码。每个字符串都有一个标记,告诉你使用了哪种编码。早期的Excel根据用户的“代码页”来编码字符串。无论如何,xlrd会生成unicode对象。文件编码只有在XLS文件是由第三方软件创建时才重要,这种软件可能会省略代码页或对其进行虚假描述。有关Unicode的更多信息,请查看xlrd文档前面的部分。
(B) 未解释的现象:
这段代码:
bcw = csv.writer(bc,csv.excel,b.encoding)
在Python 2.5、2.6和3.1中会导致以下错误:TypeError: expected at most 2 arguments, got 3
——根据csv.writer的文档,我大致可以预期到这个错误;它期望一个文件对象,后面跟着(1)什么都不跟(2)一个方言或(3)一个或多个格式参数。你给了它一个方言,而csv.writer没有编码参数,所以就出错了。你用的是什么版本的Python?或者你是不是没有复制/粘贴你实际运行的脚本?
(C) 关于回溯和实际出错数据的未解释现象:
"the_script.py", line 40, in <module>
this_row.append(str(s.cell_value(row,col)))
UnicodeEncodeError: 'ascii' codec can't encode character u'\xed' in position 5: ordinal not in range(128)
首先,出错代码行中有一个str(),而在简化的脚本中没有——你是不是没有复制/粘贴你实际运行的脚本?无论如何,通常不应该使用str——这样你无法获得浮点数的完整精度;让csv模块来转换它们就行。
其次,你说“似乎卡住的值是‘516-777316’——原始Excel表中的文本是‘516-7773167’(最后有个7)”——很难想象最后的7是怎么丢失的。我会用这样的代码来找出到底是什么问题数据:
try:
str_value = str(s.cell_value(row, col))
except:
print "row=%d col=%d cell_value=%r" % (row, col, s.cell_value(row, col))
raise
那 %r 可以让你省去输入 cell_value=%s ... repr(s.cell_value(row, col))
... repr()会生成你数据的明确表示。学会使用它。
你是怎么得到“516-777316”的?
第三,错误信息实际上是在抱怨一个unicode字符 u'\xed' 在偏移量5(即第六个字符)。U+00ED是带有重音的拉丁小写字母i,而“516-7773167”中根本没有这样的字符。
第四,错误位置似乎是个移动目标——你在某个解决方案的评论中说:“错误在bcw.writerow。”什么情况?
(D) 为什么你会收到那个错误信息(使用str()):str(a_unicode_object)
试图将unicode对象转换为str对象,而在没有任何编码信息的情况下使用ascii,但你有非ascii数据,所以出错了。请注意,你的目标是生成一个utf8编码的csv文件,但你的简化脚本中没有提到utf8。
(E) “... s.cell(row,col))(例如s.cell而不是s.cell_value
)整个文档写入没有错误。输出不是特别理想(text:u'516-7773167')”
这是因为csv写入器调用了你的Cell对象的__str__
方法,这会生成<type>:<repr(value)>
,这对调试可能有用,但正如你所说,在你的csv文件中并不太好。
(F) Alex Martelli的解决方案很好,让你开始了。不过你应该阅读xlrd文档中关于Cell类的部分:单元格的类型有文本、数字、布尔值、日期、错误、空白和空的。如果你有日期,你会想把它们格式化为日期而不是数字,所以你不能使用isinstance()(而且你可能也不想要函数调用的开销)……这就是Cell.ctype
属性和Sheet.cell_type()
以及Sheet.row_types()
方法的用途。
(G) UTF8不是Unicode。UTF16LE也不是Unicode。UTF16也不是Unicode……而且认为单个字符串会在UTF16 BOM上浪费2个字节的想法,连微软都不敢想象 :-)
(H) 进一步阅读(除了xlrd文档):
http://www.joelonsoftware.com/articles/Unicode.html
http://www.amk.ca/python/howto/unicode
我认为cell_value
返回的值是让你遇到问题的unicode字符串(请打印它的type()
来确认一下),如果是这样的话,你只需要改这一行:
this_row.append(s.cell_value(row,col))
改成:
this_row.append(s.cell_value(row,col).encode('utf8'))
如果cell_value
返回了多种不同的类型,那么你只需要在它返回unicode字符串的时候进行编码;所以你需要把这一行拆成几行:
val = s.cell_value(row, col)
if isinstance(val, unicode):
val = val.encode('utf8')
this_row.append(val)