2016-08-19 73 views
1

使用R和XML包,我一直在試圖從有類似這樣的結構HTML文件地址:如何解析一個嵌套結構的html文件?

<!DOCTYPE html> 
    <body> 
    <div class='entry'> 
     <span class='name'>Marcus Smith</span> 
     <span class='town'>New York</span> 
     <span class='phone'>123456789</span> 
    </div> 
    <div class='entry'> 
     <span class='name'>Henry Higgins</span> 
     <span class='town'>London</span> 
    </div> 
    <div class='entry'> 
     <span class='name'>Paul Miller</span> 
     <span class='town'>Boston</span> 
     <span class='phone'>987654321</span> 
    </div> 
    </body> 
</html> 

我第一次做以下

library(XML) 
html <- htmlTreeParse("test.html", useInternalNodes = TRUE) 
root <- xmlRoot(html) 

現在,我可以得到所有這個名字:

xpathSApply(root, "//span[@class='name']", xmlValue) 
## [1] "Marcus Smith" "Henry Higgins" "Paul Miller" 

這個問題現在是一些元素不存在的所有地址。在這個例子中,這就是電話號碼:

xpathSApply(root, "//span[@class='phone']", xmlValue) 
## [1] "123456789" "987654321" 

如果我做這樣的事情,沒有辦法,我的電話號碼分配給合適的人。所以,我想先提取整個地址簿條目如下:

divs <- getNodeSet(root, "//div[@class='entry']") 
divs[[1]] 
## <div class="entry"> 
## <span class="name">Marcus Smith</span> 
## <span class="town">New York</span> 
## <span class="phone">123456789</span> 
## </div> 

從我計算過,我已經達到了我的目標輸出和我可以得到的,例如,對應於第一條目的名稱如下:

xpathSApply(divs[[1]], "//span[@class='name']", xmlValue) 
## [1] "Marcus Smith" "Henry Higgins" "Paul Miller" 

但是,即使的divs[[1]]輸出表現爲Marcus Smith數據而已,我得到的所有三個名字後面。

這是爲什麼?我該怎麼做,以這種方式提取地址數據,我知道name,townphone屬於哪個值?

回答

1

也許xpath表達式有問題,並且「//」總是進入根元素?

此代碼製作的測試數據:

one.entry <- function(x) { 
    name <- getNodeSet(x, "span[@class='name']") 
    phone <- getNodeSet(x, "span[@class='phone']") 
    town <- getNodeSet(x, "span[@class='town']") 

    name <- if(length(name)==1) xmlValue(name[[1]]) else NA 
    phone <- if(length(phone)==1) xmlValue(phone[[1]]) else NA 
    town <- if(length(town)==1) xmlValue(town[[1]]) else NA 

    return(data.frame(name=name, phone=phone, town=town, stringsAsFactors=F)) 
} 

do.call(rbind, lapply(divs, one.entry)) 
+0

非常感謝你。它確實似乎''''去根。這也適用:'xpathSApply(divs [[1]],「span [@ class ='name']」,xmlValue)'。我意識到你可以使用'node'和'/ node'來搜索節點,但不知道'node'也可以。 – Stibu

2

如果你有數目不詳的每個條目的項目,你可以利用類似的組合dplyr::bind_rowsdata.table::rbindlistrvest如下:

require(rvest) 
require(dplyr) 
# Little helper-function to extract all children and set Names 
extract_info <- function(node){ 
    child <- html_children(node) 
    as.list(setNames(child %>% html_text(), child %>% html_attr("class"))) 
} 

doc <- read_html(txt) 
doc %>% html_nodes(".entry") %>% lapply(extract_info) %>% bind_rows 

給您:

  name  town  phone 
      (chr) (chr)  (chr) 
1 Marcus Smith New York 123456789 
2 Henry Higgins London  NA 
3 Paul Miller Boston 987654321 

或者使用rbindlist(fill=TRUE)而不是bind_rows,這導致data.table。或者使用purrr代替使用map_df(as.list)

+0

感謝提到'rvest',我不知道。對於我目前的小項目,我不會重寫所有使用'rvest'的東西,因爲我通過Karsten的回答只改變了代碼的小部分內容,從而實現了我的目標。但下次我會試一試。 – Stibu

2

purrr使得rvest更加有用通過嵌套節點和黑客攻擊的結果列表爲data.frame:

library(rvest) 
library(purrr) 

html %>% read_html() %>% 
    # select all entry divs 
    html_nodes('div.entry') %>% 
    # for each entry div, select all spans, keeping results in a list element 
    map(html_nodes, css = 'span') %>% 
    # for each list element, set the name of the text to the class attribute 
    map(~setNames(html_text(.x), html_attr(.x, 'class'))) %>% 
    # convert named vectors to list elements; convert list to a data.frame 
    map_df(as.list) %>% 
    # convert character vectors to appropriate types 
    dmap(type.convert, as.is = TRUE) 

## # A tibble: 3 x 3 
##   name  town  phone 
##   <chr> <chr>  <int> 
## 1 Marcus Smith New York 123456789 
## 2 Henry Higgins London  NA 
## 3 Paul Miller Boston 987654321 

你可以,當然,更換全部purrr與基礎,但它需要幾更多步驟。

+0

可能使用'purrr :: set_names()'和'setNames()',因爲你已經對'purrr'成語進行了大量的投入。 – hrbrmstr

+0

我從來沒有發現它是非常必要的,因爲'setNames'已經很容易被pipable了。最終我想我堅持'setNames'出於習慣,因爲我並不總是裝載'purrr'。 – alistaire

1

醜陋的基礎R + rvest溶液(但我欺騙和使用管道避免地獄般的嵌套的括號或臨時分配),以顯示++ GD @ alistaire的解決方案如何爲:

library(rvest) 
library(magrittr) 

read_html("<!DOCTYPE html> 
    <body> 
    <div class='entry'> 
     <span class='name'>Marcus Smith</span> 
     <span class='town'>New York</span> 
     <span class='phone'>123456789</span> 
    </div> 
    <div class='entry'> 
     <span class='name'>Henry Higgins</span> 
     <span class='town'>London</span> 
    </div> 
    <div class='entry'> 
     <span class='name'>Paul Miller</span> 
     <span class='town'>Boston</span> 
     <span class='phone'>987654321</span> 
    </div> 
    </body> 
</html>") -> pg 

pg %>% 
    html_nodes('div.entry') %>% 
    lapply(html_nodes, css='span') %>% 
    lapply(function(x) { 
    setNames(html_text(x), html_attr(x, 'class')) %>% 
     as.list() %>% 
     as.data.frame(stringsAsFactors=FALSE) 
    }) %>% 
    lapply(., unlist) %>% 
    lapply("[", unique(unlist(c(sapply(., names))))) %>% 
    do.call(rbind, .) %>% 
    as.data.frame(stringsAsFactors=FALSE)