paquete - web scraping con r
Raspado con rvest-completo con NA cuando la etiqueta no está presente (4)
Puede que no sea la forma más idiomática de hacerlo, pero puede usar lapply sobre los nodos .product_price
como este:
r.precio.antes <- page_source %>% html_nodes(".product_price") %>%
lapply(. %>% html_nodes(".normal_encontrado") %>% html_text() %>%
ifelse(identical(., character(0)), NA, .)) %>% unlist
Esto devolverá NA siempre que no se encuentre el elemento .normal_encontrado
.
r.precio.antes
# [1] "/n S/. 2,799.00/n "
# [2] NA
length(r.precio.antes) # 2
Si quería desarrollar el código para hacerlo más claro, primero .product_price
nodos .product_price
:
product_nodes <- page_source %>% html_nodes(".product_price")
Entonces podría usar lapply
de una manera más tradicional:
r.precio.antes <- lapply(product_nodes, function(pn) {
pn %>% html_nodes(".normal_encontrado") %>% html_text()
})
r.precio.antes <- unlist(r.precio.antes)
En su lugar, estoy usando la sintaxis magrittr
para lapply
, ver, por ejemplo, el final del párrafo de secuencias funcionales aquí .
Un último obstáculo es que si no se encuentra el elemento, este devolverá el character(0)
lugar de NA
como usted quería. Así que estoy agregando ifelse(identical(., character(0)), NA, .))
,. ifelse(identical(., character(0)), NA, .))
la tubería dentro de la aplicación para arreglar eso.
Quiero analizar este HTML: y obtener estos elementos de él:
a) etiqueta p
, con class: "normal_encontrado"
.
b) div
con class: "price"
.
A veces, la etiqueta p
no está presente en algunos productos. Si este es el caso, se debe agregar una NA
al vector que recoge el texto de estos nodos.
La idea es tener 2 vectores con la misma longitud, y luego unirlos para hacer un data.frame
. ¿Algunas ideas?
La parte 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>
Código 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()
Sube un nivel desde tu objetivo y lapply
cada elemento padre:
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
No tengo idea si querías recortar las cuerdas o hacer cualquier otra cosa, pero esas maquinaciones son bastante fáciles.
Usando el paquete XML, xmlTreeParse
la entrada con xmlTreeParse
y luego use xpathSApply
para interar sobre los nodos div
clase product_price
. Para cada nodo, la función anónima obtiene el valor de los subnodos div
y p
. La matriz de caracteres resultante m
se vuelve a trabajar en un marco de datos DF
y las columnas se limpian eliminando cualquier carácter que no sea un punto o dígito y también eliminando cualquier punto seguido de un no dígito. Copnvert resultado a numérico. Tenga en cuenta que no es necesario un procesamiento especial para el caso de p
faltante.
# 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
El resultado es:
> DF
p div
1 2799 2299
2 NA 4999
Si no se encuentra la etiqueta, rvest devuelve un carácter (0). Asumiendo que encontrará como máximo un precio actual y un precio regular en cada div.product_price, puede usar esto:
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
Editado: entendí mal los datos de entrada, así que cambié el guión para trabajar con una sola página html.