使用 rlang::duplicate 克隆的 rvest object 未正确克隆

rvest object cloned with rlang::duplicate is not properly cloned

rvest 似乎没有提供任何方法来仅从 parent object 中提取文本(忽略 children)。一个 使用 xml_remove(),它改变了原始的 object - 给定 R 的默认惰性评估一直沿内存链向上。

我查看 rlang::duplicate(),它应该用于 “修改副本使原始 object 完好无损”,但克隆似乎没有真正独立。例如:

require(rvest)

h = '<ul>
<li id="target">
text to extract
<ul><li>text to ignore</li><li>this too</li></ul>
</li>
</ul>'

doc = xml2::read_html(h)
x = html_node(doc, '#target')

html_text(x)
#> [1] "\ntext to extract\ntext to ignorethis too"

现在克隆 x,删除它的 children,并提取文本:

x2 = rlang::duplicate(x, shallow = FALSE)
children = html_children(x2)
xml2::xml_remove(children)
html_text(x2)
#> [1] "\ntext to extract\n"

按预期工作,但是 x 也发生了变化:

html_text(x)
#> [1] "\ntext to extract\n"

关于为什么以及如何解决这个问题的任何建议?我不想开始 re-attaching children..

另一种可能的解决方案(也许是更通用的方法)是使用html_children()函数获取所有子节点的文本,然后从全文中删除。

require(rvest)

h = '<ul>
<li id="target">
text to extract
<ul><li>text to ignore</li><li>this too</li></ul>
</li>
</ul>'

doc = xml2::read_html(h)
x = html_node(doc, '#target')

fulltext <- html_text(x)
# [1] "\ntext to extract\ntext to ignorethis too"

#find the text in the children nodes
childtext <- html_children(x) %>% html_text()
# "text to ignorethis too"

#replace the child node text with a numm
gsub(childtext, "", fulltext) %>% trimws() 
#"text to extract"


    #alternative using the text from the first child node
    firstchild <- xml_child(x, search=1) %>% xml_text()
    gsub(paste0(firstchild, ".*"), "", fulltext) 

当然,如果有额外的换行符“\n”或格式化字符,gsub()可能会中断。

首先让我说一下,我认为yoo 可以在不复制数据的情况下解决问题。我不是 xpath 方面的专家,但我认为您可以使用它来仅 select 直接文本后代,而忽略嵌套在其他 xml 节点中的文本。 IE。以下似乎没有任何副本就可以解决问题(x 定义为您的问题):

html_text(html_elements(x, xpath = "text()"))
# [1] "\ntext to extract\n"

话虽这么说,我也有一个关于如何制作深拷贝的问题的答案:

问题是rlang::duplicate()只能复制R数据结构。然而,rvest 建立在 xml2 之上,而 xml2 建立在 C 库 libxml2.

之上

当您在 R 中创建 xml_node 对象时,会在 libxml2 中创建相应的数据结构。在 R 端,基本上只有一个指向 libxml2 对象的指针。所以 rlang::duplicate() 只会创建该指针的副本,而不是基础数据的副本。它不能这样做,因为它无法访问它,因为它位于不同的库中(rlang 不知道)。

创建基础数据副本的最简单方法似乎是序列化和反序列化 xml。我怀疑这不是很有效。

示例:

读入原始数据:

require(rvest)

h <- '<ul>
<li id="target">
text to extract
<ul><li>text to ignore</li><li>this too</li></ul>
</li>
</ul>'


doc <- xml2::read_html(h)
x <- html_node(doc, '#target')

创建两个副本 - 一个 rlang:duplicate() 和一个 xml2::xml_unserialize():

x1 <- rlang::duplicate(x, shallow = FALSE)
x2 <- xml2::xml_unserialize(xml2::xml_serialize(x, NULL))

检查 xx1 实际上是相同的,而 x2 是一个真实的副本(你得到的内存位置当然会与这里显示的不同):

x$doc
# <pointer: 0x0000023911334ea0>
x1$doc
# <pointer: 0x0000023911334ea0>  
# --> same as x
x2$doc
# <pointer: 0x00000239113377d0>
# --> different to x

测试一切是否按预期工作:

children <- html_children(x2)
xml2::xml_remove(children)

html_text(x2)
# [1] "\n    text to extract\n    "
html_text(x)
# [1] "\n    text to extract\n    text to ignorethis too"