Introdução

Imagine-se na seguinte situação: você foi convidado a se mudar para a cidade do Rio de Janeiro a trabalho e precisa procurar um lugar para morar. Pessoas normais resolveriam esse problema pesquisando preços de apartamentos ou quartos para alugar em sites como OLX ou AirBNB. Mas como alguém fascinado em programação e análise resolveria?

Nesta série de posts, mostro como o R pode ser usado tomar a decisão sobre escolher um apartamento ou quarto para alugar. No OLX, a formatação HTML das páginas de apartamentos são diferentes das de quartos. Neste post, eu mostro como fazer o web scraping, por meio do pacote rvest, apenas de apartamentos, mas o mesmo procedimento (com pequenas modificações) pode ser feito também para quartos.

As bibliotecas usadas serão:

library(magrittr) # não vivo sem esse pacote
library(rvest) # principal pacote para web-scraping
library(readr) # usado para extrair numeros de texto
library(stringr) # usado para o data cleaning
library(curl) # usado como suporte para o rvest
library(tidyr) # data cleaning
library(dplyr) # data cleaning
devtools::source_gist(id = "aed28301b7088e47326feac136ceface", filename = "funcoes olx.R") # algumas funcoes que criei para auxiliar o data cleaning

Web scraping

A primeira etapa é obter os dados. Até a data de hoje (12 de Novembro de 2016), o OLX listava um pouco mais de 12000 apartamentos para alugar, com 245 páginas e 50 apartamentos em cada página.

url_apt <- "http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/aluguel/apartamentos"
number_pages <- 245 #hard coded
# Criar vetor com todos os urls para as páginas do olx
lista_urls <- paste0(url_apt, "?o=", 1:number_pages)

A seguir, eu uso uma função para extrair as informações importantes de cada anúncio, que são: o link para o anúncio, o título, o preço, o bairro e mais algumas informações adicionais, como número de vagas de garagem e o valor da taxa de condomínio. Explicar o passo-a-passo do web scraping e explicar como o código fonte das páginas do OLX funciona está fora do escopo deste post, mas acredito que basta ler o código da função extrairAnuncios() para entender o que o script faz. Caso o leitor deseje saber mais sobre web scraping, o post 3 da série traz um tutorial de web scraping bem detalhado.

extrairAnuncios <- function(url_pagina, info_adicional) {
  ### INPUTS:
  # url_pagina: url de uma pagina do olx com uma lista de links de anúncios.
  # info_adicional: variavel booleana. se verdadeiro, faz o scraping de dados adicionais do anuncio
  # ... deve ser usado apenas para apartamentos, pois a sintaxe do html para quartos é diferente
  mycurl <- curl(url_pagina, handle = curl::new_handle("useragent" = "Mozilla/5.0"))
  mycurl <- read_html(mycurl)

  x <- mycurl %>% html_nodes(".OLXad-list-link")
  
  # extrair link do anuncio
  col_links <- mycurl %>% html_nodes(".OLXad-list-link") %>% html_attr("href")
  # extrair titulo do anuncio
  col_titles <- mycurl %>% html_nodes(".OLXad-list-link") %>% html_attr("title")
  # extrair preço
  precos <- lapply(x, . %>% html_nodes(".col-3"))
  precos %<>% lapply(html_text)
  precos %<>% unlist()
  precos %<>% limparString()
  precos %<>% as.numeric()
  col_precos <- precos
  # extrair bairros
  bairros <- mycurl %>% html_nodes(".OLXad-list-line-2") %>% html_text()
  bairros %<>% str_replace_all("[\t]", "")
  bairros %<>% str_replace_all("[\n]", "")
  bairros %<>% str_replace_all("Apartamentos", "")
  bairros %<>% str_replace_all("Aluguel de quartos", "")
  bairros %<>% str_replace_all("Anúncio Profissional", "")
  bairros %<>% str_replace("-", "")
  bairros %<>% str_trim()
  col_bairros <- bairros
  # extrair informações adicionais de apartamento
  
  if (info_adicional) {
    adicional <- mycurl %>% html_nodes(".mt5px") %>% html_text()
    adicional %<>% str_replace_all("[\t]", "")
    adicional %<>% str_replace_all("[\n]", "")
    col_adicionais <- adicional
    
  }
    return(data.frame(link = col_links,
                    titulo = col_titles,
                    preco = col_precos,
                    bairro = col_bairros,
                    adicional = col_adicionais,
                    stringsAsFactors = FALSE))
}

