题 克隆元素与beautifulsoup


我必须将一个文档的一部分复制到另一个文档,但我不想修改我复制的文档。

如果我使用 .extract() 它从树中删除元素。如果我只是追加选中的元素 document2.append(document1.tag) 它仍然从document1中删除元素。

当我使用真实文件时,我可以在修改后不保存document1,但有没有办法在不损坏文档的情况下执行此操作?


14
2018-04-14 10:21


起源


或许考虑使用 extract 方法,但在此之前复制标记的树位置。然后将提取的标签添加回其原始位置(使用您复制的位置信息)并将其添加到新位置。 - dilbert
@dilbert:那不起作用;你还没有副本,所以将元素插入多个位置只需更新 parent 和兄弟参考。深刻的一个 .extract() / deepcopy / reinsert操作也不起作用 NavigableString 对象假设它们是不可变的,因此实现了一个 __copy__ 返回的方法 self 但忽略了他们的事实 parent 和 next_sibling 和 previous_sibling 参考 是 可变的。 - Martijn Pieters♦
@MartijnPieters:在我的黑暗中刺伤。我认为BS比模块更模块化,允许多次删除和重新插入操作。 - dilbert


答案:


在4.4之前的版本(2015年7月发布)中,BeautifulSoup中没有本机克隆功能;你必须自己创建一个深层拷贝,这很棘手,因为每个元素都保持与树的其余部分的链接。

要克隆元素及其所有元素,您必须复制所有属性和 重启 他们的亲子关系;这必须以递归方式发生。最好不要复制关系属性并重新安置每个递归克隆的元素:

from bs4 import Tag, NavigableString

def clone(el):
    if isinstance(el, NavigableString):
        return type(el)(el)

    copy = Tag(None, el.builder, el.name, el.namespace, el.nsprefix)
    # work around bug where there is no builder set
    # https://bugs.launchpad.net/beautifulsoup/+bug/1307471
    copy.attrs = dict(el.attrs)
    for attr in ('can_be_empty_element', 'hidden'):
        setattr(copy, attr, getattr(el, attr))
    for child in el.contents:
        copy.append(clone(child))
    return copy

这种方法对当前的BeautifulSoup版本很敏感;我用4.3测试了这个,未来的版本可能会添加需要复制的属性。

您还可以将此功能monkeypatch到BeautifulSoup:

from bs4 import Tag, NavigableString


def tag_clone(self):
    copy = type(self)(None, self.builder, self.name, self.namespace, 
                      self.nsprefix)
    # work around bug where there is no builder set
    # https://bugs.launchpad.net/beautifulsoup/+bug/1307471
    copy.attrs = dict(self.attrs)
    for attr in ('can_be_empty_element', 'hidden'):
        setattr(copy, attr, getattr(self, attr))
    for child in self.contents:
        copy.append(child.clone())
    return copy


Tag.clone = tag_clone
NavigableString.clone = lambda self: type(self)(self)

让你打电话 .clone() 直接在元素上:

document2.body.append(document1.find('div', id_='someid').clone())

我的 功能要求 到BeautifulSoup项目 被接受和调整 使用 copy.copy() 功能;既然BeautifulSoup 4.4已经发布,你可以使用该版本(或更新版本)并执行:

import copy

document2.body.append(copy.copy(document1.find('div', id_='someid')))

22
2018-04-14 11:16



您还可以创建第二个实例 BeautifulSoup 并将标记移动到第一个。 - Peter Wood
你现在可以使用copy.copy(树)吗? - Overdrivr
@Overdrivr是的,只要你有最新的版本 - Martijn Pieters♦


它可能不是最快的解决方案,但它很短,似乎有效......

clonedtag = BeautifulSoup(str(sourcetag))

这个想法源自Peter Woods上面的评论。


5
2018-01-10 20:43



如果我试试这个,我会得到一个额外的 <html><body>...</body></html> 在标签周围(因为它应该 - BeautifulSoup试图将它变成一个理智的HTML文档,因为它被要求)。需要进一步从该文件中提取埋藏标签的方法: clonedtag = BeautifulSoup(str(sourcetag)).body.contents[0] - Clemens Klein-Robbenhaar
谢谢Clemens,你是对的。自发布以来我发现我的kludge也很慢,表明它必须做大量的工作格式化才能创建字符串然后再将它解析回一个新的BeautifulSoup实例。 - andrew pate