用Python编辑HTML时,lxml将美好的HTML实体转换为奇怪的编码
我正在尝试使用Python(配合pyquery和lxml)来修改和清理一些HTML代码。
Eg. html = "<div><!-- word style><bleep><omgz 1,000 tags><--><p>It’s a spicy meatball!</div>"
lxml.html.clean函数clean_html()效果不错,但它会把一些漂亮的HTML实体,比如
’
替换成一些看起来很奇怪的unicode字符串。
\xc2\x92
这些unicode在不同的浏览器中显示得很奇怪(比如Firefox和Opera使用自动编码、utf8、latin-1等),有时候会像一个空框框。请问我该如何阻止lxml转换这些实体呢?我怎么才能让它们都用latin-1编码呢?这听起来有点奇怪,因为这个模块是专门为HTML设计的。
我不太确定里面有哪些字符,所以不能直接使用
replace("\xc2\x92","’").
我试过使用
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 个回答
你也可以把utf8字符串转换成带有xml字符的ascii格式。
result = result.decode('utf-8').encode('ascii', 'xmlcharrefreplace')
我想’
应该是个引号。值为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’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’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’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’s a spicy meatball!</p></div>
有几个小知识,如果你知道了,就能找到最简单、最好的解决办法:
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’s a spicy meatball!</div>")
html = clean_html(html)
result = lxml.html.tostring(html, encoding="ascii")
(还有一个稍微复杂一点的技巧,就是在unicode字符串的encode()
方法中使用errors参数:试试用s.encode('ascii', 'xmlcharrefreplace')
来编码一个包含“特殊”字符的unicode字符串,看看会发生什么……)