Agora já podemos aplicar a função extrairAnuncios() no vetor da lista de urls para baixar os dados. Para fins de demonstração, vou executar o procedimento apenas para a primeira página.

url_teste <- lista_urls[1]
system.time(df <- extrairAnuncios(url_teste, info_adicional = TRUE))
##    user  system elapsed 
##   0.948   0.064   1.013
# Vamos dar uma olhada nos dados
head(df) %>% knitr::kable()
link titulo preco bairro adicional
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/village-pavuna-270991833 Village pavuna 400 Rio de Janeiro, Pavuna 1 quarto | 50 m² | Condomínio: R$ 250 | 1 vaga
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/excelente-apartamento-todo-reformado-na-freguesia-270535159 Excelente apartamento todo reformado na Freguesia 1500 Rio de Janeiro, Freguesia 1 quarto | 42 m² | Condomínio: R$ 390 | 1 vaga
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/excelente-apartamento-270991747 Excelente Apartamento 2000 Rio de Janeiro, Recreio Dos Bandeirantes 3 quartos | 82 m² | Condomínio: R$ 970 | 2 vagas
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/excelente-apartamento-270990414 Excelente Apartamento 1900 Rio de Janeiro, Recreio Dos Bandeirantes 2 quartos | 80 m² | Condomínio: R$ 300 | 2 vagas
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/barra-da-tijuca-cidade-jardim-empreendimento-reserva-do-parque-com-area-util-de-114-m-270988871 Barra da Tijuca - Cidade Jardim, Empreendimento Reserva do Parque, com área útil de 114 m 3500 Rio de Janeiro, Jacarepaguá 3 quartos | 114 m² | Condomínio: R$ 1300 | 2 vagas
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apartamento-copacabana-3-quartos-270989551 Apartamento Copacabana, 3 quartos 3300 Rio de Janeiro, Copacabana 3 quartos | 98 m² | Condomínio: R$ 979

Data Cleaning

Pode-se ver que o web scraping (ao menos para esses 5 exemplos) foi bem feito pois os os dados foram extraídos adequadamente. Contudo, é evidente a necessidade de se limpar os dados para poder os analisar. A coluna de informações adicionais, por exemplo, informa dados de até quatro variáveis: quantidade de quartos, quantidade de vagas de garagem, área e o preço da taxa de condomínio. Para deixar o processo de limpeza ainda mais difícil, nem todos os anúncios fornecem dados dessas quatro variáveis.

Antes de partir para esse problema, vamos separa a coluna de bairro em duas: uma de cidade e outra de bairro. Removi os imóveis que não são do Rio de Janeiro ou de Niterói para fins de simplicidade.

# remover os que nao sao do RJ ou de niteroi
df %<>% filter(str_detect(bairro, "Niterói") | str_detect(bairro, "Rio de Janeiro"))
df %<>% separate(bairro, c("cidade", "bairro"), sep = ",")
head(df) %>% knitr::kable()
link titulo preco cidade bairro adicional
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/village-pavuna-270991833 Village pavuna 400 Rio de Janeiro Pavuna 1 quarto | 50 m² | Condomínio: R$ 250 | 1 vaga
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/excelente-apartamento-todo-reformado-na-freguesia-270535159 Excelente apartamento todo reformado na Freguesia 1500 Rio de Janeiro Freguesia 1 quarto | 42 m² | Condomínio: R$ 390 | 1 vaga
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/excelente-apartamento-270991747 Excelente Apartamento 2000 Rio de Janeiro Recreio Dos Bandeirantes 3 quartos | 82 m² | Condomínio: R$ 970 | 2 vagas
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/excelente-apartamento-270990414 Excelente Apartamento 1900 Rio de Janeiro Recreio Dos Bandeirantes 2 quartos | 80 m² | Condomínio: R$ 300 | 2 vagas
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/barra-da-tijuca-cidade-jardim-empreendimento-reserva-do-parque-com-area-util-de-114-m-270988871 Barra da Tijuca - Cidade Jardim, Empreendimento Reserva do Parque, com área útil de 114 m 3500 Rio de Janeiro Jacarepaguá 3 quartos | 114 m² | Condomínio: R$ 1300 | 2 vagas
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apartamento-copacabana-3-quartos-270989551 Apartamento Copacabana, 3 quartos 3300 Rio de Janeiro Copacabana 3 quartos | 98 m² | Condomínio: R$ 979

