2016-09-01 8 views
11

일부 html 문서에 저장된 데이터를 병렬 처리하여 data.frames (문서의 수백만 개가되므로 병렬 처리의 유용성)에 저장하려고합니다.foreach에 목록 사용하기 R

첫 번째 단계에서는 대기열을 등록하는 컴퓨터에서 html 파일의 하위 집합을 선택하고 read_html 함수에 적용합니다 (rvest 패키지에서 비슷한 함수를 XML에서 시도했습니다. 패키지하지만 많은 메모리 페이지의 내용을 저장하는 고유 한 목록을 얻기 위해 메모리 누수 문제가 발생했습니다.

그런 다음이 목록의 반복기를 사용하여 foreach에 공급할 작은 청크를 얻습니다.

foreach 내부에서 (html_table 함수와 기본 데이터 조작을 사용하여) data.frame (s)을 빌드하고 정리 된 data.frames 인 요소를 가진 목록을 반환합니다.

우분투 16.04에서 doSNOW 백엔드와 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 StackOverflow and many thanks to 
#JanKanis's answer : http://stackoverflow.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() 
} 

오류가 발생합니다 :

*** 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 ... 
+6

첫 번째 질문에 대한 축하하고 stackoverflow에 오신 것을 환영합니다. – Tensibai

+0

나는 네가 너무 영리 해 지려고 노력하고 있다고 생각한다. 나는 클로저를 사용할 이유가 없다. 왜이 "확장 목록"이 필요한가요? 분명히리스트가 얼마나 큰 지 알기 때문에'vector (mode = "list", length = length (tables))를 사용하여 미리 할당 할 수 있습니다. – Roland

+0

안녕하세요, 귀하의 제안에 두 가지 반대 의견이 있습니다.첫 번째는 문제를 명확히하기 위해 모든 페이지 (g-loop)에서 모든 테이블 (i- 루프)의 모든 (최종 행)을 수집하고 모든 페이지의 테이블 수를 "temp_tot" 불명. 나는 이것이 g-loop의 끝에 c()에 의해 (코드를 사용하여) i-loop에서 생성 된리스트에 의해 해결 될 수 있음을 알았다. expand.list()를 선호하는 두 번째 이의 제기는 "c() way"(g- 인덱스에서 상속 된 첫 번째 인덱스, i- 인덱스에서 두 번째 상속받은 인덱스)에서 발생하는 중첩 구조로 인해 발생합니다. expanding.list() 회피 – dgdi

답변

3

나는 문제는 당신이 "read_html"를 호출하여 마스터에서 XML/HTML 문서 객체를 생성하고 처리하고 있다는 생각 그들을 노동자들에게. 몇 가지 실험을 시도했는데 작동하지 않는 것처럼 보입니다. 아마도 이러한 객체를 직렬화하고, 작업자에게 전송 한 다음 올바르게 직렬화 할 수 없기 때문일 수 있습니다. 개체가 손상되어 "html_table"함수를 사용하여 작업하려고 할 때 작업자가 segfault를 일으키는 것으로 생각합니다.

작업자가 "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) 

내가 눈이 기능을 사용하여 직접 문제가 foreach 문 또는 doSNOW에없는 것을 확인 :


는 여기에 내가 실험 테스트 코드의 일부입니다.

+0

안녕하세요, 스티브, 시간 내 주셔서 감사합니다. 귀하의 답변은 실제로 큰 진전을 이루었고 지금은 청소를 시작할 수 있습니다. 접근 가능한리스트 목록 (클러스터에 export 된)과 함께'clusterApply (cl, 1:10, function (i) html_table (read_xml (paste0 ("", ""), fill = T))' data.frames가 반환됩니다 (훌륭합니다!). 나도 너의 doc2, 어쩌면 print xml과 관련된 문제가있어? 그러나 질문에서 나는 ("doRedis"경우에) 다른 컴퓨터가 작업에 참여할 수 있도록 master에서 읽음으로써 페이지 내용을 "캡슐화"하고 있었지만 잘못된 경우 나에게 수정하십시오. 귀하의 전략에서는 불가능합니다. – dgdi

+0

@dgdi 나는 master에서 worker로 read_html 함수를 옮기지 않아 근본적으로 작업 스케줄이 바뀌지는 않았지만 아마도 뭔가 빠졌을 것이라고 생각했습니다. –