在 foreach R 中使用列表

Posted

技术标签:

【中文标题】在 foreach R 中使用列表【英文标题】:Using lists in foreach R 【发布时间】:2017-01-09 00:25:13 【问题描述】:

我正在尝试并行提取保存在一些 html 文档中的数据并将其存储到 data.frames(数百万个文档,因此并行化的用处)。

在第一步中,在我注册队列的机器上,我选择了 html 文件的一个子集,并将 read_html 函数应用于它们(来自 rvest 包,我也尝试了来自 XML 包的类似函数但我遇到了内存泄漏问题)来获取一个存储许多 html 页面内容的唯一列表。

然后我在这个列表上使用一个迭代器来获取它的更小块以提供给 foreach。

在 foreach 中,我构建了 data.frame(s)(使用 html_table 函数和一些基本的数据操作)并返回一个列表,其元素是清理后的 data.frames。

我尝试在 win 8 上使用 doSNOW 后端,在 ubuntu 16.04 上使用 doRedis。

在第一种情况下,返回一个空列表列表,而在第二种情况下,抛出内存映射错误;您可以在问题的最底部找到回溯。

据我了解,我发送到核心的(大块)列表的行为与我预期的不同。我已经收集到列表对象可能只是一组指针,但我无法确认它;也许这可能是问题? 有没有替代“列表方式”来“封装”多个html页面的数据?

您可以在下面找到一些重现该问题的代码。 我是堆栈溢出的新手,并行编程的新手和 R 编程的新手:欢迎任何改进建议。 提前谢谢大家。

library(rvest)
library(foreach)

#wikipedia pages of olympic medalist between 1992 and 2016 are
# downloaded for reproducibility
for(i in seq(1992, 2016, by=4))

  html = paste("https://en.wikipedia.org/wiki/List_of_", i, "_Summer_Olympics_medal_winners", sep="")
  con = url(html)
  htmlCode = readLines(con)
  writeLines(htmlCode, con=paste(i, "medalists", sep="_"))
  close(con)



#declaring the redis backend (doSNOW code is also included below)

#note that I am using the package from 
#devtools::install_github("bwlewis/doRedis") due to a "nodelay error"
#(more info on that here: https://github.com/bwlewis/doRedis/issues/24)
# if it is not your case please drop the nodelay and timeout options

#Registering cores ---Ubuntu---
cores=2
library('doRedis')
options('redis:num'=TRUE)
registerDoRedis("jobs", nodelay=FALSE)
startLocalWorkers(n=cores, "jobs", timeout=2, nodelay=FALSE)
foreachOpt <- list(preschedule=FALSE)


#Registering cores ---Win---
#cores=2
#library("doSNOW")
#registerDoSNOW(makeCluster(cores, type = "SOCK"))


#defining the iterator
iterator <- function(x, ...) 
  i <- 1
  it <- idiv(length(x), ...)

  if(exists("chunks"))
    nextEl <- function() 
      n <- nextElem(it)
      ix <- seq(i, length=n)
      i <<- i + n
      x[ix]
    
  else
    nextEl <- function() 
      n <- nextElem(it)
      ix <- seq(i, i+n-1)
      i <<- i + n
      x[ix]
    
  
  obj <- list(nextElem=nextEl)
  class(obj) <- c(
    'ivector', 'abstractiter','iter')
  obj


#reading files
names_files<-list.files()
html_list<-lapply(names_files, read_html)

#creating iterator
ChunkSize_html_list<-2
iter<-iterator(html_list, chunkSize=ChunkSize_html_list)

#defining expanding list (thanks *** and many thanks to
#JanKanis's answer : http://***.com/questions/2436688/append-an-object-to-a-list-in-r-in-amortized-constant-time-o1  )
expanding_list <- function(capacity = 10) 
  buffer <- vector('list', capacity)
  length <- 0

  methods <- list()

  methods$double.size <- function() 
    buffer <<- c(buffer, vector('list', capacity))
    capacity <<- capacity * 2
  

  methods$add <- function(val) 
    if(length == capacity) 
      methods$double.size()
    

    length <<- length + 1
    buffer[[length]] <<- val
  

  methods$as.list <- function() 
    b <- buffer[0:length]
    return(b)
  

  methods


#parallelized part
clean_data<-foreach(ite=iter, .packages=c("itertools", "rvest"), .combine=c,
 .options.multicore=foreachOpt, .options.redis=list(chunkSize=1)) %dopar% 

  temp_tot <- expanding_list()
      for(g in 1:length(ite))

        #extraction of data from tables
      tables <- html_table(ite[[g]], fill=T, header = T)

        for(i in 1:length(tables))

          #just some basic data manipulation
          temp<-lapply(tables, function(d)d[nrow(d),])
          temp_tot$add(temp)
          rm(temp)
          gc(verbose = F)
        
      
  #returning the list of cleaned data.frames to the foreach 
    temp_tot$as.list()

使用redis后端时抛出的错误:

*** caught segfault ***
address 0x60, cause 'memory not mapped'


