使用mclapply时HTML页面未保留在列表中

时间:2018-09-08 08:54:45

标签: r web-scraping doparallel xml2

仅使用lapply read_html页面结果时,就会保留。

library(xml2)  

lapply(c("https://www.analyticsvidhya.com/blog/2018/06/datahack-radio-1-machine-learning-competitions-with-kaggle-ceo-anthony-goldbloom/","https://www.analyticsvidhya.com/blog/2018/09/datahack-radio-lyft-dr-alok-gupta/"), function(x){read_html(x)})
#> [[1]]
#> {xml_document}
#> <html>
#> [1] <head lang="en-US" prefix="og: http://ogp.me/ns#">\n<meta http-equiv ...
#> [2] <body class="post-template-default single single-post postid-45087 s ...
#> 
#> [[2]]
#> {xml_document}
#> <html>
#> [1] <head lang="en-US" prefix="og: http://ogp.me/ns#">\n<meta http-equiv ...
#> [2] <body class="post-template-default single single-post postid-46725 s ...

使用并行mclapply时:

library(xml2)
library(parallel)  

mclapply(c("https://www.analyticsvidhya.com/blog/2018/06/datahack-radio-1-machine-learning-competitions-with-kaggle-ceo-anthony-goldbloom/","https://www.analyticsvidhya.com/blog/2018/09/datahack-radio-lyft-dr-alok-gupta/"), function(x){read_html(x)}, mc.cores = 2)
#> [[1]]
#> {xml_document}
#> 
#> [[2]]
#> {xml_document}

我不知道为什么会发生这种情况,即使使用foreach我也无法像平常的lapply那样获得理想的结果。救命!

1 个答案:

答案 0 :(得分:1)

缝制时间

(我的意思是,您使用了 thread 一词,所以我不会错过一两个双关语的机会)。

?parallel::mclapply的手册页中,您最终会发现它的工作原理:

  • 分叉过程
  • 序列化结果
  • 最终收集这些序列化结果并将其组合为一个对象

您可以阅读?serialize来查看使用的方法。

为什么我们不能序列化xml_document / html_document对象?

首先,让我们做一个:

library(xml2)

(doc <- read_html("<p>hi there!</p>"))
## {xml_document}
## <html>
## [1] <body><p>hi there!</p></body>

看看str的故事:

str(doc)
## List of 2
##  $ node:<externalptr> 
##  $ doc :<externalptr> 
##  - attr(*, "class")= chr [1:2] "xml_document" "xml_node"

doc$node
## <pointer: 0x7ff45ab17ce0>

嗯。这些是<externalptr>对象。 ?"externalptr-class"(最终)对他们说什么?

…
"externalptr" # raw external pointers for use in C code

由于它不是内置对象,并且数据被隐藏起来并且只能通过package接口访问,因此R不能自己和needs help对其进行序列化。 (该十六进制字符串0x7ff45ab17ce0是指向不透明数据隐藏位置的内存指针)。

“你不能当真……”

完全是

如果您来自密苏里州(“ Show Me”状态),只需尝试将上面的文档保存到RDS文件中,然后就可以看到没有并行操作和原始连接对象序列化复杂性的情况会发生什么情况。读回去:

tf <- tempfile(fileext = ".rds")
saveRDS(doc, tf)

(doc2 <- readRDS(tf))
## List of 2
##  $ node:<externalptr> 
##  $ doc :<externalptr> 
##  - attr(*, "class")= chr [1:2] "xml_document" "xml_node"

现在,您可能都像“ AHA!看,它有效!” Aaaaand…您会

doc2$node
## <pointer: 0x0>

0x0表示没有指向任何内容。您已经丢失了所有数据。它消失了。永远。 (但是,它运行良好,所以我们不要太难过吧)。 他们has been discussed by the xml2 devs和{而不是使我们的生活变得更轻松–而是捣碎了?xml_serialize

等等…有一个xml_serialize,但这还不是那么有用吗?

是的。而且,它甚至变得更更好

希望您的好奇心足以使您继续前进,并找出该相当认真的 xml_serialize()函数的作用。如果不是,则为R,因此要找出答案,只需键入其名称,而无需使用()即可得到:

function (object, connection, ...) 
{
    if (is.character(connection)) {
        connection <- file(connection, "w", raw = TRUE)
        on.exit(close(connection))
    }
    serialize(structure(as.character(object, ...), class = "xml_serialized_document"), 
        connection)
}

此功能xml_serialize后面的复杂魔术除了连接一些连接位以外,还很容易成为 as.character() 。 (实际上令人失望)。

由于并行操作在返回saveRDS()readRDS()(或它们的xml_document兄弟姐妹)时(惯常地)等效于html_document => _node[s]在并行应用中,您最终一无所有。

内容小偷无辜的刮板能做些什么来克服这一毁灭性的限制?

(至少)您有四个选择:

  • 并行扩展功能的复杂性,以将XML / HTML文档处理为数据框,向量或对象列表,所有这些都可以由R自动进行序列化,以便可以为您组合
  • 很酷,有一个并行应用程序,可以将HTML保存到文件中(无论如何,HTTP ops可能都是很慢的事情),然后执行非并行操作,依次读取它们并进行处理-看起来就像您要执行的操作一样反正要做。请注意,如果您无论如何都不执行HTML缓存来归档文件,那么您就很容易成为垃圾邮件用户,因为您显示的是您实际上并不在乎您所内容的带宽和CPU成本。 hit>抢劫抓取。
  • 不要通过^^来变酷,而应使用as.character((read_html(…))直接从并行应用中返回原始的,可序列化的字符HTML,然后将它们重新xml2重新使用您的程序
  • xml2层插入适当的序列化黑客程序中,不要打扰它,因为您可能会花费大量时间试图说服他们值得,但由于此“ externalptr序列化是一件棘手的事情,到处都是危险,您可能会错过一些边缘案例(例如,Hadley / Jim / etc知道他们在做什么,如果被批评了,这是不值得做的事情)。
  

实际上,我不是使用xml2::read_html() + httr::GET()来获取内容,而是使用httr::content(…, as="text") + read_html()(如果您很酷并且缓存页面,而又浪费了其他资源自libxml2起就使用了{{1}},并转换了文档(即使有时只是一点点),与未转换的原始,缓存的源数据相比,最好由认为是比我们聪明。

FIN

除了上面的详细模式起泡之外,我真的无话可说。希望这种扩展还可以帮助其他人了解发生了什么。