使用Python和lxml仅剥离具有特定属性/值的标记

时间:2014-02-10 19:08:59

标签: python lxml

我熟悉etree的strip_tagsstrip_elements方法,但我正在寻找一种直接剥离标记(并保留其内容)只包含特定属性/值的方法。

例如:我想从具有span属性的树(div l)中删除所有xhtmclass='myclass'标记(或其他元素) / value(保留元素的内容,如strip_tags)。同时, class='myclass'的那些元素应保持不变。

相反:我想要一种方法从树中剥离所有“裸”spansdivs。仅表示具有绝对 no 属性的spans / divs(或任何其他元素)。 属于属性(任何)的相同元素保持不变。

我觉得我错过了一些明显的东西,但是我已经找了很长时间没有运气了。

3 个答案:

答案 0 :(得分:11)

HTML

lxml HTML元素有一个方法drop_tag(),您可以调用lxml.html解析的树中的任何元素。

它与strip_tags类似,因为它删除了元素,但保留了文本,并且可以在元素上调用 - 这意味着您可以轻松选择元素对XPath表达式不感兴趣,然后循环它们并删除它们:

<强> doc.html

<html>
    <body>
        <div>This is some <span attr="foo">Text</span>.</div>
        <div>Some <span>more</span> text.</div>
        <div>Yet another line <span attr="bar">of</span> text.</div>
        <div>This span will get <span attr="foo">removed</span> as well.</div>
        <div>Nested elements <span attr="foo">will <b>be</b> left</span> alone.</div>
        <div>Unless <span attr="foo">they <span attr="foo">also</span> match</span>.</div>
    </body>
</html>

<强> strip.py

from lxml import etree
from lxml import html

doc = html.parse(open('doc.html'))
spans_with_attrs = doc.xpath("//span[@attr='foo']")

for span in spans_with_attrs:
    span.drop_tag()

print etree.tostring(doc)

<强>输出:

<html>
    <body>
        <div>This is some Text.</div>
        <div>Some <span>more</span> text.</div>
        <div>Yet another line <span attr="bar">of</span> text.</div>
        <div>This span will get removed as well.</div>
        <div>Nested elements will <b>be</b> left alone.</div>
        <div>Unless they also match.</div>
    </body>
</html>

在这种情况下,XPath表达式//span[@attr='foo']选择具有值span的属性attr的所有foo元素。有关如何构造XPath表达式的更多详细信息,请参阅此XPath tutorial

XML / XHTML

编辑我刚刚注意到您在问题中特别提到了XHTML,根据文档更好地将其解析为XML。不幸的是,drop_tag()方法实际上只适用于HTML文档中的元素。

因此,对于XML来说,它有点复杂:

<强> doc.xml

<document>
    <node>This is <span>some</span> text.</node>
    <node>Only this <span attr="foo">first <b>span</b></span> should <span>be</span> removed.</node>
</document>

<强> strip.py

from lxml import etree


def strip_nodes(nodes):
    for node in nodes:
        text_content = node.xpath('string()')

        # Include tail in full_text because it will be removed with the node
        full_text = text_content + (node.tail or '')

        parent = node.getparent()
        prev = node.getprevious()
        if prev:
            # There is a previous node, append text to its tail
            prev.tail += full_text
        else:
            # It's the first node in <parent/>, append to parent's text
            parent.text = (parent.text or '') + full_text
        parent.remove(node)


doc = etree.parse(open('doc.xml'))
nodes = doc.xpath("//span[@attr='foo']")
strip_nodes(nodes)

print etree.tostring(doc)

<强>输出:

<document>
    <node>This is <span>some</span> text.</node>
    <node>Only this first span should <span>be</span> removed.</node>
</document>

如您所见,这将使用递归文本内容替换节点所有子节点。我真的希望这是你想要的,否则事情变得更加复杂; - )

注意上次修改已更改相关代码。

答案 1 :(得分:1)

我遇到了同样的问题,经过一番考虑之后有了这个相当愚蠢的想法,这是借用Perl在线游戏中的正则表达式Markup:如何用element.iterfind带来的所有权力来捕获所有不需要的元素,将这些元素重命名为不太可能的东西,然后删除所有这些元素?

是的,这并不是绝对干净和健壮,因为您总是可能有一个实际使用您选择的“不太可能”的标记名称的文档,但生成的代码相当干净且易于维护。如果您确实需要确保文件中已经不存在您选择的任何“不太可能”的名称,您可以随时检查它是否已存在,并且仅在您找不到任何预先存在的情况下进行重命名该名称的标签。

<强> doc.xml

<document>
    <node>This is <span>some</span> text.</node>
    <node>Only this <span attr="foo">first <b>span</b></span> should <span>be</span> removed.</node>
</document>

<强> strip.py

from lxml import etree
xml = etree.parse("doc.xml")
deltag ="xxyyzzdelme"
for el in xml.iterfind("//span[@attr='foo']"):
    el.tag = deltag
etree.strip_tag(xml, deltag)
print(etree.tostring(xml, encoding="unicode", pretty_print=True))

<强>输出

<document>
     <node>This is <span>some</span> text.</node>
     <node>Only this first <b>span</b> should <span>be</span> removed.</node>
</document>

答案 2 :(得分:0)

我有同样的问题。但在我的情况下,场景更容易,我有一个选项 - 不删除标签,只是清除它,我们的用户看到渲染的HTML,如果我有例如

<div>Hello <strong>awesome</strong> World!</div>

我希望通过css选择器strong清除div > strong标记并保存尾部上下文,在lxml中你不能使用strip_tagskeep_tail选择器,你可以只删除标签,它让我发疯。如果你只删除<strong>awesome</strong>节点,你还可以删除这个尾部 - “World!”,包含strong标签的文本。 输出将如下:

<div>Hello</div>

对我来说好吧:

<div>Hello <strong></strong> World!</div>

不再为用户提供 awesome

doc = lxml.html.fromstring(markup)
selector = lxml.cssselect.CSSSelector('div > strong')
for el in list(selector(doc)):
    if el.tail:
        tail = el.tail
        el.clear()
        el.tail = tail
    else:
        #if no tail, we can safety just remove node
        el.getparent().remove(el)

您可以使用带有strong调用的物理删除element.remove(child)标记来调整代码,并将其附加到父级,但对于我的情况,它是开销。