【问题标题】:Scraping with rvest - complete with NAs when tag is not present使用 rvest 抓取 - 当标签不存在时使用 NAs 完成
【发布时间】:2015-10-21 04:14:06
【问题描述】:

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

a) p 标签,带有class: "normal_encontrado"
b) divclass: "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()

【问题讨论】:

标签: r tags web-scraping rvest


【解决方案1】:

使用 XML 包解析带有xmlTreeParse 的输入,然后使用xpathSApplyproduct_pricediv 节点进行交互。对于每个这样的节点,匿名函数获取divp 子节点的值。生成的字符矩阵 m 被重新加工成数据框 DF 并清理列,删除任何不是点或数字的字符,并删除任何后跟非数字的点。将结果转换为数字。请注意,不需要对缺少的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

【讨论】:

    【解决方案2】:

    如果没有找到标签,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 页面。

    【讨论】:

    • 更清晰的方法,谢谢。我也喜欢格罗腾迪克的方法,但是我从来没有用过 XML 包。
    【解决方案3】:

    从你的目标向上一层,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  
    

    我不知道您是否想要修剪琴弦或完成其他任何事情,但这些阴谋很容易。

    【讨论】:

      【解决方案4】:

      这可能不是最惯用的方法,但您可以像这样在 .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 语法来表示lapply,参见例如end of the Functional sequences paragraph here

      最后一个障碍是,如果找不到该元素,这将返回character(0),而不是您想要的NA。因此,我将 ifelse(identical(., character(0)), NA, .)) 添加到 lapply 内的管道以解决该问题。

      【讨论】:

      • 您介意解释一下代码吗?尤其是这部分:lapply(. %&gt;% html_nodes(".normal_encontrado")为什么是“.”那里(在 lapply 之后)?还有:(function(x) ifelse(identical(x, character(0)), NA, x)) )。谢谢。
      • 实际上,我意识到您可以只使用 ifelse(identical(., character(0)), NA, .)) 而不是 (function(x) ...) 语法。我已经开发了代码和解释。这更清楚了吗?
      猜你喜欢
      • 2015-09-08
      • 1970-01-01
      • 1970-01-01
      • 2015-04-05
      • 2018-11-17
      • 2020-07-18
      • 1970-01-01
      • 2023-01-07
      相关资源
      最近更新 更多