在Python中无误地将Unicode转换为ASCII

202 投票
12 回答
537609 浏览
提问于 2025-04-15 19:55

我的代码只是抓取一个网页,然后把它转换成Unicode格式。

html = urllib.urlopen(link).read()
html.encode("utf8","ignore")
self.response.out.write(html)

但是我遇到了一个 UnicodeDecodeError 错误:


Traceback (most recent call last):
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/webapp/__init__.py", line 507, in __call__
    handler.get(*groups)
  File "/Users/greg/clounce/main.py", line 55, in get
    html.encode("utf8","ignore")
UnicodeDecodeError: 'ascii' codec can't decode byte 0xa0 in position 2818: ordinal not in range(128)

我猜这意味着HTML里面有一些格式不正确的Unicode字符。 我能不能直接忽略掉那些导致问题的代码字节,而不是出现错误呢?

12 个回答

139

这是对Ignacio Vazquez-Abrams回答的一个扩展

>>> u'aあä'.encode('ascii', 'ignore')
'a'

有时候,我们希望去掉字符上的重音符号,打印出基本的形式。可以通过以下方法实现:

>>> import unicodedata
>>> unicodedata.normalize('NFKD', u'aあä').encode('ascii', 'ignore')
'aa'

你可能还想把其他字符(比如标点符号)转换成最接近的等价物。例如,右单引号这个unicode字符在编码时不会被转换成ascii的撇号。

>>> print u'\u2019'
’
>>> unicodedata.name(u'\u2019')
'RIGHT SINGLE QUOTATION MARK'
>>> u'\u2019'.encode('ascii', 'ignore')
''
# Note we get an empty string back
>>> u'\u2019'.replace(u'\u2019', u'\'').encode('ascii', 'ignore')
"'"

虽然还有更有效的方法可以做到这一点。想了解更多细节,可以查看这个问题 Python的“最佳ASCII对应这个Unicode”的数据库在哪里?

245
>>> u'aあä'.encode('ascii', 'ignore')
'a'

首先,你需要解码你收到的字符串,这可以通过查看响应中的合适的 meta 标签里的字符集,或者在 Content-Type 头部找到的字符集来完成。然后再进行编码。

方法 encode(encoding, errors) 允许你自定义错误处理方式。除了 ignore,默认的错误处理方式还有:

>>> u'aあä'.encode('ascii', 'replace')
b'a??'
>>> u'aあä'.encode('ascii', 'xmlcharrefreplace')
b'aあä'
>>> u'aあä'.encode('ascii', 'backslashreplace')
b'a\\u3042\\xe4'

详细信息请查看 https://docs.python.org/3/library/stdtypes.html#str.encode

110

2018 更新:

截至2018年2月,使用像 gzip 这样的压缩方式已经变得 相当流行(大约73%的所有网站都在用,包括像Google、YouTube、Yahoo、Wikipedia、Reddit、Stack Overflow和Stack Exchange网络上的大网站)。
如果你像原来的回答那样简单解码一个gzipped的响应,你会遇到类似这样的错误:

UnicodeDecodeError: 'utf8' codec can't decode byte 0x8b in position 1: unexpected code byte

为了正确解码一个gzipped的响应,你需要在Python 3中添加以下模块:

import gzip
import io

注意: 在Python 2中,你需要用 StringIO 替代 io

然后你可以像这样解析内容:

response = urlopen("https://example.com/gzipped-ressource")
buffer = io.BytesIO(response.read()) # Use StringIO.StringIO(response.read()) in Python 2
gzipped_file = gzip.GzipFile(fileobj=buffer)
decoded = gzipped_file.read()
content = decoded.decode("utf-8") # Replace utf-8 with the source encoding of your requested resource

这段代码会读取响应,并把字节放在一个缓冲区里。接着,gzip模块会用 GZipFile 函数读取这个缓冲区。之后,gzipped的文件可以再次读取成字节,最后解码成普通可读的文本。

2010年的原始回答:

我们能得到 link 的实际值吗?

此外,当我们尝试对已经编码的字节字符串使用 .encode() 时,通常会遇到这个问题。所以你可以先尝试解码,如下所示:

html = urllib.urlopen(link).read()
unicode_str = html.decode(<source encoding>)
encoded_str = unicode_str.encode("utf8")

举个例子:

html = '\xa0'
encoded_str = html.encode("utf8")

会失败并出现:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xa0 in position 0: ordinal not in range(128)

而:

html = '\xa0'
decoded_str = html.decode("windows-1252")
encoded_str = decoded_str.encode("utf8")

则成功且没有错误。请注意,“windows-1252”只是我用作 示例 的编码。我从 chardet 得到这个编码,它的置信度是0.5!(好吧,考虑到这是一个字符长度的字符串,你还能期待什么呢)你应该把它改成从 .urlopen().read() 返回的字节字符串的实际编码。

我还看到的另一个问题是,.encode() 字符串方法返回的是修改后的字符串,而不会直接修改原始字符串。所以如果你用 self.response.out.write(html),html并不是来自html.encode的编码字符串(如果这就是你最初想要的)。

正如Ignacio所建议的,检查源网页以获取从 read() 返回的字符串的实际编码。这个信息通常在Meta标签中或者响应的ContentType头里。然后把这个编码作为 .decode() 的参数。

不过要注意,不应该假设其他开发者会负责确保头信息和/或meta字符集声明与实际内容匹配。(这确实让人头疼,是的,我以前也是其中之一)。

撰写回答