Beautiful Soup tag.contents 项从不同的汤实例中移除

Beautiful Soup tag.contents item removed from different soup instance

不知何故,如果我将 BeautifulSoup 实例 'a' 的内容插入到实例 'b' 中,它会从实例 'a.' 中删除这有意义吗?

a = BeautifulSoup('<p>0</p><p>1</p>')
b = BeautifulSoup('<p>2</p>')
additions = a.body.contents
while additions:
    b.body.insert(0, additions[-1])

'a' 会变成

<html><body></body></html>

'b' 会变成

<html><body><p>0</p><p>1</p><p>2</p></body></html>

除了无限循环,我希望 'a' 保持不变。我只是没有正确阅读文档吗?

如果我在循环之前制作 'additions' 的副本(类似于 not_a_problem = additions[:] ),副本将保持原状——这意味着它的值将是 [<p>0</p>, <p>1</p>]

如果您访问 a.body.contents,您得到的不是字符串列表,而是 BeautifulSoup 的 Tag 对象列表。 对于这些 Tag 对象,BeautifulSoup 使用与 HTML/XML DOM 元素相似的语义。

例如,Tag 对象有一个 parent 属性,它包含当前 HTML (BeautifulSoup) 文档树中该标签的父级。

如果您将标签插入到不同的 BeautifulSoup 文档中,插入它的标签将成为其新的父标签,并且由于它无法保留其旧的父标签,因此将从旧文档中删除。那是因为每个标签都有一个且只有一个父标签。

就像一棵树的任何元素只能是一棵树的一部分,而不是两棵树。否则你最终会遇到这样的情况,其中一个标签有一个子列表,其中一些子标签的父标签与这个标签不同,因为它们已被移动到其他标签。至少这可能令人困惑。因此,当你在不同的地方插入一个标签时,它就会从原来的地方分离。

例如,在您的情况下,ab 最初具有如下树结构:

 a = Tag(html)         b = Tag(html)
       |                       |
    Tag(body)              Tag(body)
     /    \                    |
 Tag(p)   Tag(p)             Tag(p)
   |        |                  |
Str('0') Str('1')           Str('2')

(Str 这里是 BeautifulSoups 的 NavigableString 的 shorthand,它大致对应于 DOM 中的 TextNode)

现在,当您通过 b.body.insert(0, a.body.contents[-1]) 将第二个 p 标签从 a 移动到 b 正文时,b 的结构现在如下所示:

  b = Tag(html)
       |
    Tag(body)
     /    \
 Tag(p)   Tag(p)
   |        |
Str('1') Str('2')

但是,此标签的 parent 现在是 b 的正文,不再是 a 的正文。如果 a.body 的内容中仍然有标签,您将得到一个 无效的 数据结构,例如

   a = Tag(html) Tag(html) = b
       |             |
    Tag(body)    Tag(body)
     /    \      /      \
 Tag(p)    Tag(p)      Tag(p)
   |         |           |
Str('0')  Str('1')    Str('2')

那不行; <p>1</p> 包含在 b 中,如果它仍然在 a.bodycontents 中,那么您会遇到 a.body.contents 中的一个元素的情况有一个 parent 而不是 a.body.contents 本身。

相反,您最终(正如您正确观察到的那样)使用如下数据结构:

 a = Tag(html)           b = Tag(html)
       |                       |
    Tag(body)              Tag(body)
       |                    /     \
     Tag(p)              Tag(p)  Tag(p)
       |                   |       |
    Str('0')            Str('1') Str('2')

据我所知,文档中没有提及,可能是因为文档的作者假设这是 "everybody knows".

如果要在文档树之间复制 Tag 对象,则需要 克隆 它们;那么你可以得到类似的结果:

 a = Tag(html)         b = Tag(html)
       |                       |
    Tag(body)              Tag(body)
     /    \                 /     \
 Tag(p)   Tag(p)         Tag(p)  Tag(p)
   |        |              |       |
Str('0') Str('1')       Str('1') Str('2')

在这种情况下,请查看 clone element with beautifulsoup 如何操作。这并不那么简单,因为您需要对所有相关数据进行深拷贝。