如何获取“subsoups”并将它们连接/合并?

8
我有一个需要处理的HTML文档。我正在使用“beautifulsoup”进行处理。现在,我想从该文档中检索几个“子soup”,并将它们合并成一个soup,以便稍后将其用作期望soup对象的函数的参数。
如果不清楚,我可以举个例子...
from bs4 import BeautifulSoup

my_document = """
<html>
<body>

<h1>Some Heading</h1>

<div id="first">
<p>A paragraph.</p>
<a href="another_doc.html">A link</a>
<p>A paragraph.</p>
</div>

<div id="second">
<p>A paragraph.</p>
<p>A paragraph.</p>
</div>

<div id="third">
<p>A paragraph.</p>
<a href="another_doc.html">A link</a>
<a href="yet_another_doc.html">A link</a>
</div>

<p id="loner">A paragraph.</p>

</body>
</html>
"""

soup = BeautifulSoup(my_document)

# find the needed parts
first = soup.find("div", {"id": "first"})
third = soup.find("div", {"id": "third"})
loner = soup.find("p", {"id": "loner"})
subsoups = [first, third, loner]

# create a new (sub)soup
resulting_soup = do_some_magic(subsoups)

# use it in a function that expects a soup object and calls its methods
function_expecting_a_soup(resulting_soup)

目标是在 resulting_soup 中拥有一个对象,它的行为类似于以下内容的 soup:
<div id="first">
<p>A paragraph.</p>
<a href="another_doc.html">A link</a>
<p>A paragraph.</p>
</div>

<div id="third">
<p>A paragraph.</p>
<a href="another_doc.html">A link</a>
<a href="yet_another_doc.html">A link</a>
</div>

<p id="loner">A paragraph.</p>

有没有一种方便的方法可以做到这一点?如果有比find()更好的检索“subsoups”的方法,我可以使用它。谢谢。

更新

Wondercricket提出了一个solution,它将包含找到的标签的字符串连接起来,然后再解析成一个新的BeautifulSoup对象。虽然这是解决问题的一种可能的方法,但重新解析可能需要比我想要的时间更长,特别是当我想要检索大部分内容并且有许多这样的文档需要处理时。 find()返回一个bs4.element.Tag。难道没有一种方法可以将几个Tag连接成一个soup而不将Tag转换为字符串并解析该字符串吗?

2个回答

8
可以完美地实现您所需的内容,而且还能提高性能,因为它只会解析您想要解析的内容 - 而不是整个文档树:

SoupStrainer

from bs4 import BeautifulSoup, SoupStrainer

parse_only = SoupStrainer(id=["first", "third", "loner"])
soup = BeautifulSoup(my_document, "html.parser", parse_only=parse_only)

现在,soup对象将只包含所需的元素:
<div id="first">
 <p>
  A paragraph.
 </p>
 <a href="another_doc.html">
  A link
 </a>
 <p>
  A paragraph.
 </p>
</div>
<div id="third">
 <p>
  A paragraph.
 </p>
 <a href="another_doc.html">
  A link
 </a>
 <a href="yet_another_doc.html">
  A link
 </a>
</div>
<p id="loner">
 A paragraph.
</p>

是否可以指定标签而非仅限于id?比如我想过滤所有class="someclass"的段落,但不包括同样有该class的div标签?

在这种情况下,您可以创建一个搜索函数,将多个条件组合为SoupStrainer

from bs4 import BeautifulSoup, SoupStrainer, ResultSet

my_document = """
<html>
<body>

    <h1>Some Heading</h1>

    <div id="first">
    <p>A paragraph.</p>
    <a href="another_doc.html">A link</a>
    <p>A paragraph.</p>
    </div>

    <div id="second">
    <p>A paragraph.</p>
    <p>A paragraph.</p>
    </div>

    <div id="third">
    <p>A paragraph.</p>
    <a href="another_doc.html">A link</a>
    <a href="yet_another_doc.html">A link</a>
    </div>

    <p id="loner">A paragraph.</p>

    <p class="myclass">test</p>
</body>
</html>
"""

def search(tag, attrs):
    if tag == "p" and "myclass" in attrs.get("class", []):
        return tag

    if attrs.get("id") in ["first", "third", "loner"]:
        return tag


parse_only = SoupStrainer(search)

soup = BeautifulSoup(my_document, "html.parser", parse_only=parse_only)

print(soup.prettify())

太好了,谢谢!除了指定 id 之外,还可以指定标签吗?例如,如果我想过滤所有带有 class="someclass" 的段落,但不包括具有相同类的 div 元素,这也是可能的吗? - geckon

4

您可以使用findAll并传入要使用的元素的ids

import bs4

soup = bs4.BeautifulSoup(my_document)

#EDIT -> I discovered you do not need regex, you can pass in a list of `ids`
sub = soup.findAll(attrs={'id': ['first', 'third', 'loner']})

#EDIT -> adding `html.parser` will force `BeautifulSoup` to not auto append `html` and `body` tags.
sub = bs4.BeautifulSoup('\n\n'.join(str(s) for s in sub), 'html.parser')

print(sub)

>>> <div id="first">
<p>A paragraph.</p>
<a href="another_doc.html">A link</a>
<p>A paragraph.</p>
</div>
<div id="third">
<p>A paragraph.</p>
<a href="another_doc.html">A link</a>
<a href="yet_another_doc.html">A link</a>
</div>
<p id="loner">A paragraph.</p>

但是 sub 是一个字符串对象,不是吗? 它不会像汤一样行为吧? - geckon
我想没有可能在没有额外的<html><body>(...)</body></html>的情况下创建一个soup,对吧?此外,重复解析和创建新的soup对象需要一些时间。难道不能从现有的soup中构建这样的soup吗?也就是说,只需要一次解析? - geckon
@geckon BeautifulSoup 会自动添加 html/body 标签,但有一种方法可以避免这种情况;我会更新我的答案。至于重复解析,我不确定是否有办法避免。findAll 返回一个 ResultSet,它是 list 的子类。为了连接结果,您需要将项目转换为 str,这会丢失 BeautifulSoup 的状态。 - Wondercricket
我想我可以接受“额外”的<html><body>标签。重新解析可能会成为长文档的一个更大问题,特别是当我想检索大多数文档并且有许多这样的文档需要处理时。find()返回一个bs4.element.Tag。难道没有一种方法可以将几个Tag连接成一个soup而不将Tag转换为字符串并解析字符串吗? - geckon
@Wondercricket,我认为使用SoupStrainer更容易实现 - 作为答案发布。看看它,告诉我你的想法。谢谢。 - alecxe

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