使用Python解码HTML实体

19

我正在尝试解码来自NYTimes.com的HTML条目,但我无法弄清楚我做错了什么。

以此为例:

"U.S. Adviser’s Blunt Memo on Iraq: Time ‘to Go Home’"

我尝试了BeautifulSoup、decode('iso-8859-1')以及django.utils.encoding的smart_str,但都没有成功。


这个问题似乎经常出现,但没有好的解决方案。让我想写点自己的东西... - Kenan Banks
哈,我认为这是迄今为止我找到的最佳解决方案。我可能会尝试自己去做。如果我这样做了,我会发布我的解决方案。 - KeyboardInterrupt
@Triptych:这里有一个unescape()函数(https://dev59.com/7nM_5IYBdhLWcg3w1G2N#20715131)。 - jfs
4个回答

22
>>> from HTMLParser import HTMLParser
>>> print HTMLParser().unescape('U.S. Adviser’s Blunt Memo on Iraq: '
...                             'Time ‘to Go Home’')
U.S. Adviser’s Blunt Memo on Iraq: Time ‘to Go Home’

该函数在Python 2中没有记录。 它已在Python 3.4+中修复:在那里,它被公开为html.unescape()


4
对于未来的用户而言,这个答案之所以没有得到更多的赞同是因为它比现有的答案晚了4年。它似乎至少和其他答案一样好。 这个答案的优点是简单易懂(不像编写使用正则表达式来解释HTML标准的自定义函数那样复杂),并且使用了标准库(不像BeautifulSoup)。它的缺点是使用了一个未经记录的函数。 - Daniel Koverman

20

实际上你所拥有的并不是HTML实体。 这些 &.....; 有三种变体,例如       都表示U+00A0 不间断空格。

 (您所拥有的类型)是“数字字符引用”(十进制)。
  是“数字字符引用”(十六进制)。
  是一个实体。

更多阅读: http://htmlhelp.com/reference/html40/entities/

在此处,您将找到Python2.x的代码,在一次扫描输入中执行所有三个操作:http://effbot.org/zone/re-sub.htm#unescape-html


18

这个确实有效:

from BeautifulSoup import BeautifulStoneSoup
s = "U.S. Adviser’s Blunt Memo on Iraq: Time ‘to Go Home’"
decoded = BeautifulStoneSoup(s, convertEntities=BeautifulStoneSoup.HTML_ENTITIES)
如果你想要一个字符串而不是 Unicode 对象,你需要将其解码为支持所使用字符的编码;ISO-8859-1 不支持这些字符:
result = decoded.encode("UTF-8")

可惜的是,在像这样的事情上,您需要使用外部模块;简单的HTML/XML实体解码应该在标准库中,不应要求我使用像“BeautifulStoneSoup”这样毫无意义的类名的库。 (类和函数名称不应该是“有创意的”,它们应该是有意义的。)


2
lxml,不幸的是也不在标准库中,但提供了一个Beautiful Soup解析器(以及更多)并且名称相对较少“创意”。 - Ned Deily
1
支持实体解码的标准库(模块htmlentitydefs)已经存在。OP拥有的是(十进制)数字字符引用,而不是实体。 - John Machin
使用BeautifulSoup而不是BeautifulStoneSoup同样有效 - 减少了一步“创意” :) - Beni Cherniavsky-Paskin
“names should not be "creative"” 这是一个铁律,还是个人选择? - TankorSmash
@TankorSmash:除了编译器外,没有任何权威强制你遵循任何编码标准,但这似乎对我来说是常识。 - Glenn Maynard

6

试一下这个:

import re

def _callback(matches):
    id = matches.group(1)
    try:
        return unichr(int(id))
    except:
        return id

def decode_unicode_references(data):
    return re.sub("&#(\d+)(;|(?=\s))", _callback, data)

data = "U.S. Adviser’s Blunt Memo on Iraq: Time ‘to Go Home’"
print decode_unicode_references(data)

UnicodeEncodeError:'charmap'编解码器无法在位置12处编码字符u'\u2019':该字符映射到<undefined>。无论我尝试什么,似乎都会出现这个错误。 - KeyboardInterrupt
你能提供更多的代码吗?我刚刚用我写的函数尝试了一下,字符2019可以正常显示为:ߣ。 - Evan Fosmark
关于您的正则表达式,有几个问题:(1) 应该使用\d而不是\w吧? 正则表达式将匹配&#xa0;&nbsp;,但然后它将在int()中崩溃 (2) 允许字符引用(它不是实体)以空格而不是';'结尾似乎非常宽容 - 您不应该提到这一点吗? (3) 最后一部分是否最好写成[;\s]? - John Machin
约翰,你在第一点上部分正确。它不会匹配   ,因为它不以 &# 开头,但是确实应该使用 \d。关于第二点允许以空格结尾,应该注意的是,即使它不太美观,它仍然被支持。我已经按照以下方式更新了代码:(1)将其更改为\d,(2)使回调更强大,(3)使用前瞻断言进行结尾空格,而不是像之前那样吸收它。 - Evan Fosmark
Evan,感谢你的启示,特别是关于空格的容忍度,这是我不知道的。我通过查看HTML 4.01和2.0规范获得了更多线索。它们引用了SGML标准(ISO 8879)。成本= CHF 238(!)所以我没有阅读它,但是HTML 2.0评论说当引用后面的字符本来是名称的一部分时才需要“;”。使用空格-/ X A和&代替;进行FF,IE和Opera实验都产生了相同的结果:它们终止了引用并且没有被吞噬。我期待着您的更新解决方案;-) - John Machin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接