Traceback:
 1: .Call("xml2_doc_namespaces", PACKAGE = "xml2", doc)
 2: doc_namespaces(doc)
 3: xml_ns.xml_document(x)
 4: xml_ns(x)
 5: xpath_search(x$node, x$doc, xpath = xpath, nsMap = ns, num_results = Inf)
 6: xml_find_all.xml_node(x, ".//table")
 7: xml2::xml_find_all(x, ".//table")
 8: html_table.xml_document(ite[[g]], fill = T, header = T)
 9: html_table(ite[[g]], fill = T, header = T)
10: eval(expr, envir, enclos)
11: eval(.doRedisGlobals$expr, envir = .doRedisGlobals$exportenv)
12: doTryCatch(return(expr), name, parentenv, handler)
13: tryCatchOne(expr, names, parentenv, handlers[[1L]])
14: tryCatchList(expr, classes, parentenv, handlers)
15: tryCatch(    lapply(names(args), function(n) assign(n, args[[n]], pos = .doRedisGlobals$exportenv))    if (exists(".Random.seed", envir = .doRedisGlobals$exportenv))         assign(".Random.seed", .doRedisGlobals$exportenv$.Random.seed,             envir = globalenv())        tryCatch(        if (exists("set.seed.worker", envir = .doRedisGlobals$exportenv))             do.call("set.seed.worker", list(0), envir = .doRedisGlobals$exportenv)    , error = function(e) cat(as.character(e), "\n"))    eval(.doRedisGlobals$expr, envir = .doRedisGlobals$exportenv), error = function(e) e)
16: FUN(X[[i]], ...)
17: lapply(work[[1]]$argsList, .evalWrapper)
18: redisWorker(queue = "jobs", host = "localhost", port = 6379,     iter = Inf, linger = 30, log = stdout(), timeout = 2, nodelay = FALSE)
aborting ...

【问题讨论】:

恭喜您提出第一个问题,欢迎使用 ***。 我认为你在这里太聪明了。我看不出使用闭包的理由。为什么需要这个“扩展列表”?显然,您知道列表需要多大,因此只需使用 vector(mode = "list", length = length(tables)) 预先分配它们。 您好 Roland,我对您的建议有两个反对意见。第一个,只是为了澄清问题,是“temp_tot”从所有页面(g-loop)收集所有(最后一行)所有表(i-loop),每个页面的表数是未知。我看到这可以通过 c() (在 g 循环的末尾)在 i 循环中创建的列表(使用您的代码)来解决。第二个反对意见,导致我更喜欢 expand.list(),是由于“c() 方式”(第一个索引继承自 g-index,第二个索引继承自 i-index)产生的嵌套结构, expand.list() 避免 【参考方案1】:

我认为问题在于您通过调用“read_html”在主服务器上创建 XML/HTML 文档对象,然后在工作人员上处理它们。我已经尝试了一些实验,它看起来不起作用,可能是因为这些对象无法序列化,发送给工作人员,然后正确反序列化。我认为对象已损坏,导致工作人员在尝试使用“html_table”函数对其进行操作时出现段错误。

我建议您修改代码以遍历文件名,以便工作人员可以自己调用“read_html”,从而避免序列化 XML 文档对象。


这是我尝试过的一些测试代码:

library(xml2)
library(snow)
cl <- makeSOCKcluster(3)
clusterEvalQ(cl, library(xml2))

# Create XML documents on the master
docs <- lapply(1:10,
      function(i) read_xml(paste0("<foo>", i, "</foo>")))

# Call xml_path on XML documents created on master
r1 <- lapply(docs, xml_path)            # correct results
r2 <- clusterApply(cl, docs, xml_path)  # incorrect results

# This seems to work...
docs2 <- clusterApply(cl, 1:10,
      function(i) read_xml(paste0("<foo>", i, "</foo>")))

# But this causes a segfault on the master
print(docs2)

我直接使用 snow 函数来验证问题不在 foreach 或 doSNOW 中。

【讨论】:

嗨,史蒂夫,非常感谢您抽出宝贵时间,您的回答确实是一个重大进展,我现在可以开始清理了。使用 clusterApply(cl, 1:10, function(i) html_table(read_xml(paste0("&lt;foo&gt;", i, "&lt;/foo&gt;")), fill=T))(将 rvest 导出到集群)返回一个可访问的 data.frames 列表(太棒了!)。我的 doc2 也有错误,可能是 print xml 的问题?但是,在问题中,我通过在 master 上阅读来“封装”页面内容以允许其他机器加入工作(在“doRedis”的情况下),如果我错了,请纠正我,这对你的策略是不可能的 @dgdi 我认为将 read_html 函数从 master 转移到 workers 并没有从根本上改变工作安排的方式,但也许我错过了一些东西。

以上是关于在 foreach R 中使用列表的主要内容,如果未能解决你的问题,请参考以下文章

foreach (R):抑制从全局环境加载的包中的消息

用foreach循环option下拉列表内容怎么会出现隔行空白的情况

在 foreach 循环中找不到函数

在 r 中使用 foreach 循环返回 NA

在 R 中使用“foreach()”函数时如何创建进度条?

在 foreach 循环中使用 mclapply 出现 R 错误