用Python编辑HTML时,lxml将美好的HTML实体转换为奇怪的编码

10 投票
3 回答
10581 浏览
提问于 2025-04-16 11:05

我正在尝试使用Python(配合pyquery和lxml)来修改和清理一些HTML代码。

Eg. html = "<div><!-- word style><bleep><omgz 1,000 tags><--><p>It&#146;s a spicy meatball!</div>"

lxml.html.clean函数clean_html()效果不错,但它会把一些漂亮的HTML实体,比如

&#146; 

替换成一些看起来很奇怪的unicode字符串。

\xc2\x92

这些unicode在不同的浏览器中显示得很奇怪(比如Firefox和Opera使用自动编码、utf8、latin-1等),有时候会像一个空框框。请问我该如何阻止lxml转换这些实体呢?我怎么才能让它们都用latin-1编码呢?这听起来有点奇怪,因为这个模块是专门为HTML设计的。

我不太确定里面有哪些字符,所以不能直接使用

replace("\xc2\x92","&#146;").

我试过使用

clean_html(html).encode('latin-1')

但是unicode问题依然存在。

当然,我会告诉大家不要用Word来写HTML,但我总会听到这样的话:

"这是我喜欢的方式,你不能让我改变!"

补充:一个使用BeautifulSoup的解决方案:

from BeautifulSoup import BeautifulSoup, Comment
soup = BeautifulSoup(str(desc[desc_type]))
                    comments = soup.findAll(text=lambda text:isinstance(text, Comment))
                    [comment.extract() for comment in comments]
                    print soup

3 个回答

1

你也可以把utf8字符串转换成带有xml字符的ascii格式。

result = result.decode('utf-8').encode('ascii', 'xmlcharrefreplace')
2

我想&#146;应该是个引号。值为146的字节对象chr(146),用cp1252解码后是一个引号:

In [46]: print(chr(146).decode('cp1252'))
’

所以,你可以这样做:

import lxml.html.clean as clean
import re

html = "<div><!-- word style><bleep><omgz 1,000 tags><--><p>It&#146;s a spicy meatball!</div>"

html=re.sub('&#(\d+);',lambda m: chr(int(m.group(1))).decode('cp1252'),html)
print(html)
# <div><!-- word style><bleep><omgz 1,000 tags><--><p>It’s a spicy meatball!</div>
print(type(html))
# <type 'unicode'>
print(clean.clean_html(html))
# <div><p>It’s a spicy meatball!</p></div>

或者,

doc=lh.fromstring(html)
clean.clean(doc)

注意,这个引号的unicode编码值是8217。也就是说,ord(chr(146).decode('cp1252'))的结果是8217,所以lh.tostring返回:

print(lh.tostring(doc))
# <div><p>It&#8217;s a spicy meatball!</p></div>   

你可以这样在cp1252中重新编码:

print(repr(lh.tostring(doc,encoding='cp1252')))
# '<div><p>It\x92s a spicy meatball!</p></div>'

不过,我不知道怎么让lxml返回

'<div><p>It&#146;s a spicy meatball!</p></div>'

以匹配你用BeautifulSoup代码得到的输出。不过,很明显可以用正则表达式来实现(反向操作我上面做的),但我不确定这样做是否必要或明智,因为lxml应该已经返回了其他应用程序能理解的html。

result=re.sub('&#(\d+);',lambda m: '&#{n};'.format(
    n=ord(unichr(int(m.group(1))).encode('cp1252'))),
            lh.tostring(doc))
print(result)
# <div><p>It&#146;s a spicy meatball!</p></div>
12

有几个小知识,如果你知道了,就能找到最简单、最好的解决办法:

  • clean_html()这个函数会返回你给它的输入类型:如果你给它一个字符串,它就会返回一个字符串;如果你给它一个元素(Element)或者元素树(ElementTree),它就会分别返回元素或元素树。

  • 你可以通过给lxml.html.tostring()方法或者树的write()方法设置编码选项,来控制元素或元素树的序列化方式(也就是把它们转换成字符串的过程)。比如,你可以用encoding='utf-8'来设置编码。

  • 任何可以用这种编码表示的内容,都会输出为编码后的字符串,而那些不能用的内容会被“转义”成实体。使用encoding="ascii"会强制把任何非ascii字符转换成你想要的“好看”的实体。

综合起来,这意味着:首先把字符串解析成一个元素(或者如果你愿意,也可以解析成树),然后清理它,最后根据需要把它转换成字符串:

html = lxml.html.fromstring("<div><!-- word style><bleep><omgz 1,000 tags><--><p>It&#146;s a spicy meatball!</div>")
html = clean_html(html)
result = lxml.html.tostring(html, encoding="ascii")

(还有一个稍微复杂一点的技巧,就是在unicode字符串的encode()方法中使用errors参数:试试用s.encode('ascii', 'xmlcharrefreplace')来编码一个包含“特殊”字符的unicode字符串,看看会发生什么……)

撰写回答