BeautifulSoup解析树上的深度优先遍历

16

有没有一种方法可以在BeautifulSoup解析树上执行DFT(深度优先遍历)?我试图做的是从根节点开始,通常是,获取所有子元素,然后对于每个子元素获取它们的子元素,等等,直到我到达一个叶节点,在这一点上,我将构建回到树。问题是,我似乎找不到一种方法来实现这一点。我找到了findChildren方法,但似乎只是将整个页面多次放入列表中,每个后续条目都会减少。我可能可以使用此方法进行遍历,但除了列表中的最后一个条目之外,似乎没有任何方法将条目标识为终端节点还是非终端节点。有什么想法吗?


我不知道使用BeautifulSoup是否有解决方案,但是我有一个使用lxml库的解决方案可行。使用BeautifulSoup是强制性的吗?如果不是,我可以建议使用lxml的解决方案。 - user225312
2个回答

19

mytag.find_all() 已经可以做到这一点:

如果你调用 mytag.find_all(),Beautiful Soup 将会检查 mytag 的所有后代节点:它的子节点、子节点的子节点等等。

from bs4 import BeautifulSoup  # pip install beautifulsoup4

soup = BeautifulSoup("""<!doctype html>
<div id=a>A
  <div id=1>A1</div>
  <div id=2>A2</div>
</div>
<div id=b>B
  <div id=I>BI</div>
  <div id=II>BII</div>
</div>
""")

for div in soup.find_all("div", recursive=True):
    print(div.get('id'))

输出

a
1
2
b
I
II

输出证实了这是深度优先遍历。

旧版Beautiful Soup 3的答案:

recursiveChildGenerator()已经实现了这个功能:

soup = BeautifulSoup.BeautifulSoup(html)
for child in soup.recursiveChildGenerator():
     name = getattr(child, "name", None)
     if name is not None:
         print name
     elif not child.isspace(): # leaf node, don't print spaces
         print child

输出

对于来自@msalvadores的答案的HTML:

html
ul
li
Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
li
Aliquam tincidunt mauris eu risus.
li
Vestibulum auctor dapibus neque.
html

注意:由于示例包含两个开放的<html>标签,因此html会打印两次。


@Sebastian 我已经修复了HTML示例,以防您希望编辑您的答案。很高兴知道 recursiveChildGenerator 存在(加1)。 - Manuel Salvadores
2
谢谢 - 这对我有用!对于那些可能正在寻找此文档的人,在BeautifulSoup 4中,recursiveChildGenerator()已更名为descendants() - J. Taylor
@J.Taylor,关于descendants(一个属性,所以没有括号)的文档说明它执行广度优先遍历。我不确定这个答案是否仍适用于当前的bs4版本? - Charles Langlois
2
@CharlesLanglois:要获取DFT,可以在bs4中使用find_all(recursive=True)。我已经更新了答案。 - jfs
在一般情况下,使用soup.find_all(recursive=True)代替soup.find_all("div", recursive=True)可以简单地获取所有子元素。 - Bálint Sass

6

我认为你可以使用"childGenerator"方法,并递归地使用它以深度优先的方式解析树结构。

def recursiveChildren(x):
   if "childGenerator" in dir(x):
      for child in x.childGenerator():
          name = getattr(child, "name", None)
          if name is not None:
             print "[Container Node]",child.name
          recursiveChildren(child)
    else:
       if not x.isspace(): #Just to avoid printing "\n" parsed from document.
          print "[Terminal Node]",x

if __name__ == "__main__":
    soup = BeautifulSoup(your_data)
    for child in soup.childGenerator():
        recursiveChildren(child)

使用"childGenerator" in dir(x),我们可以确定一个元素是否是容器,像NavigableStrings这样的终端节点不是容器,也不包含子节点。

例如,对于以下HTML:

<html>
<ul>
   <li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
   <li>Aliquam tincidunt mauris eu risus.</li>
   <li>Vestibulum auctor dapibus neque.</li>
</ul>
</html>

这个脚本打印出......
[Container Node] ul
[Container Node] li
[Terminal Node] Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
[Container Node] li
[Terminal Node] Aliquam tincidunt mauris eu risus.
[Container Node] li
[Terminal Node] Vestibulum auctor dapibus neque.

1
你的代码存在一些问题:1. BeautifulSoup已经执行了DFT https://dev59.com/nW445IYBdhLWcg3wiayV#4815399 2. 不要使用dir().__dict__使用hasattr()代替。3. x已经具备所需的字符串方法;你不需要使用str(x)(这是多余的,如果x包含非ASCII字符,它将失败) - jfs
我更喜欢这个答案,因为稍加修改就可以跟踪终端节点的父标签,这可能是需要深度优先迭代的原因。BeautifulSoup已经做到了这一点,并不是问题@J.F.Sebastian - bfaskiplar

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