Python urllib.request 和 UTF-8 解码问题

2 投票
3 回答
10886 浏览
提问于 2025-04-16 09:26

我正在写一个简单的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 个回答

0

与其纠结于 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 数据发送到浏览器。

(1) http://httpd.apache.org/docs/2.2/howto/cgi.html#env

(2) http://docs.python.org/3.3/library/sys.html#sys.stdin

1

你可能访问的网站没有使用UTF-8编码。试着把"iso-8859-1"传给解码的方法看看。

5

当你在命令行打印内容时,其实是把一个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格式。

撰写回答