在BeautifulSoup解析树上进行深度优先遍历

16 投票
2 回答
15557 浏览
提问于 2025-04-16 10:42

有没有办法对BeautifulSoup解析树进行深度优先遍历(DFT)呢?我想做的事情是从根节点开始,通常是html,获取所有的子元素,然后对每个子元素再获取它们的子元素,依此类推,直到到达一个终端节点(也就是没有子元素的节点),然后再从这个节点往回构建树的结构。问题是我找不到可以让我这样做的方法。我发现了findChildren这个方法,但它似乎只是把整个页面的内容放进一个列表里,而且每次放进去的内容都在减少。我可能可以用这个方法来遍历,但除了列表中的最后一个条目外,似乎没有其他方法可以识别哪些条目是终端节点,哪些不是。有没有什么好主意呢?

2 个回答

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.
21

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 被打印了两次。

撰写回答