Python urllib.request 和 UTF-8 解码问题
我正在写一个简单的Python CGI脚本,它可以抓取一个网页并在网页浏览器中显示这个HTML文件(就像一个代理一样)。下面是这个脚本:
#!/usr/bin/env python3.0
import urllib.request
site = "http://reddit.com/"
site = urllib.request.urlopen(site)
site = site.read()
site = site.decode('utf8')
print("Content-type: text/html\n\n")
print(site)
这个脚本在命令行中运行得很好,但当我用网页浏览器查看时,它却显示一个空白页面。这里是我在Apache的错误日志中看到的错误:
Traceback (most recent call last):
File "/home/public/projects/proxy/script.cgi", line 11, in <module>
print(site)
File "/usr/local/lib/python3.0/io.py", line 1491, in write
b = encoder.encode(s)
File "/usr/local/lib/python3.0/encodings/ascii.py", line 22, in encode
return codecs.ascii_encode(input, self.errors)[0]
UnicodeEncodeError: 'ascii' codec can't encode character '\u2019' in position 33777: ordinal not in range(128)
3 个回答
与其纠结于 sys.stdout
的内部细节,不如简单点,让网络服务器 (1) 设置一个叫 PYTHONIOENCODING
的环境变量 (2),并把它的值设为 UTF8
。
如果你使用的是 Apache2,你需要先启用 mod_env.so
模块。在 Debian 系统上,这意味着你需要在 /etc/apache2/mods-enabled
目录下创建一个指向 /etc/apache2/mods-available/env.load
的符号链接,并且如果你想保持和其他模块加载及配置一样的结构,还需要创建一个配置文件 /etc/apache2/conf-available/env.conf
,然后在 /etc/apache2/conf-enabled
目录下也创建一个指向它的符号链接。
我创建的 env_mod.conf
文件的内容是:
<IfModule mod_env.c>
SetEnv PYTHONIOENCODING UTF8
</IfModule>
在我做这个之前,我的脚本显示 sys.stdout.encoding
是 "ANSI ..."
,当我尝试打印包含 Unicode 字符的字符串时会出错;而在设置之后,它变成了 "UTF8"
,并且能够正确地将 UTF-8 数据发送到浏览器。
你可能访问的网站没有使用UTF-8编码。试着把"iso-8859-1"
传给解码的方法看看。
当你在命令行打印内容时,其实是把一个Unicode字符串打印到终端。终端有自己的编码方式,所以Python会把你的Unicode字符串转换成这种编码。这种方式是没问题的。
但是当你在CGI中使用它时,你实际上是在向标准输出(stdout)打印,而标准输出没有特定的编码。因此,Python会尝试用ASCII编码这个字符串。可惜ASCII并不包含你想打印的所有字符,所以就会出现错误。
解决这个问题的方法是把你的字符串转换成某种编码(比如UTF8),并在头部说明这一点。
所以可以像这样做:
sys.stdout.buffer.write(b"Content-type: text/html;encoding=UTF-8\n\n") # Not 100% sure about the spelling.
sys.stdout.buffer.write(site.encode('UTF8'))
在Python 2中,这样做也可以:
print("Content-type: text/html;encoding=UTF-8\n\n") # Not 100% sure about the spelling.
print(site.encode('UTF8'))
但在Python 3中,编码后的数据是以字节形式存在的,所以打印出来的效果就不好了。
当然,你会发现你现在首先是从UTF8解码,然后再重新编码。严格来说,你不一定要这样做。但如果你想在中间修改HTML,这样做其实是个好主意,可以保持所有的修改都是Unicode格式。