如何用Python解码这个从随机网站上获取并通过Django ORM保存的utf-8字符串?
我解析了一个文件,并使用Django把内容保存到了数据库里。这个网站完全是英文的,所以我天真地以为所有内容都是ASCII编码,就开心地把文本保存为unicode格式。
接下来的事情你们可以猜到了 :-)
当我打印的时候,出现了常见的编码错误:
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2019' in position 48: ordinal not in range(128)
快速搜索一下,我发现u'\u2019'是字符’
的UTF-8表示方式。
repr(string)
给我显示了这个:
"u'his son\\u2019s friend'"
然后我当然尝试了django.utils.encoding.smart_str
,还有一种更直接的方法,就是用string.encode('utf-8'),最后得到了可以打印的内容。不幸的是,在我的(linux UTF-8)终端里,它打印成了这样:
In [76]: repr(string.encode('utf-8'))
Out[76]: "'his son\\xe2\\x80\\x99s friend '"
In [77]: print string.encode('utf-8')
his son�s friend
这不是我预期的结果。我怀疑我可能重复编码了什么,或者漏掉了重要的点。
当然,文件的原始编码没有和文件一起公布。我想我可以查看HTTP头信息或者问问网站管理员,但因为\u2019看起来像是UTF-8,所以我以为它就是utf-8。也许我错得很离谱,如果我错了请告诉我。
当然,解决方案我很感激,但如果能深入解释一下原因,以及如何避免这种情况再次发生,那就更好了。我经常在编码上遇到麻烦,这说明我对这个主题还没有完全掌握。
3 个回答
试着像这样打开一个Python命令行:
python2 -S -i -c 'import sys;sys.setdefaultencoding("utf-8");import site'
然后:
>>> s = u'his son\u2019s friend'
>>> print s.encode("utf-8")
his son’s friend
这样的话,默认的编码方式是utf-8,应该可以正常显示。
也许我有点天真,但...你遇到的问题不就是因为没有正确处理unicode编码前面的\
吗?
你最开始的字符串表现得像这样:
>>> s = u'his son\\u2019s friend'
>>> print(s)
his son\u2019s friend
但是去掉前面的\
后,变成了:
>>> s = u'his son\u2019s friend'
>>> print(s)
his son’s friend
你没问题。你的数据是正确的。没错,原始数据是UTF-8格式(根据上下文,u2019作为“son”和“s”之间的撇号是很合理的)。那个奇怪的?
错误字符可能只是因为你的终端设置的字体没有这个字符的显示样式(就是那种特别的撇号)。这没什么大不了的。数据在重要的地方是正确的。如果你担心,可以试试不同的终端和操作系统组合(我是在OS X上用iTerm)。我花了很多时间向我的质量保证团队解释,那个可怕的?
问号字符只是意味着他们的Windows电脑上没有安装中文字体(在我的情况下,我们是在测试中文数据)。这里有一些评论
#Create a Python Unicode object
#(abstract code points, independent of any encoding)
#single backslash tells python we want to represent
#a code point by its unicode code point number, typed out with ASCII numbers
>>> s1 = u'his son\u2019s friend'
#If you just type it at the prompt,
#the interpreter does the equivalent of `print repr(s1)`
#and since repr means "show it like a string typed into a python source file",
#you get your ASCII escaped version back
>>> s1
u'his son\u2019s friend'
>>> print repr(s1)
u'his son\u2019s friend'
#This isn't ASCII, so encoding into ASCII generates your original
#error as expected
>>> s1.encode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character
u'\u2019' in position 7:
ordinal not in range(128)
# Encode in UTF-8 and now we have a string,
# which gets displayed as hex escapes.
#Unicode code point 2019 looks like it gets 3 bytes in UTF-8 (yup, it does)
>>> s1.encode('utf-8')
'his son\xe2\x80\x99s friend'
#My terminal DOES have a different glyph (symbol) to use here,
#so it displays OK for me.
#Note that my terminal has a different glyph for a normal ASCII apostrophe
#(straight vertical)
>>> print s1
his son’s friend
>>> repr(s1)
"u'his son\\u2019s friend'"
>>> str(s1.encode('utf-8'))
'his son\xe2\x80\x99s friend'
另见: http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html
关于字符2019(十六进制表示为e28099,可以在这个页面搜索“2019”): http://www.utf8-chartable.de/unicode-utf8-table.pl?start=8000