Agora podemos partir para a limpeza da coluna de adicionais. Primeiramente, vamos ver quantos anúncios possuem as quatro variáveis adicionais:

# substituir quartos por quarto
df$adicional %<>% str_replace_all("quartos", "quarto")
df %<>% mutate(
  tem_quarto = str_detect(adicional, "quarto"),
  tem_area = str_detect(adicional, "m²"),
  tem_taxa = str_detect(adicional, "Condomínio"),
  tem_garagem = str_detect(adicional, "vaga")
)

x <- round(apply(df[, 7:10], 2, mean), 3) * 100
print(x)
##  tem_quarto    tem_area    tem_taxa tem_garagem 
##         100          94          94          80

Assim, 100% dos apartamentos (dessa amostra de 50 apartamentos) têm informação sobre a quantidade de quartos, 94% sobre área, 94% informam a taxa de condomínio e 80% têm vaga de garagem.

É necessário usar o pacote stringr para observar a posição dos termos que identificam a variável:

  • O substring “quarto” indica a presença de informação sobre quantidade de quartos;
  • O substring “Condomínio: R$” indica a presença de informação sobre taxa do condomínio;
  • O substring “m²” indica a presença de informação sobre área;
  • O substring “vaga” indica a presença de informação sobre vagas de garagem.

O desafio aqui é criar colunas adicionais para cada uma dessas categorias de informação adicional. A seguir, eu comento linha a linha o procedimento necessário para realizar essa tarefa, que é basicamente o mesmo para as variáveis.

# COLUNA DE QUANTIDADE DE QUARTOS
# Quarto: pegar posicao inicial e final do string quarto
# Localizar trecho dentro do string referente a quartos
matriz_posicao <- str_locate(df$adicional, "quarto")
# Voltar 2 posições no string para pegar o número (ex: 2 quarto)
matriz_posicao[,1] <- matriz_posicao[,1] - 2
# extrair string com posições iniciais e finais
vetor_quartos <- str_sub(df$adicional, matriz_posicao[,1], matriz_posicao[,2])
# extrair apenas número (primeiro caractere do string) e converter para numeric
vetor_quartos <- str_sub(vetor_quartos, 1, 1)
vetor_quartos %<>% as.numeric()
# adicionar ao data frame
df$qtd_quarto <- vetor_quartos


