Scraping with rvest - complete with NAs when tag is not present
I want to parse this HTML: and get this elements from it:
a) p
tag, with class: "normal_encontrado"
.
b) div
with class: "price"
.
Sometimes, the p
tag is not present in some products. If this is the case, an NA
should be added to the vector collecting the text from this nodes.
The idea is to have 2 vectors with the same length, and after join them to make a data.frame
. Any ideas?
The HTML part:
<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 Code:
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()
Using the XML package parse the input with xmlTreeParse
and then use xpathSApply
to interate over the product_price
class div
nodes. For each such node the anonyous function gets the value of the div
and p
subnodes. The resulting character matrix m
is reworked into a data frame DF
and the columns are cleaned removing any character that is not a dot or digit and also removing any dot followed by a non-digit. Copnvert result to numeric. Note that no special processing for the missing p
case is needed.
# 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
The result is:
> DF
p div
1 2799 2299
2 NA 4999
If the tag is not found, rvest returns a character(0). So assuming you will find at most one current and one regular price in each div.product_price, you can use this:
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
Edited: I misunderstood the input data, so changed the script to work with just a single html page.