【问题标题】:Combine multiple tags with lxml用lxml组合多个标签
【发布时间】:2015-08-30 11:05:41
【问题描述】:

我有一个 html 文件,如下所示:

...
<p>  
    <strong>This is </strong>  
    <strong>a lin</strong>  
    <strong>e which I want to </strong>  
    <strong>join.</strong>  
</p>
<p>
    2.
    <strong>But do not </strong>
    <strong>touch this</strong>
    <em>Maybe some other tags as well.</em>
    bla bla blah...
</p>
...

我需要的是,如果'p'块中的所有标签都是'strong',那么将它们组合成一行,即

<p>
    <strong>This is a line which I want to join.</strong>
</p>

不接触另一个块,因为它包含其他东西。

有什么建议吗?我正在使用 lxml。

更新:

到目前为止我试过了:

for p in self.tree.xpath('//body/p'):
        if p.tail is None: #no text before first element
            children = p.getchildren()
            for child in children:
                if len(children)==1 or child.tag!='strong' or child.tail is not None:
                    break
            else:
                etree.strip_tags(p,'strong')

使用这些代码,我能够在所需部分剥离强标签,给出:

<p>
      This is a line which I want to join.  
</p>  

所以现在我只需要一种方法将标签放回...

【问题讨论】:

  • 第一个块应该是&lt;strong&gt;This is &lt;/strong&gt; 等还是&lt;/strong&gt; 缺失?
  • @Scott 感谢您的指出,是的,我错过了。将编辑我的问题。
  • @har07 请看我的更新。

标签: python html xpath lxml


【解决方案1】:

我可以用 bs4 (BeautifulSoup) 做到这一点:

from bs4 import BeautifulSoup as bs

html = """<p>  
<strong>This is </strong>  
<strong>a lin</strong>  
<strong>e which I want to </strong>  
<strong>join.</strong>  
</p>
<p>
<strong>But do not </strong>
<strong>touch this</strong>
</p>"""

soup = bs(html)
s = ''
# note that I use the 0th <p> block ...[0],
# so make the appropriate change in your code
for t in soup.find_all('p')[0].text:
    s = s+t.strip('\n')
s = '<p><strong>'+s+'</strong></p>'
print s # prints: <p><strong>This is a line which I want to join.</strong></p>

然后使用replace_with():

p_tag = soup.p
p_tag.replace_with(bs(s, 'html.parser'))
print soup

打印:

<html><body><p><strong>This is a line which I want to join.</strong></p>
<p>
<strong>But do not </strong>
<strong>touch this</strong>
</p></body></html>

【讨论】:

  • 非常感谢。您的代码给了我很多直觉-请参阅我的更新。不幸的是,我不使用 BeautifulSoup(应该在前面提到过)。一旦我有声望,肯定会支持你的答案。
  • @lpounng 同样,我不使用 lxml,尽管它可能具有“替换标签”功能。 BeautifulSoup 只需快速安装 pip...
【解决方案2】:

我已经设法解决了我自己的问题。

for p in self.tree.xpath('//body/p'):
    if p.tail is None:  # some conditions specifically for my doc 
        children = p.getchildren()
        if len(children)>1:
            for child in children:
                #if other stuffs present, break
                if child.tag!='strong' or child.tail is not None: 
                    break
            else:
                # If not break, we find a p block to fix
                # Get rid of stuffs inside p, and put a SubElement in
                etree.strip_tags(p,'strong')
                tmp_text = p.text_content()
                p.clear()
                subtext = etree.SubElement(p, "strong")
                subtext.text = tmp_text

特别感谢@Scott,他帮助我找到了这个解决方案。虽然我不能将他的答案标记为正确,但我对他的指导同样感激。

【讨论】:

  • 干得好。 +1 用于回答您自己的问题。您可以接受(单击向上/向下箭头下方的复选标记)您自己的答案。
【解决方案3】:

或者,您可以使用更具体的 xpath 直接获取目标 p 元素:

p_target = """
//p[strong]
   [not(*[not(self::strong)])]
   [not(text()[normalize-space()])]
"""
for p in self.tree.xpath(p_target):
    #logic inside the loop can also be the same as your `else` block
    content = p.xpath("normalize-space()")
    p.clear()
    strong = etree.SubElement(p, "strong")
    strong.text = content

关于使用 xpath 的简要说明:

  • //p[strong] :找到 p 元素,在 XML/HTML 文档中的任何位置,具有子元素 strong...
  • [not(*[not(self::strong)])] : ..除了strong之外没有子元素...
  • [not(text()[normalize-space()])] : ..并且没有非空文本节点子节点。
  • normalize-space() :从当前上下文元素中获取所有文本节点,并与标准化为单个空格的连续空格连接

【讨论】:

  • 太棒了!在深入研究 lxml 时,我将来可能需要它。解释也很好!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-05
  • 1970-01-01
  • 2012-05-07
  • 2023-01-18
  • 2019-10-30
  • 2016-05-02
相关资源
最近更新 更多