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
source("/home/sillas/R/Projetos/olx/funcoes.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))
## usuário sistema decorrido
## 1.084 0.028 11.953
# 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/apartamento-de-2-quartos-no-rio-centro-417491615 | Apartamento de 2 quartos no Rio Centro | 1500 | Rio de Janeiro, Camorim | 2 quartos | Condomínio: R$ 550 | 1 vaga |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apartamento-no-meier-vilela-tavares-417491090 | Apartamento no Méier- Vilela Tavares | 1600 | Rio de Janeiro, Méier | 2 quartos | 60 m² | Condomínio: R$ 650 |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/vip-aluga-lindo-apartamento-2-quartos-com-armarios-417489866 | (Vip Aluga) Lindo apartamento 2 quartos com armários | 900 | Rio de Janeiro, Campo Grande | 2 quartos | 48 m² | Condomínio: R$ 310 | 1 vaga |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/tijuquinha-kitinete-itanhanga-417487116 | Tijuquinha kitinete Itanhangá | 750 | Rio de Janeiro, Itanhangá | 1 quarto |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apartamento-de-3-quartos-com-dep-no-recreio-417487754 | Apartamento de 3 quartos com dep. no Recreio | 2500 | Rio de Janeiro, Recreio Dos Bandeirantes | 3 quartos | 107 m² | Condomínio: R$ 880 | 1 vaga |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apartamento-concetto-recreio-2-quartos-mobiliado-opcional-consulte-primeira-locacao-417487425 | Apartamento Concetto Recreio, 2 Quartos, Mobiliado (Opcional, Consulte)- Primeira Locação | 1799 | Rio de Janeiro, Recreio Dos Bandeirantes | 2 quartos | 82 m² | Condomínio: R$ 492 | 1 vaga |
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/apartamento-de-2-quartos-no-rio-centro-417491615 | Apartamento de 2 quartos no Rio Centro | 1500 | Rio de Janeiro | Camorim | 2 quartos | Condomínio: R$ 550 | 1 vaga |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apartamento-no-meier-vilela-tavares-417491090 | Apartamento no Méier- Vilela Tavares | 1600 | Rio de Janeiro | Méier | 2 quartos | 60 m² | Condomínio: R$ 650 |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/vip-aluga-lindo-apartamento-2-quartos-com-armarios-417489866 | (Vip Aluga) Lindo apartamento 2 quartos com armários | 900 | Rio de Janeiro | Campo Grande | 2 quartos | 48 m² | Condomínio: R$ 310 | 1 vaga |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/tijuquinha-kitinete-itanhanga-417487116 | Tijuquinha kitinete Itanhangá | 750 | Rio de Janeiro | Itanhangá | 1 quarto |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apartamento-de-3-quartos-com-dep-no-recreio-417487754 | Apartamento de 3 quartos com dep. no Recreio | 2500 | Rio de Janeiro | Recreio Dos Bandeirantes | 3 quartos | 107 m² | Condomínio: R$ 880 | 1 vaga |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apartamento-concetto-recreio-2-quartos-mobiliado-opcional-consulte-primeira-locacao-417487425 | Apartamento Concetto Recreio, 2 Quartos, Mobiliado (Opcional, Consulte)- Primeira Locação | 1799 | Rio de Janeiro | Recreio Dos Bandeirantes | 2 quartos | 82 m² | Condomínio: R$ 492 | 1 vaga |
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.0 87.5 79.2 64.6
Assim, 100% dos apartamentos (dessa amostra de 50 apartamentos) têm informação sobre a quantidade de quartos, 87.5% sobre área, 79.2% informam a taxa de condomínio e 64.6% 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 2 quarto | Condomínio: RS 550 | 1 vaga 550
## 2 2 quarto | 60 m² | Condomínio: RS 650 650
## 3 2 quarto | 48 m² | Condomínio: RS 310 | 1 vaga 310
## 4 1 quarto NA
## 5 3 quarto | 107 m² | Condomínio: RS 880 | 1 vaga 880
## 6 2 quarto | 82 m² | Condomínio: RS 492 | 1 vaga 492
## 7 2 quarto | 51 m² | Condomínio: RS 504 | 1 vaga 504
## 8 1 quarto | 58 m² NA
## 9 2 quarto | 140 m² | Condomínio: RS 135 | 1 vaga 135
## 10 2 quarto | 50 m² | Condomínio: RS 315 | 1 vaga 315
## 11 2 quarto | 72 m² | Condomínio: RS 620 | 1 vaga 620
## 12 2 quarto | 60 m² | Condomínio: RS 1200 1200
## 13 2 quarto | 49 m² | Condomínio: RS 290 | 1 vaga 290
## 14 1 quarto NA
## 15 2 quarto | 76 m² | Condomínio: RS 980 | 1 vaga 980
## 16 3 quarto | 72 m² | Condomínio: RS 490 | 2 vagas 490
## 17 2 quarto | 80 m² | Condomínio: RS 1100 | 1 vaga 1100
## 18 1 quarto | 45 m² | 1 vaga NA
## 19 2 quarto NA
## 20 3 quarto | 1 vaga 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 2 quarto | Condomínio: RS 550 | 1 vaga NA
## 2 2 quarto | 60 m² | Condomínio: RS 650 60
## 3 2 quarto | 48 m² | Condomínio: RS 310 | 1 vaga 48
## 4 1 quarto NA
## 5 3 quarto | 107 m² | Condomínio: RS 880 | 1 vaga 107
## 6 2 quarto | 82 m² | Condomínio: RS 492 | 1 vaga 82
## 7 2 quarto | 51 m² | Condomínio: RS 504 | 1 vaga 51
## 8 1 quarto | 58 m² 58
## 9 2 quarto | 140 m² | Condomínio: RS 135 | 1 vaga 140
## 10 2 quarto | 50 m² | Condomínio: RS 315 | 1 vaga 50
## 11 2 quarto | 72 m² | Condomínio: RS 620 | 1 vaga 72
## 12 2 quarto | 60 m² | Condomínio: RS 1200 60
## 13 2 quarto | 49 m² | Condomínio: RS 290 | 1 vaga 49
## 14 1 quarto NA
## 15 2 quarto | 76 m² | Condomínio: RS 980 | 1 vaga 76
## 16 3 quarto | 72 m² | Condomínio: RS 490 | 2 vagas 72
## 17 2 quarto | 80 m² | Condomínio: RS 1100 | 1 vaga 80
## 18 1 quarto | 45 m² | 1 vaga 45
## 19 2 quarto NA
## 20 3 quarto | 1 vaga 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 2 quarto | Condomínio: RS 550 | 1 vaga 1
## 2 2 quarto | 60 m² | Condomínio: RS 650 NA
## 3 2 quarto | 48 m² | Condomínio: RS 310 | 1 vaga 1
## 4 1 quarto NA
## 5 3 quarto | 107 m² | Condomínio: RS 880 | 1 vaga 1
## 6 2 quarto | 82 m² | Condomínio: RS 492 | 1 vaga 1
## 7 2 quarto | 51 m² | Condomínio: RS 504 | 1 vaga 1
## 8 1 quarto | 58 m² NA
## 9 2 quarto | 140 m² | Condomínio: RS 135 | 1 vaga 1
## 10 2 quarto | 50 m² | Condomínio: RS 315 | 1 vaga 1
## 11 2 quarto | 72 m² | Condomínio: RS 620 | 1 vaga 1
## 12 2 quarto | 60 m² | Condomínio: RS 1200 NA
## 13 2 quarto | 49 m² | Condomínio: RS 290 | 1 vaga 1
## 14 1 quarto NA
## 15 2 quarto | 76 m² | Condomínio: RS 980 | 1 vaga 1
## 16 3 quarto | 72 m² | Condomínio: RS 490 | 2 vagas 2
## 17 2 quarto | 80 m² | Condomínio: RS 1100 | 1 vaga 1
## 18 1 quarto | 45 m² | 1 vaga 1
## 19 2 quarto NA
## 20 3 quarto | 1 vaga 1
# 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/apartamento-de-2-quartos-no-rio-centro-417491615 | Apartamento de 2 quartos no Rio Centro | 1500 | Rio de Janeiro | Camorim | 2 quarto | Condomínio: RS 550 | 1 vaga | TRUE | FALSE | TRUE | TRUE | 2 | 550 | NA | 1 |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apartamento-no-meier-vilela-tavares-417491090 | Apartamento no Méier- Vilela Tavares | 1600 | Rio de Janeiro | Méier | 2 quarto | 60 m² | Condomínio: RS 650 | TRUE | TRUE | TRUE | FALSE | 2 | 650 | 60 | NA |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/vip-aluga-lindo-apartamento-2-quartos-com-armarios-417489866 | (Vip Aluga) Lindo apartamento 2 quartos com armários | 900 | Rio de Janeiro | Campo Grande | 2 quarto | 48 m² | Condomínio: RS 310 | 1 vaga | TRUE | TRUE | TRUE | TRUE | 2 | 310 | 48 | 1 |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/tijuquinha-kitinete-itanhanga-417487116 | Tijuquinha kitinete Itanhangá | 750 | Rio de Janeiro | Itanhangá | 1 quarto | TRUE | FALSE | FALSE | FALSE | 1 | NA | NA | NA |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apartamento-de-3-quartos-com-dep-no-recreio-417487754 | Apartamento de 3 quartos com dep. no Recreio | 2500 | Rio de Janeiro | Recreio Dos Bandeirantes | 3 quarto | 107 m² | Condomínio: RS 880 | 1 vaga | TRUE | TRUE | TRUE | TRUE | 3 | 880 | 107 | 1 |
http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apartamento-concetto-recreio-2-quartos-mobiliado-opcional-consulte-primeira-locacao-417487425 | Apartamento Concetto Recreio, 2 Quartos, Mobiliado (Opcional, Consulte)- Primeira Locação | 1799 | Rio de Janeiro | Recreio Dos Bandeirantes | 2 quarto | 82 m² | Condomínio: RS 492 | 1 vaga | TRUE | TRUE | TRUE | TRUE | 2 | 492 | 82 | 1 |
Conclusão
No próximo post, analisaremos os dados obtidos aqui.