获取lxml中标签内的所有文本

106
我想写一个代码片段,可以在lxml中获取所有三个实例中标签内的文本,包括代码标签。我尝试过tostring(getchildren()) ,但它会忽略标签之间的文本。我在API中搜索相关函数时并没有很好的运气。你能帮帮我吗?
<!--1-->
<content>
<div>Text inside tag</div>
</content>
#should return "<div>Text inside tag</div>

<!--2-->
<content>
Text with no tag
</content>
#should return "Text with no tag"


<!--3-->
<content>
Text outside tag <div>Text inside tag</div>
</content>
#should return "Text outside tag <div>Text inside tag</div>"

1
谢谢 - 我正在尝试编写一个RSS源解析器,并显示<content>标签内的所有内容,其中包括来自提供程序的HTML标记。 - Kevin Burke
15个回答

104
只需使用node.itertext()方法,例如:
 ''.join(node.itertext())

3
这个功能很好,但会去除你想要保留的任何标签。 - Yablargo
这个字符串里面不应该有空格吗?还是我漏掉了什么? - Private
1
@私有的 这取决于您的具体需求。例如,我可以使用标记 <word><pre>con</pre>gregate</word> 来指示单词中的前缀。假设我想提取没有标记的单词。如果我使用带空格的 .join,那么我会得到 "con gregate",而不带空格则得到 "congregate" - Louis
虽然上面的答案被接受了,但这才是我真正想要的。 - jason m

91

6
text_content()会删除所有标记,而OP想要保留标记内部的标记。 - benselme
11
为什么我使用text_content,它会显示AttributeError: 'lxml.etree._Element' object has no attribute 'text_content'错误提示。 - roger
8
@roger 只有在您的树是HTML(即如果使用lxml.html中的方法解析),才可以使用text_content() - Louis
@EdSummers 非常感谢!在解析<p>标签时,这非常有用。当我在XPath中使用text()时,我错过了文本(例如嵌套链接),但是您的方法对我很有效! - Sam Chats
3
正如Louis所指出的那样,这仅适用于使用lxml.html解析的树。Arthur Debert的解决方案使用itertext()是通用的。 - SergiyKolesnikov
为了更清晰,text_content是HTML元素方法,itertext是元素方法。 - sfy

48

尝试:

def stringify_children(node):
    from lxml.etree import tostring
    from itertools import chain
    parts = ([node.text] +
            list(chain(*([c.text, tostring(c), c.tail] for c in node.getchildren()))) +
            [node.tail])
    # filter removes possible Nones in texts and tails
    return ''.join(filter(None, parts))

示例:

from lxml import etree
node = etree.fromstring("""<content>
Text outside tag <div>Text <em>inside</em> tag</div>
</content>""")
stringify_children(node)

结果生成:'\n<div> 标签外的文本 <em>标签内的文本</em></div>\n'


2
@delnan不需要,tostring已经处理了递归情况。你让我开始怀疑了,所以我在真实代码上尝试了一下,并更新了答案并附上了一个例子。谢谢指出。 - albertov
5
代码出现问题,会生成重复的内容:
stringify_children(lxmlhtml.fromstring('A<div>B</div>C')) 'A<p>A</p>B<div>B</div>CC'
- hoju
1
为了修复@hoju报告的错误,在tostring()函数中添加参数with_tail=False。因此,tostring(c, with_tail=False)。这将解决尾部文本(C)的问题。要解决前缀文本(A)的问题,似乎是tostring()函数中添加了<p>标签的错误,因此这不是OP代码中的错误。 - anana
1
第二个 bug 可以通过将 c.textparts 列表中移除来修复。我提交了一个已经修复了这些 bug 的新答案。 - anana
5
应该在Python 3上运行时添加tostring(c, encoding=str) - Antoine Dusséaux
显示剩余2条评论

22
一种 albertov 的 stringify-content 版本,解决了 hoju 报告的 bugs
def stringify_children(node):
    from lxml.etree import tostring
    from itertools import chain
    return ''.join(
        chunk for chunk in chain(
            (node.text,),
            chain(*((tostring(child, with_tail=False), child.tail) for child in node.getchildren())),
            (node.tail,)) if chunk)