# Condominio
# retirar cifrao pra ficar mais facil
df$adicional %<>% str_replace_all("\\$", "S")
matriz_posicao <- str_locate(df$adicional, "Condomínio: RS ")
# mover cinco posicoes para pegar algarismos após o RS
vetor_taxa <- str_sub(df$adicional, matriz_posicao[, 2], matriz_posicao[, 2] + 4)
# extrair apenas numeros
vetor_taxa %<>% parse_number()
# vendo se funcionou
data.frame(df$adicional, vetor_taxa) %>% head(20)
##                                          df.adicional vetor_taxa
## 1      1 quarto | 50 m² | Condomínio: RS 250 | 1 vaga        250
## 2      1 quarto | 42 m² | Condomínio: RS 390 | 1 vaga        390
## 3     3 quarto | 82 m² | Condomínio: RS 970 | 2 vagas        970
## 4     2 quarto | 80 m² | Condomínio: RS 300 | 2 vagas        300
## 5   3 quarto | 114 m² | Condomínio: RS 1300 | 2 vagas       1300
## 6               3 quarto | 98 m² | Condomínio: RS 979        979
## 7      2 quarto | 70 m² | Condomínio: RS 990 | 1 vaga        990
## 8     1 quarto | 60 m² | Condomínio: RS 1000 | 1 vaga       1000
## 9      2 quarto | 84 m² | Condomínio: RS 923 | 1 vaga        923
## 10     1 quarto | 50 m² | Condomínio: RS 250 | 1 vaga        250
## 11   4 quarto | 290 m² | Condomínio: RS 1000 | 1 vaga       1000
## 12              2 quarto | 60 m² | Condomínio: RS 501        501
## 13                                           1 quarto         NA
## 14                2 quarto | 80 m² | Condomínio: RS 0          0
## 15   2 quarto | 96 m² | Condomínio: RS 1287 | 2 vagas       1287
## 16  3 quarto | 130 m² | Condomínio: RS 1100 | 2 vagas       1100
## 17     2 quarto | 70 m² | Condomínio: RS 700 | 1 vaga        700
## 18                1 quarto | 26 m² | Condomínio: RS 0          0
## 19   3 quarto | 115 m² | Condomínio: RS 653 | 2 vagas        653
## 20                                           2 quarto         NA
# Funcionou! Incorporar vetor ao data frame
df$taxa_condominio <- vetor_taxa


# Área
matriz_posicao <- str_locate(df$adicional, " m²")
# voltar quatro posições
vetor_area <- str_sub(df$adicional, matriz_posicao[,1] - 4, matriz_posicao[, 1])
# converter para numerico
vetor_area %<>% parse_number()
# vendo se funcionou
data.frame(df$adicional, vetor_area) %>% head(20)
##                                          df.adicional vetor_area
## 1      1 quarto | 50 m² | Condomínio: RS 250 | 1 vaga         50
## 2      1 quarto | 42 m² | Condomínio: RS 390 | 1 vaga         42
## 3     3 quarto | 82 m² | Condomínio: RS 970 | 2 vagas         82
## 4     2 quarto | 80 m² | Condomínio: RS 300 | 2 vagas         80
## 5   3 quarto | 114 m² | Condomínio: RS 1300 | 2 vagas        114
## 6               3 quarto | 98 m² | Condomínio: RS 979         98
## 7      2 quarto | 70 m² | Condomínio: RS 990 | 1 vaga         70
## 8     1 quarto | 60 m² | Condomínio: RS 1000 | 1 vaga         60
## 9      2 quarto | 84 m² | Condomínio: RS 923 | 1 vaga         84
## 10     1 quarto | 50 m² | Condomínio: RS 250 | 1 vaga         50
## 11   4 quarto | 290 m² | Condomínio: RS 1000 | 1 vaga        290
## 12              2 quarto | 60 m² | Condomínio: RS 501         60
## 13                                           1 quarto         NA
## 14                2 quarto | 80 m² | Condomínio: RS 0         80
## 15   2 quarto | 96 m² | Condomínio: RS 1287 | 2 vagas         96
## 16  3 quarto | 130 m² | Condomínio: RS 1100 | 2 vagas        130
## 17     2 quarto | 70 m² | Condomínio: RS 700 | 1 vaga         70
## 18                1 quarto | 26 m² | Condomínio: RS 0         26
## 19   3 quarto | 115 m² | Condomínio: RS 653 | 2 vagas        115
## 20                                           2 quarto         NA
# Funcionou! Incorporar ao data frame
df$area_condominio <- vetor_area


