【问题标题】:rvest object cloned with rlang::duplicate is not properly cloned使用 rlang::duplicate 克隆的 rvest 对象未正确克隆
【发布时间】:2021-11-01 09:51:18
【问题描述】:

rvest 似乎没有提供任何仅从父对象中提取文本的方法(忽略子对象)。一个workaround 使用xml_remove(),它会改变原始对象——给定R 的默认惰性求值,一直沿内存链向上。

我查看rlang::duplicate(),它被认为是“修改副本使原始对象保持不变”,但克隆似乎并不是真正独立的。例如:

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,移除它的子节点,然后提取文本:

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"

任何建议为什么以及如何解决这个问题?我不想开始重新连接孩子..

【问题讨论】:

  • data.table::copy() 提供相同的结果。

标签: r rvest rlang


【解决方案1】:

另一种可能的解决方案(可能是更通用的方法)是使用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() 可能会中断。

【讨论】:

  • 谢谢。是的,这可能是可能的,但确实感觉很笨拙和不必要的计算负担。仍然赞赏:)
【解决方案2】:

首先让我说,我认为 yoo 可以在不复制数据的情况下解决问题。我不是 xpath 方面的专家,但我认为您可以使用它来仅选择直接文本后代,而忽略嵌套在其他 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"

【讨论】:

  • 这看起来很有希望让我明天测试:)
  • 你成功了!非常感谢 AEF,只要页面允许,我就会奖励赏金。
  • xpath 解决方案不起作用的原因是我的父文本包含我确实想要保留的 标记,所以我需要控制要删除哪些子项,ajd 反序列化方法解决了这个问题.
  • 很高兴我能帮上忙 :) 我很确定无论如何都会有适合您的用例的 xpath 表达式(例如,您可以尝试html_text(html_elements(x, xpath = "text()|a"))),但这取决于您的文档的复杂程度是,创建/查找它可能很麻烦。所以也许复制真的是最简单的选择。
猜你喜欢
  • 2010-10-18
相关资源
最近更新 更多