Python URL解码后跟Unicode解码

12

我有一个类似于'%C3%A7%C3%B6asd+fjkls%25asd'的unicode字符串,我想对它进行解码。
我使用了urllib.unquote_plus(str)但结果不正确。

  • 期望的结果是:çöasd+fjkls%asd
  • 实际得到的结果是:çöasd fjkls%asd

双重编码的utf-8字符(%C3%A7%C3%B6)被错误地解码了。
我的python版本是2.7,在Linux发行版上运行。 有什么最好的方法可以得到期望的结果吗?


3
请帮助尝试提供帮助的人,执行以下操作并发布结果:import sys; print sys.stdout.encoding - John Machin
实际上,解码本身可能运行良好,但重新编码以供控制台显示可能存在问题。 - ncoghlan
6个回答

29
你有3个或4个或5个问题...但是repr()unicodedata.name()可以帮助你,它们能够明确地展示你所拥有的内容,而不会因控制台编码不同造成混淆。使用print fubar输出结果时可能会产生这种情况。
摘要:(a)如果你从一个Unicode对象开始并对其应用unquote函数;(b)如果你从一个str对象开始并且你的控制台编码不是UTF-8。
如果如你所说你从一个Unicode对象开始:
>>> s0 = u'%C3%A7%C3%B6asd+fjkls%25asd'
>>> print repr(s0)
u'%C3%A7%C3%B6asd+fjkls%25asd'

这是一段偶然的无意义文本。如果你对其应用urllibX.unquote_YYYY(),你将得到另一个无意义的Unicode对象(u'\xc3\xa7\xc3\xb6asd+fjkls%asd'),当你打印它时会出现你所看到的症状。你应该立即将原始的Unicode对象转换为字符串对象:

>>> s1 = s0.encode('ascii')
>>> print repr(s1)
'%C3%A7%C3%B6asd+fjkls%25asd'

那么您应该将其取消引用:

>>> import urllib2
>>> s2 = urllib2.unquote(s1)
>>> print repr(s2)
'\xc3\xa7\xc3\xb6asd+fjkls%asd'

看前四个字节,它是用UTF-8编码的。如果你运行print s2,如果你的控制台期望UTF-8,那么它会看起来很好,但如果期望ISO-8859-1(也称为latin1),你会看到症状性垃圾字符(第一个字符将是A-tilde)。让我们暂且放下这个想法并将其转换为Unicode对象:

>>> s3 = s2.decode('utf8')
>>> print repr(s3)
u'\xe7\xf6asd+fjkls%asd'

并检查它以查看我们实际获得了什么:

>>> import unicodedata
>>> for c in s3[:6]:
...     print repr(c), unicodedata.name(c)
...
u'\xe7' LATIN SMALL LETTER C WITH CEDILLA
u'\xf6' LATIN SMALL LETTER O WITH DIAERESIS
u'a' LATIN SMALL LETTER A
u's' LATIN SMALL LETTER S
u'd' LATIN SMALL LETTER D
u'+' PLUS SIGN

看起来和你所期望的一样。现在我们来讨论如何在控制台上显示它。注意:当你看到"cp850"时不要惊慌,我正在依照可移植性的原则操作并且刚好是在Windows命令提示符上运行。

>>> import sys
>>> sys.stdout.encoding
'cp850'
>>> print s3
çöasd+fjkls%asd
注意:unicode对象是使用sys.stdout.encoding进行显式编码的。幸运的是,s3中的所有unicode字符都可以用该编码(以及cp1252和latin1)表示。

1
我没有和 OP 一样的问题,但是你清晰的编码和解码步骤指导帮助我立即解决了我在阅读大量文档后仍无法解决的问题。谢谢。 - KobeJohn

12

使用unquoteunquote_plus都会得到一个字节串。如果你想要Unicode字符串,那么你需要将字节串解码为Unicode:

>>> print(urllib.unquote_plus('%C3%A7%C3%B6asd+fjkls%25asd').decode('utf8'))
çöasd fjkls%asd
>>> 

相对于:

>>> print(urllib.unquote_plus('%C3%A7%C3%B6asd+fjkls%25asd'))
çöasd fjkls%asd
>>> 

请注意,您的输入字符串必须是字节字符串:如果您将Unicode传递给unquote/unquote_plus,那么您将得到一些混乱的结果。如果是这种情况,请先进行编码:

>>> print(urllib.unquote_plus(u'%C3%A7%C3%B6asd+fjkls%25asd'.encode('ascii')).decode('utf8'))
çöasd fjkls%asd

1
在 Django 1.7 中,我需要使用 urllib.unquote_plus(u'äö'.encode('ascii')).decode('utf8') 来解码文件上传名称 - Larpon

0
你有一个双重问题:你的字符串是Unicode编码的,同时包含了URL编码的字符。有些匹配。你可以将字符串规范化为ASCII,以确保它不会被错误地解释:
>>> s = '%C3%A7%C3%B6asd+fjkls%25asd' # ascii string
>>> print urllib2.unquote(s) # works as expected
çöasd+fjkls%asd
>>> s = u'%C3%A7%C3%B6asd+fjkls%25asd' # unicode string
>>> print urllib2.unquote(s) # decode stuff that it shouldn't
çöasd+fjkls%asd
>>> print urllib2.unquote(s.encode('ascii')) # encode the unicode string to ascii: works!
çöasd+fjkls%asd

1
我真的认为我的Python版本有问题,因为我复制了你的代码,但结果仍然是“çöasd+fjkls%asd”。尽管我已经调查了替代方案,你知道我可以用哪个模块代替urllib吗? - user637287
问题很可能不在Python上。但说实话,我已经没有理性的解释了 :-) 你试过巫术吗?你直接在Python shell中尝试过吗?如果没有,你可能想在文件顶部定义编码。你用的操作系统是什么?我猜测你正在使用Windows,因为它有很多编码问题。 - Bite code

0

再试一次urllib2

print urllib2.unquote('%C3%A7%C3%B6asd+fjkls%25asd')

谢谢您的快速回复,我已经尝试过了,但结果还是一样。您有其他建议吗? - user637287

0
'

%C3%A7%C3%B6asd + fjkls%25asd' - 这不是一个Unicode字符串。

这是一个URL编码的字符串。请使用urllib2.unquote()。

'

这是程序相关内容的翻译:这是结果:`>>> import urllib2
print urllib2.unquote('%C3%A7%C3%B6asd+fjkls%25asd')
çöasd+fjkls%asd`我的Python版本是2.7,可能是因为版本差异引起的问题吗?
- user637287

-1

您正在使用 unquote_plus 方法,该方法会考虑空格并将其转换为 +。只需使用 unquote 方法即可。

>>> import urllib
>>> print urllib.unquote('%C3%A7%C3%B6asd+fjkls%25asd')
çöasd+fjkls%asd
>>> print urllib.unquote_plus('%C3%A7%C3%B6asd+fjkls%25asd')
çöasd fjkls%asd

实际上,我期望的是第二个输出结果,但我正在做完全相同的事情,这是我的结果:
`>>> import urllib
print urllib.unquote('%C3%A7%C3%B6asd+fjkls%25asd')
çöasd+fjkls%asd
print urllib.unquote_plus('%C3%A7%C3%B6asd+fjkls%25asd') çöasd fjkls%asd`
- user637287
将您的字符串编码为 ASCII (s.encode('ascii')),然后使用 quote_plus。这样就可以了。 - Senthil Kumaran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接