获取具有任意嵌套度的html元素的内容(以及内容的xpath)

时间:2018-03-28 10:59:35

标签: python html parsing xpath beautifulsoup

我正在寻找一种方法来读取具有任意嵌套度的HTML元素的文本内容(即没有HTML代码)。

如果没有嵌套,那就很容易了,但由于HTML不是常规语言,others with the same problem have been told to use (X)HTML parsers.

有可能用美味的汤做到这一点吗?类似的东西:

page = soup.find('*').getText()  # obviously this won't give xpath info

我可以想象使用生成器将不同的标记名称提供给find函数,但我不知道标记名称是什么。我还需要返回类似于带有文本的元素的xpath引用,以便我知道最终从find函数返回的内容的来源。

因此,对于以下HTML:

<div>
  text of div 1
  <span>
     text of span 1
     <span>
       text of span 2
     </span>
  </span>
</div>

我需要一个函数来返回类似的东西:

('text of div 1', '/div'), ('text of span 1', '/div/span'), ('text of span 2', '/div/span/span')

2 个答案:

答案 0 :(得分:2)

这个怎么样:

result_set = []

for tag in soup.find_all():
    parent_list = []
    content_of_tag = tag.find(text=True)

    parent_list.append(tag.name)

    while tag.parent is not None:
        tag = tag.parent
        parent_list.append(tag.name)

    result_set.append((content_of_tag, parent_list))

第一个find_all()将在所有级别上找到所有类型的所有标记。迭代这些tag.find(text=True)会找到每个标记中的第一个文本。循环前parent_list.append(tag.name)将当前标记名称添加到父列表中。然后while循环查找所有标记父项,并将它们的名称添加到父列表中。

答案 1 :(得分:2)

我编写了一个递归函数,它将返回字典中所有文本的XPATH,格式如下:

{'xpath1': {'text': 'text1'}, 'xpath2': {'text': 'text2'}, ...}

代码:

from bs4 import BeautifulSoup, NavigableString

def get_xpaths_dict(soup, xpaths={}, curr_path=''):
    curr_path += '/{}'.format(soup.name)
    for item in soup.contents:
        if isinstance(item, NavigableString):
            if item.strip():
                try:
                    xpaths[curr_path]['count'] += 1
                    count = xpaths[curr_path]['count']
                    curr_path += '[{}]'.format(count)
                    xpaths[curr_path] = {'text': item.strip()}
                except KeyError:
                    xpaths[curr_path] = {'text': item.strip(), 'count': 1}
        else:
            xpaths = get_xpaths_dict(item, xpaths, curr_path)
    return xpaths

html = '''<div>
  text of div 1
  <span>
     text of span 1.1
     <span>
       text of span 2.1
     </span>
     <span>
       text of span 2.2
       <span>
         text of span 3
       </span>
     </span>
  </span>
</div>'''
soup = BeautifulSoup(html, 'html.parser')

xpaths = get_xpaths_dict(soup.div)
print(xpaths)

输出:

{'/div': {'text': 'text of div 1', 'count': 1}, '/div/span': {'text': 'text of span 1.1', 'count': 1}, '/div/span/span': {'text': 'text of span 2.1', 'count': 2}, '/div/span/span[2]': {'text': 'text of span 2.2'}, '/div/span/span[2]/span': {'text': 'text of span 3', 'count': 1}}

我知道这不是您期望输出的格式。但是,您可以将其转换为您想要的任何格式。例如,要将其转换为预期输出,只需执行以下操作:

expected_output = [(v['text'], k) for k, v in xpaths.items()]
print(expected_output)

输出:

[('text of div 1', '/div'), ('text of span 1.1', '/div/span'), ('text of span 2.1', '/div/span/span'), ('text of span 2.2', '/div/span/span[2]'), ('text of span 3', '/div/span/span[2]/span')]

一些解释:

字典中的额外键count用于存储当前标记中具有相同名称的标记数。使用这种格式(字典)可以优化代码。您只需访问一次标记一次。

<强>加成:

因为,该函数返回一个以XPATH为键的字典,您可以使用XPATH获取任何文本。例如:

xpaths = get_xpaths_dict(soup.div)
print(xpaths['/div/span/span[2]/span']['text'])
# text of span 3