2

我在爬网时遇到了一些非常麻烦的字符串。特别是,一个页面宣传为 is UTF-7,尽管它并不完全UTF-7是问题所在。我不关心表达文本的确切意图,但我只需要进入UTF-8下游消费。

我面临的奇怪之处是我能够得到一个unicode不能先UTF-8编码然后解码的字符串。我已经尽可能多地提取字符串,同时仍然显示错误:

bytes = [43, 105, 100, 41, 46, 101, 95, 39, 43, 105, 100, 43]
string = ''.join(chr(c) for c in bytes)

# This particular string happens to be advertised as UTF-7, though it is
# a bit malformed. We'll ignore these errors when decoding it.
decoded = string.decode('utf-7', 'ignore')

# This decoded string, however, cannot be encoded into UTF-8 and back:
error = decoded.encode('utf-8').decode('utf-8')

我已经在许多系统上成功地尝试过这个:Mac 10.5.7 上的 Python 2.7.1 和 2.6.7,CentOS 上的 Python 2.7.2 和 2.6.8。不幸的是,在我们需要它工作的机器上,Ubuntu 12.04 上的 Python 2.7.3 失败了。在失败的系统上,我看到:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/usr/lib/python2.7/encodings/utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xf7 in position 4: invalid start byte

以下是我在工作系统与非工作系统上看到的一些中间值:

# Working:
>>> repr(decoded)
'u".e_\'\\u89df"'
>>> repr(decoded.encode('utf-8'))
'".e_\'\\xe8\\xa7\\x9f"'

# Non-working:
>>> repr(decoded)
'u".e_\'\\U089d89df"'
>>> repr(decoded.encode('utf-8'))
'".e_\'\\xf7\\x98\\xa7\\x9f"'

两者在第一次编码后有所不同,但为什么对我来说仍然是个谜。我这是缺少一些字符表或辅助库的问题,因为看起来 2.7.2 和 2.7.3 之间的任何东西都不能解释这种行为。在它正常工作的系统上,打印 unicode 实体会显示一个中文符号,但在系统上它不会显示一个占位符。

这让我想到了一个问题:这样的问题对任何人来说都很熟悉吗,或者是否有人知道我在出现问题的系统上可能缺少哪些支持库?

4

1 回答 1

0

这里的问题是,由于某种原因,UTF-7 解码会返回非法的 Unicode 字符。

它基本上没有记录当你有一个unicode包含非法字符的对象时会发生什么。C API 基本上只是告诉你“不要那样做,否则会坏掉”。Python API 没有提到它,因为它应该是不可能的,除非你用 C API 做了一些未定义的事情,这已经被介绍过了。

当然,除非内置编解码器中的错误导致它代表您执行未定义的操作。这似乎就是这里发生的事情。

您在某些平台上看到此问题但在其他平台上没有看到的一个合理原因是工作平台都使用窄 Unicode,这意味着这个问题不可能发生。(您不能> 0x10FFFF在代码点只有 2 个字节的平台上拥有代码点,除非使用 UTF-16 代理字节,因此您可能会在代理编码中获得异常或忽略。)

事实上,你得到的非法字符是\U089d89df,而你在 Mac 上得到的字符(系统 Python 构建是窄 Unicode)是\u89df,这非常暗示一些代码在假设窄 Unicode 的地方走捷径。但要真正追踪错误,您需要查看源代码中的多个位置,并比较 Python 在每个平台上的构建方式(窄与宽可能不是唯一的区别),和/或查看错误和更改日志...</p>

最终,如果您想在具有该 Python 构建的 Ubuntu 系统上运行,除非您想编写自己的自定义 C 模块,否则您将不得不解决该错误,对吗?

因此,您可能只是在寻找一个简单的解决方法。在这种情况下,这应该有效:

decoded = u''.join(c for c in decoded if ord(c) <= 10FFFF)

这会去除代码点大于最大合法 Unicode 字符的任何字符。它应该在任何存在的地方解决问题,否则是无害的(除了一些浪费的 CPU 时间)。

对于许多应用程序,您实际上只需要处理 BMP 字符,并且补充和专用平面中的任何内容都比实际数据更容易出错,因此使用它可能更简单或更健壮,0xFFFF而不是0x10FFFF.

于 2013-04-17T22:02:55.717 回答