# Garagem
matriz_posicao <- str_locate(df$adicional, " vaga")
# voltar quatro posições
vetor_garagem <- str_sub(df$adicional, matriz_posicao[,1] - 2, matriz_posicao[, 1])
# converter para numerico
vetor_garagem %<>% readr::parse_number()
# vendo se funcionou
data.frame(df$adicional, vetor_garagem) %>% head(20)
##                                          df.adicional vetor_garagem
## 1      1 quarto | 50 m² | Condomínio: RS 250 | 1 vaga             1
## 2      1 quarto | 42 m² | Condomínio: RS 390 | 1 vaga             1
## 3     3 quarto | 82 m² | Condomínio: RS 970 | 2 vagas             2
## 4     2 quarto | 80 m² | Condomínio: RS 300 | 2 vagas             2
## 5   3 quarto | 114 m² | Condomínio: RS 1300 | 2 vagas             2
## 6               3 quarto | 98 m² | Condomínio: RS 979            NA
## 7      2 quarto | 70 m² | Condomínio: RS 990 | 1 vaga             1
## 8     1 quarto | 60 m² | Condomínio: RS 1000 | 1 vaga             1
## 9      2 quarto | 84 m² | Condomínio: RS 923 | 1 vaga             1
## 10     1 quarto | 50 m² | Condomínio: RS 250 | 1 vaga             1
## 11   4 quarto | 290 m² | Condomínio: RS 1000 | 1 vaga             1
## 12              2 quarto | 60 m² | Condomínio: RS 501            NA
## 13                                           1 quarto            NA
## 14                2 quarto | 80 m² | Condomínio: RS 0            NA
## 15   2 quarto | 96 m² | Condomínio: RS 1287 | 2 vagas             2
## 16  3 quarto | 130 m² | Condomínio: RS 1100 | 2 vagas             2
## 17     2 quarto | 70 m² | Condomínio: RS 700 | 1 vaga             1
## 18                1 quarto | 26 m² | Condomínio: RS 0            NA
## 19   3 quarto | 115 m² | Condomínio: RS 653 | 2 vagas             2
## 20                                           2 quarto            NA
# Funcionou! Incorporar ao data frame
df$garagem <- vetor_garagem

# Remover objetos desnecessários
rm(matriz_posicao, vetor_adicional, vetor_area, vetor_garagem, vetor_quartos, vetor_taxa)

Vamos ver como ficou o data frame final

head(df) %>% knitr::kable()
link titulo preco cidade bairro adicional tem_quarto tem_area tem_taxa tem_garagem qtd_quarto taxa_condominio area_condominio garagem
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/village-pavuna-270991833 Village pavuna 400 Rio de Janeiro Pavuna 1 quarto | 50 m² | Condomínio: RS 250 | 1 vaga TRUE TRUE TRUE TRUE 1 250 50 1
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/excelente-apartamento-todo-reformado-na-freguesia-270535159 Excelente apartamento todo reformado na Freguesia 1500 Rio de Janeiro Freguesia 1 quarto | 42 m² | Condomínio: RS 390 | 1 vaga TRUE TRUE TRUE TRUE 1 390 42 1
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/excelente-apartamento-270991747 Excelente Apartamento 2000 Rio de Janeiro Recreio Dos Bandeirantes 3 quarto | 82 m² | Condomínio: RS 970 | 2 vagas TRUE TRUE TRUE TRUE 3 970 82 2
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/excelente-apartamento-270990414 Excelente Apartamento 1900 Rio de Janeiro Recreio Dos Bandeirantes 2 quarto | 80 m² | Condomínio: RS 300 | 2 vagas TRUE TRUE TRUE TRUE 2 300 80 2
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/barra-da-tijuca-cidade-jardim-empreendimento-reserva-do-parque-com-area-util-de-114-m-270988871 Barra da Tijuca - Cidade Jardim, Empreendimento Reserva do Parque, com área útil de 114 m 3500 Rio de Janeiro Jacarepaguá 3 quarto | 114 m² | Condomínio: RS 1300 | 2 vagas TRUE TRUE TRUE TRUE 3 1300 114 2
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apartamento-copacabana-3-quartos-270989551 Apartamento Copacabana, 3 quartos 3300 Rio de Janeiro Copacabana 3 quarto | 98 m² | Condomínio: RS 979 TRUE TRUE TRUE FALSE 3 979 98 NA

Conclusão

No próximo post, analisaremos os dados obtidos aqui.