Web Scraping: encontrando la propiedad más barata
Si usted tiene un ahorro y está pensando en comprar una propiedad, sea para vivir o para invertir, ¿dónde busca la información? En Colombia uno de los principales portales inmobiliarios es fincaraiz. En esta entrada, vamos a ver cómo podemos extraer automáticamente los resultados de búsqueda en este portal, y cómo podemos hacer uso de los modelos estadísticos para encontrar las propiedades más “baratas”.
En primer lugar, limpiamos el ambiente de R y cargamos los paquetes necesarios:
rm(list=ls())
library(rvest)
library(dplyr)
library(stringr)
library(purrr)
library(ggplot2)
Ahora vamos a fijar los criterios de búsqueda, buscaremos apartamentos en venta en Chapinero Central. Así que entremos a la página de fincaraiz e ingresamos estos criterios.
Verá que los resultados se encuentran en 5 páginas, entonces vamos a necesitar extraer información de todas ellas de forma automática. Lo primero que vamos a hacer es crear todas estas direcciones url, desde la primera hasta la quinta.
url1 <- "https://www.fincaraiz.com.co/apartamentos/venta/chapinero-central/bogota/?ad=30|"
url2 <- "||||1||8|||67|3630001|||||||||||||||||||1||griddate%20desc|||chapinero+central|||"
url.all <- str_c(url1, 1:5, url2) # Aquí están la dirección de las 5 páginas
Ahora vamos a escribir algunas funciones que permiten extraer informaciones puntuales que queremos de la página. Nos vamos a apoyar en las siguientes funciones para lograr esta extracción:
read_html del paquete xml2. Esta función lee los contenidos de una dirección HTML.
html_nodes del paquete rvest. Selecciona nodos de un documento HTML, (entiéndase que los nodos son los elementos/ partes/ características/ informaciones de la página).
html_text, también del paquete rvest. Permite convertir los resultados de la función html_nodes a text para que los podamos limpiar, convertir a números, etc.
Ilustramos el procedimiento extrayendo la información del precio. Para extraer determinadas informaciones de una página con la función html_nodes, utilizamos los selectores CSS (Cascading Style Sheets). Lo hacemos con Google Chrome, instalando previamente la extensión SelectorGadget (descargar aquí). Después de agregar esta extensión, verás su logo en la esquina superior de tu google chrome, así:
Ahora necesitamos el nombre del nodo que queremos seleccionar que en este caso es el precio, para eso, entramos a la primera página de los resultados, y seleccionamos la extensión SelectorGadget, verás que aparece abajo una ventana pequeña.
Ahora hacemos click sobre el precio de cualquier propiedad, ese precio quedará resaltado en color verde, y muy posiblemente otras áreas quedaron en amarillo, y si hacemos click sobre las partes en amarillo, quedarán en color rojo. Lo que se debe garantizar es que todos los precios de esa página queden en verde o amarillo, y nunca en rojo (el rojo significa que están excluidos), las informaciones que no sean precios pueden quedar en rojo. Así quedó la página:
Una vez verificado que todos los precios estén en verde o amarillo, miramos la ventana de SelectorGadget para copiar el nombre, que en este caso es .li_advert div:nth-child(1). Este nombre lo utilizaremos en la función html_nodes junto con la dirección html.
Ahora sí, estamos listos para escribir la función que extrae la información de precio:
# Extraer el precio
extraer_precio <- function(html){
# extrae el nodo que corresponde al precio
precio <- html_nodes(html, '.li_advert div:nth-child(1)')
# lo convertimos en texto
precio2 <- html_text(precio)
# limpiamos los resultados eliminando caracteres y espacios innecesarios
precio2 <- gsub("\r\n", "", precio2)
precio2 <- gsub(" ", "", precio2)
precio2 <- gsub("\\.", "", precio2)
# expresamos el precio en millones
round(as.numeric(gsub("\\$", "", precio2)) / 1000000)
}
Repetimos el proceso con el área y con el número de habitaciones:
# Extraer el metro cuadrado
extraer_m2 <- function(html){
m2 <- html_nodes(html, '.surface')
m22 <- html_text(m2)
m22 <- gsub("\r\n", "", m22)
m22 <- gsub(" ", "", m22)
m22 <- gsub("\\,", ".", m22)
n <- length(m22)
a <- matrix(NA, n, 2)
for(i in 1:n){
a[i,] <- strsplit(m22, split = "m")[[i]]
}
round(as.numeric(a[,1]))
}
# Extraer el número de habitaciones
extraer_hab <- function(html){
habitacion <- html_nodes(html, '.surface div')
habitacion2 <- html_text(habitacion)
as.numeric(gsub(" hab\\.", "", habitacion2))
}
Ahora, escribimos una función que combine toda la información extraída. Aquí, utilizamos la función read_html para la lectura de la página HTML. El resultado de read_html alimentará las funciones que creamos antes.
# Combinar información de todas las variables
extraer_info <- function(html){
html <- read_html(html)
precio <- extraer_precio(html)
m2 <- extraer_m2(html)
hab <- extraer_hab(html)
tibble(precio = precio,
m2 = m2,
habitacion = hab)
Finalmente, recuerdan que los resultados de búsqueda se encuentran en 5 páginas, entonces necesitamos aplicar la función extraer_info a cada una de estas páginas. Esto lo hacemos con la función map del paquete purrr:
# Extraer información de todos las páginas
extraer_url <- function(url.all){
url.all %>% map(extraer_info) %>% bind_rows()
}
x <- extraer_url(url.all)
x <- distinct(x)
Así, la información de todos los resultados de búsqueda están dentro de x, (al quedar informaciones repetidas, aplicamos la función distinct para eliminar las filas repetidas). En total tenemos información de 123 apartamentos en venta en Chapinero Central (naturalmente a usted le puede aparecer más o menos apartamentos dependiendo el momento de la búsqueda):
Encontrando el apartamento más barato
Una vez tengamos toda la información disponible, podemos intentar encontrar las propiedades más baratas, para eso ajustamos un modelo de regresión lineal, con el objetivo de explicar el precio de los apartamentos en términos del número de habitaciones y el área.
Empiezo con una gráfica de dispersión entre área y precio.
ggplot(x, aes(x=m2, y=precio)) +
geom_point(alpha=0.5) + geom_smooth()
Se puede ver que para apartamentos pequeños, la relación entre el área y el precio es casi lineal, mientras que para los más grandes, la dispersión es mayor y ajustar un buen modelo para todos parece complicado. Así que para simplificar el ejercicio, trabajaremos solo con los apartamentos más pequeños, digamos, con área menor de 60 metros cuadrados. Procedemos a crear la nueva base, y visualizamos la relación entre el área y precio.
x.chico <- x %>% filter(m2 < 60)
ggplot(x.chico, aes(x=m2, y=precio)) + geom_point(alpha=0.5)
Los datos se ven mejores ahora, y ahora sí vamos con el modelo de regresión lineal.
# Ajustar modelo de regresión lineal
m1 <- lm(precio ~ ., data = x.chico)
summary(m1)
El coeficiente de determinación es del 52%, no es para nada alto, pero tampoco se le puede pedir mucho, dado que no hay información sobre las otras características de los apartamentos (garaje, antigüedad, número de baños, etc.).
Paso a seguir: calcular los valores ajustados de precio y comparar con el precio real de venta, los apartamentos donde el precio real sea menor al precio ajustado se pueden considerar como “baratos”.
precio.fitted <- fitted(m1)
x.chico.fitted <- x.chico %>%
mutate(precio.fitted = precio.fitted) %>%
arrange(precio - precio.fitted)
x.chico.fitted
Vemos que la propiedad más “barata” es uno de 30 metros cuadrados, que vale 110 millones, pero el precio según el modelo para un apartamento con estas características debería ser de 213 millones.
Finalmente, presentamos la gráfica de dispersión, resaltando en color rojo los 5 apartamentos más baratos según el modelo.
p <- ggplot(x.chico, aes(x=m2, y=precio)) + geom_point()
p + geom_point(data=x.chico.fitted[1:5,], col=2, size = 3)