我熟悉etree的strip_tags
和strip_elements
方法,但我正在寻找一种直接剥离标记(并保留其内容)只包含特定属性/值的方法。
例如:我想从具有span
属性的树(div
l)中删除所有xhtm
或class='myclass'
标记(或其他元素) / value(保留元素的内容,如strip_tags
)。同时,不 class='myclass'
的那些元素应保持不变。
相反:我想要一种方法从树中剥离所有“裸”spans
或divs
。仅表示具有绝对 no 属性的spans
/ divs
(或任何其他元素)。 属于属性(任何)的相同元素保持不变。
我觉得我错过了一些明显的东西,但是我已经找了很长时间没有运气了。
答案 0 :(得分:11)
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。
编辑:我刚刚注意到您在问题中特别提到了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_tags
和keep_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)
标记来调整代码,并将其附加到父级,但对于我的情况,它是开销。