如何使用lxml移除HTML实体(和更多)?

5 投票
2 回答
5204 浏览
提问于 2025-04-16 16:55

我有一个HTML文件,里面有一些文本,经过lxml.html parselxml.html clean处理后,使用etree.tostring(table, pretty_print=True)得到的结果是这样的:

 <tr><td>&#13;
224&#13;
9:00 am&#13;
-3:00 pm&#13;
NPHC Leadership</td>&#13;
<td>&#13;
<font>ALSO IN 223; WALL OPEN</font></td>&#13;

我找到的关于lxml的文档有点零散。我已经做了很多工作才能达到这个阶段,但我想做的是去掉所有标签,只保留<table><td><tr>。我还想去掉这些标签的所有属性,并且想去掉像&#13;这样的实体。

目前我用来去掉属性的方法是:

    etree.strip_attributes(tree, 'width', 'href', 'style', 'onchange',
                           'ondblclick', 'class', 'colspan', 'cols',
                           'border', 'align', 'color', 'value',
                           'cellpadding', 'nowrap', 'selected',
                           'cellspacing')

这个方法效果不错,但我觉得应该有更好的办法。似乎应该有一些比较简单的方法可以实现我想要的,但我找不到合适的例子。

我试过使用Cleaner,但是当我传入allow_tags时,像这样:

错误:Cleaner(allow_tags=['table', 'td', 'tr']).clean_html(tree),结果出现了这个错误:

ValueError: 同时传入allow_tags和remove_unknown_tags是没有意义的。而且,当我添加remove_unknown_tags=False时,又出现了这个错误:

Traceback (most recent call last):
  File "parse.py", line 73, in <module>
    SParser('schedule.html').test()
  File "parse.py", line 38, in __init__
    self.clean()
  File "parse.py", line 42, in clean
    Cleaner(allow_tags=['table', 'td', 'tr'], remove_unknown_tags=False).clean_html(tree)
  File "/usr/lib/python2.6/dist-packages/lxml/html/clean.py", line 488, in clean_html
    self(doc)
  File "/usr/lib/python2.6/dist-packages/lxml/html/clean.py", line 390, in __call__
    el.drop_tag()
  File "/usr/lib/python2.6/dist-packages/lxml/html/__init__.py", line 191, in drop_tag
    assert parent is not None
AssertionError

所以,总结一下:

  1. 我想去掉HTML实体,比如&#13;
  2. 我想去掉所有标签,除了<table><tr><td>
  3. 我想去掉剩下标签的所有属性。

任何帮助都会非常感谢!

2 个回答

4

这里有一个例子,展示了如何去掉所有属性,只保留 [table, tr, td] 这些标签。我还加了一些Unicode字符来帮助说明。

DATA = '''<table border="1"><tr colspan="4"><td rowspan="2">\r
224&#13;
&#8220;hi there&#8221;
9:00 am\r
-3:00 pm&#13;
NPHC Leadership</td>\r
<td rowspan="2">\r
<font>ALSO IN 223; WALL OPEN</font></td>\r
</table>'''

import lxml.html
from lxml.html import clean

def _clean_attrib(node):
    for n in node:
        _clean_attrib(n)
    node.attrib.clear()

tree = lxml.html.fromstring(DATA)
cleaner = clean.Cleaner(allow_tags=['table','tr','td'],
                        remove_unknown_tags=False)
cleaner.clean_html(tree)
_clean_attrib(tree)

print lxml.html.tostring(tree, encoding='utf-8', pretty_print=True, 
                         method='html')

结果:

<table><tr>
<td>
224
“hi there”
9:00 am
-3:00 pm
NPHC Leadership</td>
<td>
<font>ALSO IN 223; WALL OPEN</font>
</td>
</tr></table>

你确定要去掉所有的字符实体吗?&#13; 代表的是换行符,当 lxml 解析文档时,它会把所有的字符实体转换成对应的Unicode字符。

字符实体是否显示出来,还取决于输出的方法和编码方式。例如,如果你使用 lxml.html.tostring(encoding='ascii', method='xml'),那么 '\r' 和Unicode字符会以字符实体的形式输出:

<table>
  <tr><td>&#13;
  &#8220;hi there&#8221;
...
3

对我来说,基于文本、标签和尾部这几个基本元素来写代码,会让我们更容易调整行为,做到我们想要的效果,同时也能加入错误检查(比如确保输入的数据里没有意外的标签)。

这里提到的if语句是因为当长度为零时,它们返回的是None,而不是空字符串""。

def ctext(el):
    result = [ ]
    if el.text:
        result.append(el.text)
    for sel in el:
        if sel.tag in ["tr", "td", "table"]:
            result.append("<%s>" % sel.tag)
            result.append(ctext(sel))
            result.append("</%s>" % sel.tag)
        else:
            result.append(ctext(sel))
        if sel.tail:
            result.append(sel.tail)
    return "".join(result)

html = """your input string"""
el = lxml.html.fromstring(html)
print ctext(el)

记住它们之间的关系是:

  <b>text of the bold <i>text of the italic</i> tail of the italic</b>

撰写回答