将HTML结构转换成网络图

8
我想要做的是将HTML站点DOM(文档对象模型)表示为网络图,然后对此图进行统计计算(如度、介数、接近度、绘图等)。我找不到任何直接执行此操作的库或SO帖子。我的想法是使用BeautifulSoup库,然后使用Networkx库。我尝试编写一些代码来循环遍历HTML结构的每个元素(使用recursive=True),但我不知道如何识别每个唯一的标记(您可以在此处看到,向图中添加第二个h1节点会覆盖第一个节点,对于父项也是如此,因此最终图形完全错误)。
import networkx as nx
import bs4
from bs4 import BeautifulSoup
ex0 = "<html><head><title>Are you lost ?</title></head><body><h1>Lost on the Intenet ?</h1><h1>Don't panic, we will help you</h1><strong><pre>    * <----- you are here</pre></strong></body></html>"
soup = BeautifulSoup(ex0)
G=nx.Graph()
for tag in soup.findAll(recursive=True):
    G.add_node(tag.name)
    G.add_edge(tag.name, tag.findParent().name)
nx.draw(G)   
G.nodes
#### NodeView(('html', '[document]', 'head', 'title', 'body', 'h1', 'strong', 'pre'))

在此输入图片描述

有什么想法可以实现它(包括完全不同的方法)。 谢谢

PS:图表可以是有向的或无向的,我不在意。


你需要循环遍历所有嵌套元素。https://dev59.com/OFoV5IYBdhLWcg3wi_WY - Pedro Lobito
@PedroLobito 谢谢,我已经在做了,但我的问题是关于标签的唯一性甚至标签值的唯一性。 - agenis
你可以使用字典来存储每个标签和唯一键,例如:{'div1',element1,'div2':element2} - Pedro Lobito
是的,那可能很好,但我不知道如何将其实现到我的代码中 :-)。而且Beautifulsoup只接受标准标记,不能使用一些带有标识数字的标记名称... - agenis
我理解,但我也没有时间为您开发定制答案。祝好运! - Pedro Lobito
1个回答

8
你可以循环遍历每个BeautifulSoup对象的content属性。要显示标签,只需在nx.draw中使用with_labels属性即可:
import networkx as nx
import matplotlib.pyplot as plt
from collections import defaultdict
from bs4 import BeautifulSoup as soup
ex0 = "<html><head><title>Are you lost ?</title></head><body><h1>Lost on the Intenet ?</h1><h1>Don't panic, we will help you</h1><strong><pre>    * <----- you are here</pre></strong></body></html>"
d = soup(ex0, 'html.parser')
def _traverse_html(_d:soup, _graph:nx.Graph, _counter, _parent=None) -> None:
  for i in _d.contents:
     if i.name is not None:
       try:
         _name_count = _counter.get(i.name)
         if _parent is not None:
           _graph.add_node(_parent)
           _graph.add_edge(_parent, i.name if not _name_count else f'{i.name}_{_name_count}')
         _counter[i.name] += 1
         _traverse_html(i, _graph, _counter, i.name)
       except AttributeError:
         pass

_full_graph = nx.Graph()
_traverse_html(d, _full_graph, defaultdict(int))
nx.draw(_full_graph, with_labels = True)   
plt.show()

enter image description here


嗨,感谢您提供这么详尽的答案。然而,输出图不应该有环,因为我希望每个元素都是唯一的,这是我的主要问题。例如,在您的第一个图中,应该有第二个节点 h1 连接到 body。我们怎样才能做到这一点呢? - agenis
@agenis 谢谢您的评论。我也注意到 networkx 似乎不会显示重复节点。因此,我为每个 name 元素使用了一个计数器,并在具有相同名称的连续节点上提供了一个“下划线”。 - Ajax1234
谢谢,这正是我需要的。确实,我还需要理解你的语法,它对我来说有点“高级” :-) - agenis
@agenis 每个 bs4 对象都有一个属性 contents,它存储对象(父级)的所有子标签。递归函数循环遍历传递给它的对象的每个子元素,创建节点和边,然后在每个子元素上再次调用自身。但是,如果 bs4.contents 中的元素不是另一个 bs4 对象,则会引发 AttributeError 错误(它是标签之间的文本)。try/except 块捕获后者的错误。 - Ajax1234
谢谢,真聪明。最后一个问题,-> none:是什么意思?我不懂这个语法。 - agenis
@agenis 这是一个“注释”,连同 _d:soup, _graph:nx.Graph。它是一种文档,用于指定所需的参数类型。 - Ajax1234

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