使用rvest进行刮擦 - 在标记不存在时使用NAs

时间:2015-10-21 04:14:07

标签: r tags web-scraping rvest

我想解析这个HTML:并从中获取这些元素:

a)p代码,class: "normal_encontrado" b)div class: "price"

有时,某些产品中不存在p标记。如果是这种情况,则应将NA添加到从此节点收集文本的向量中。

这个想法是让2个矢量具有相同的长度,并在加入它们之后制作data.frame。有什么想法吗?

HTML部分:

<html>
<head></head>
<body>

<div class="product_price" id="product_price_186251">
  <p class="normal_encontrado">
    S/. 2,799.00
  </p>

  <div id="WC_CatalogEntryDBThumbnailDisplayJSPF_10461_div_10" class="price">
    S/. 2,299.00
  </div>    
</div>

<div class="product_price" id="product_price_232046">
  <div id="WC_CatalogEntryDBThumbnailDisplayJSPF_10461_div_10" class="price">
    S/. 4,999.00
  </div>
</div>
</body>
</html>

R代码:

library(rvest)

page_source <- read_html("r.html")

r.precio.antes <- page_source %>%
html_nodes(".normal_encontrado") %>%
html_text()

r.precio.actual <- page_source %>%
html_nodes(".price") %>%
html_text()

4 个答案:

答案 0 :(得分:4)

使用XML包解析输入xmlTreeParse,然后使用xpathSApply进行product_pricediv节点的交互。对于每个这样的节点,anonyous函数获得divp子节点的值。生成的字符矩阵m将重新处理为数据框DF,并清除列,删除任何不是点或数字的字符,并删除任何点后跟非数字。 Copnvert结果为数字。请注意,不需要对丢失的p案例进行特殊处理。

# input

Lines <- '<html>
<head></head>
<body>

<div class="product_price" id="product_price_186251">
  <p class="normal_encontrado">
    S/. 2,799.00
  </p>

  <div id="WC_CatalogEntryDBThumbnailDisplayJSPF_10461_div_10" class="price">
    S/. 2,299.00
  </div>    
</div>

<div class="product_price" id="product_price_232046">
  <div id="WC_CatalogEntryDBThumbnailDisplayJSPF_10461_div_10" class="price">
    S/. 4,999.00
  </div>
</div>
</body>
</html>'

# code to read input and produce a data.frame

library(XML)
doc <- xmlTreeParse(Lines, asText = TRUE, useInternalNodes = TRUE)

m <- xpathSApply(doc, "//div[@class = 'product_price']", function(node) {
  list(p = xmlValue(node[["p"]]), div = xmlValue(node[["div"]])) })

DF <- as.data.frame(t(m), stringsAsFactors = FALSE) # rework into data frame
DF[] <- lapply(DF, function(x) as.numeric(gsub("[^.0-9]|[.]\\D", "", x))) # clean

结果是:

> DF
     p  div
1 2799 2299
2   NA 4999

答案 1 :(得分:3)

如果找不到标签,则rvest返回一个字符(0)。因此,假设您在每个div.product_price中最多可以找到一个当前价格和一个正常价格,您可以使用:

pacman::p_load("rvest", "dplyr")

get_prices <- function(node){
  r.precio.antes <- html_nodes(node, 'p.normal_encontrado') %>% html_text
  r.precio.actual <- html_nodes(node, 'div.price') %>% html_text

  data.frame(
    precio.antes = ifelse(length(r.precio.antes)==0, NA, r.precio.antes),
    precio.actual = ifelse(length(r.precio.actual)==0, NA, r.precio.actual), 
    stringsAsFactors=F
  )

}

doc <- read_html('test.html') %>% html_nodes("div.product_price")
lapply(doc, get_prices) %>%
  rbind_all

编辑:我误解了输入数据,因此将脚本更改为仅使用一个html页面。

答案 2 :(得分:2)

从目标向上一级,在每个父元素上lapply

library(xml2)
library(rvest)

pg <- read_html('<html>
<head></head>
<body>

<div class="product_price" id="product_price_186251">
  <p class="normal_encontrado">
    S/. 2,799.00
  </p>

  <div id="WC_CatalogEntryDBThumbnailDisplayJSPF_10461_div_10" class="price">
    S/. 2,299.00
  </div>    
</div>

<div class="product_price" id="product_price_232046">
  <div id="WC_CatalogEntryDBThumbnailDisplayJSPF_10461_div_10" class="price">
    S/. 4,999.00
  </div>
</div>
</body>
</html>')

prod <- html_nodes(pg, "div.product_price")
do.call(rbind, lapply(prod, function(x) {
  norm <- tryCatch(xml_text(xml_node(x, "p.normal_encontrado")),
                   error=function(err) {NA})
  price <- tryCatch(xml_text(xml_node(x, "div.price")),
                    error=function(err) {NA})
  data.frame(norm, price, stringsAsFactors=FALSE)
}))

##                     norm                  price
## 1 \n    S/. 2,799.00\n   \n    S/. 2,299.00\n  
## 2                   <NA> \n    S/. 4,999.00\n  

我不知道你是否想要修剪琴弦或其他任何东西,但这些戏弄很容易。

答案 3 :(得分:0)

这可能不是最常用的方法,但您可以在.product_price节点上使用lapply,如下所示:

r.precio.antes <- page_source %>% html_nodes(".product_price") %>%
  lapply(. %>% html_nodes(".normal_encontrado") %>% html_text() %>% 
     ifelse(identical(., character(0)), NA, .)) %>% unlist

每当找不到.normal_encontrado元素时,这将返回NA。

r.precio.antes
# [1] "\n                    S/. 2,799.00\n                "
# [2] NA  

length(r.precio.antes) # 2

如果我想开发代码以使其更清晰,首先我将.product_price节点隔离开来:

product_nodes <- page_source %>% html_nodes(".product_price")

然后我可以用更传统的方式使用lapply

r.precio.antes <- lapply(product_nodes, function(pn) {
  pn %>% html_nodes(".normal_encontrado") %>% html_text()
})
r.precio.antes <- unlist(r.precio.antes)

相反,我使用magrittr的{​​{1}}语法,请参阅例如end of the Functional sequences paragraph here

最后一个障碍是,如果找不到该元素,则会返回lapply而非character(0),如您所愿。所以我将NA添加到lapply内部的管道来修复它。