如何在Python中解码已在JavaScript中编码的unicode字符串?

2 投票
6 回答
3869 浏览
提问于 2025-04-15 23:18

平台:App Engine
框架:webapp / CGI / WSGI

在我的客户端(JavaScript)上,我通过把一个网址和一个unicode字符串拼接在一起,构造了一个新的网址:

http://www.foo.com/地震

然后我调用了encodeURI来获取:

http://www.foo.com/%E5%9C%B0%E9%9C%87

接着我把这个结果放进了一个HTML表单的值里。

这个表单会提交到PayPal,而我在那里的设置是使用'utf-8'编码。

PayPal随后会通过IPN(即时支付通知)对这个网址发起一个POST请求。

在我的服务器端,WSGIApplication尝试用我定义的正则表达式提取这个unicode字符串:

(r'/paypal-listener/(.+?)', c.PayPalIPNListener)

我会尝试通过调用

query = unquote_plus(query).decode('utf-8')

(或者其他变体)来解码它,但我会遇到错误:

/paypal-listener/%E5%9C%B0%E9%9C%87

...(省略)...

'ascii' 编码无法编码位置 0-1 的字符:序号不在范围(128)内

(第一行是请求的网址)

当我检查query的长度时,Python告诉我它的长度是18,这让我觉得'%E5%9C%B0%E9%9C%87'并没有被编码过。

6 个回答

0

urllib.unquote() 这个函数在处理unicode字符串时不太好用。在这种情况下,应该传入字节字符串,然后再解码成unicode。

这样做是有效的:

>>> u = u'http://www.foo.com/%E5%9C%B0%E9%9C%87'
>>> print urllib.unquote(u.encode('ascii'))
http://www.foo.com/地震
>>> print urllib.unquote(u.encode('ascii')).decode('utf-8')
http://www.foo.com/地震

但是这样做就不行(还可以查看 urllib.unquote 用 Latin-1 解码百分号转义字符):

>>> print urllib.unquote(u)
http://www.foo.com/å °é  

已经是unicode的字符串再解码是没用的:

>>> print urllib.unquote(u).decode('utf-8')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File ".../lib/python2.6/encodings/utf_8.py", line
16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 19-24: o
rdinal not in range(128)
1

假设这个HTML页面是用utf-8编码的,如果框架能够处理URL中的百分号编码,那么只需要简单地用 path.decode('utf-8') 就可以了。

如果框架不能处理,你可以使用:

  • 如果URL是 http://www.foo.com/地震,可以用 urllib.unquote(path).decode('utf-8')
  • 如果你是在说通过AJAX或者HTML的 <form> 发送的参数,可以用 urllib.unquote_plus(path).decode('utf-8')

(详细信息可以查看 这里)

编辑:如果你还有问题,请提供以下信息,这样我们可以更好地帮助你解决问题:

  • 你在Google App Engine中使用的网络框架是什么,比如Django、WebOb、CGI等

  • 你是如何在应用中获取URL的(如果可以,请附上简短的代码示例)

  • 当你将 http://www.foo.com/地震 作为URL添加时的 repr(url) 是什么

  • 尝试将这个作为URL添加,并发布 repr(url),这样我们可以确保服务器没有将字符解码为 latin-1Windows-1252

    http://foo.com/¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
    

编辑 2:因为这是一个实际的URL(而不是查询部分,比如不是 http://www.foo.com/?param=%E5%9C%B0%E9%9C%87),所以使用

query = unquote(query.encode('ascii')).decode('utf-8')

应该是安全的。不过如果你是在解码实际的URL,应该用 unquote 而不是 unquote_plus。我不知道为什么Google会把URL作为 unicode 对象传递,但我怀疑传递给应用的实际URL不会用 windows-1252 等方式解码。我有点担心,因为我以为它在错误地解码 查询(也就是传递给 GETPOST 的参数),但看起来并不是这样。

3

原则上,这个方法应该是可行的:

>>> urllib.unquote_plus('http://www.foo.com/%E5%9C%B0%E9%9C%87').decode('utf-8')
u'http://www.foo.com/\u5730\u9707'

不过,有几点需要注意:

  1. unquote_plus 是用来处理 application/x-form-www-urlencoded 数据的,比如通过 POST 提交的表单和查询字符串参数。在 URL 的路径部分,+ 表示的是一个字面上的加号,而不是空格,所以在这里你应该使用普通的 unquote

  2. 一般来说,不应该对整个 URL 进行解码。因为 URL 中某些部分的特殊字符会丢失。你应该把 URL 拆分成几个部分,获取你感兴趣的单一路径组件(比如 %E5%9C%B0%E9%9C%87),然后再进行解码。

(如果你想把一个 URI 完全转换成 IRI,比如 http://www.foo.com/地震,事情会复杂一些。只有 IRI 的路径/查询/片段部分是用 UTF-8 编码的;而域名则是通过一种叫做‘Punycode’的奇特 IDN 方案在 Unicode 和字节之间进行映射。)

这个在我的 Python 服务器端接收到了。

你的服务器端具体是什么?是服务器、网关还是框架?你是怎么获取 url 变量的?

你似乎遇到了一个 UnicodeEncodeError,这意味着在传给 unquote 函数的 输入 中出现了意外的非 ASCII 字符,这根本不是解码的问题。所以我建议你检查一下,可能在某个地方已经把 URL 的路径部分解码成了某种 Unicode 字符串。让我们看看那个变量的 repr

不幸的是,很多网络服务器在处理 URL 路径部分的 Unicode 时存在严重问题,这不仅在 Python 中如此,普遍都是这样。

主要问题是 PATH_INFO 变量根据 CGI 规范(后来又被 WSGI 规范)定义为预解码的。这是个可怕的错误,部分原因是上面提到的第(1)点,这意味着你无法在路径部分获取 %2F,更严重的是,解码 % 序列会引入一个 Unicode 解码步骤,而这个步骤是应用程序无法控制的。不同的服务器环境在处理 URL 中的非 ASCII % 转义时差异很大,通常很难重现浏览器传入的确切字节序列。

IIS 特别麻烦,因为它默认会尝试将 URL 路径解析为 UTF-8,如果路径不是有效的 UTF-8 序列,它会退回到极不可靠的系统默认代码页(例如,在西方 Windows 安装中是 cp1252),但 不会告诉你。这样的话,你在尝试从环境变量中读取 PATH_INFO 中的任何非 ASCII 字符时,可能会遇到相当严重的问题,因为 Windows 的环境变量是 Unicode,但在 Python 2 和许多其他语言中是以系统代码页的字节形式访问的。

Apache 通过提供一个额外的非标准环境变量 REQUEST_URI 来缓解这个问题,这个变量保存了浏览器提交的原始、完全未解码的 URL,这样处理起来就简单多了。不过,如果你使用了 URL 重写或错误文档,那么这个未映射的 URL 可能和你想象中的不一样。

一些框架试图修复这些问题,效果各不相同。WSGI 1.1 预计会尝试标准化这个问题,但在此之前,我们面临的实际情况是,Unicode 路径并不总是能正常工作,而在一个服务器上尝试修复的黑科技通常会在另一个服务器上失效。

你总是可以使用 URL 重写将 Unicode 路径转换为 Unicode 查询参数。因为 QUERY_STRING 环境变量在应用程序外部并没有被解码,所以处理起来要简单得多。

撰写回答