22

下面这段使用Python生成器的代码片段运行完美且非常高效。

''.join(node.itertext()).strip()


1
如果节点是从缩进文本中获取的,根据解析器的不同,它通常会有缩进文本,而itertext()将在正常文本片段中交织。根据实际设置,以下内容可能有用:' '.join(node.itertext('span', 'b')) - 仅使用<span><b>标签中的文本,丢弃缩进中的"\n "标签。 - Zoltan K.

6

以下是一个最简单的代码片段,它对我很有用,并且根据文档,它可以使用XPath查找文本:

etree.tostring(html, method="text")

其中etree是一个节点或标签,你正在尝试读取它的完整文本。请注意,它并不会去除脚本和样式标签。


4
去除HTML标签 - Dennis Golomazov

6
定义stringify_children采用这种方式可能会更简单:
from lxml import etree

def stringify_children(node):
    s = node.text
    if s is None:
        s = ''
    for child in node:
        s += etree.tostring(child, encoding='unicode')
    return s

或者在一行中

return (node.text if node.text is not None else '') + ''.join((etree.tostring(child, encoding='unicode') for child in node))

在IT技术领域,本文需要翻译的内容如下:

原理与此答案中相同:将子节点的序列化交给lxml。在这种情况下,nodetail 部分并不重要,因为它位于结束标签之后。请注意,根据需要可以更改 encoding参数。

另一个可能的解决方案是序列化节点本身,然后剥离开始和结束标签:

def stringify_children(node):
    s = etree.tostring(node, encoding='unicode', with_tail=False)
    return s[s.index(node.tag) + 1 + len(node.tag): s.rindex(node.tag) - 2]

这段代码有些可怕。仅当node没有属性时,此代码才正确,并且即使在那种情况下,我也不认为有人想使用它。


1
如果 node.text 不为 None,则可以简写为 node.txt'' - yprez
在这里稍微玩一下拉撒路(复活的笑话...不是俏皮话),但我已经看到过这篇文章很多次,当我无法准确记住我做了什么时。鉴于node.text只返回未作为迭代器的一部分而看不见的文本(当直接迭代节点时,与node.getChildren()相同),似乎可以将解决方案从此简化为:''.join([node.text or ''] + [etree.tostring(e) for e in node]) - Tim Alexander
这个实际上可以在Python 3中运行,而得到最多赞的答案则不行。 - Andrey

5
import urllib2
from lxml import etree
url = 'some_url'

获取URL
test = urllib2.urlopen(url)
page = test.read()

获取包含表格标签的所有HTML代码
tree = etree.HTML(page)

XPath选择器

table = tree.xpath("xpath_here")
res = etree.tostring(table)

res是表格的HTML代码,这对我来说很有用。

因此,您可以使用xpath_text()提取标签内容,使用tostring()提取包括其内容的标签。

div = tree.xpath("//div")
div_res = etree.tostring(div)

text = tree.xpath_text("//content") 

or text = tree.xpath("//content/text()")

div_3 = tree.xpath("//content")
div_3_res = etree.tostring(div_3).strip('<content>').rstrip('</')

使用strip方法的最后一行不太好,但它确实有效。


对我来说,这个方法足够好,并且确实简单得多。我知道每次都有一个<details></details>标签,我可以将其删除。 - Yablargo
1
xpath_text已经从lxml中移除了吗?它显示AttributeError: 'lxml.etree._Element'对象没有属性'xpath_text' - roger

3

这里有一个简单的优化建议,因为已经有答案了。如果你想清除标签内的文本:

clean_string = ' '.join([n.strip() for n in node.itertext()]).strip()

2
作为对@Richard上面评论的回应,如果你修改stringify_children函数的读取方式为:
 parts = ([node.text] +
--            list(chain(*([c.text, tostring(c), c.tail] for c in node.getchildren()))) +
++            list(chain(*([tostring(c)] for c in node.getchildren()))) +
           [node.tail])

看起来他指的是避免重复。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接