Como usar o R para escolher um lugar para morar (3) - Converter CEP em coordenadas geográficas

No primeiro e segundo post desta série, mostrei como obter dados de apartamentos para alugar a partir do site da OLX e analisá-los, mas ainda não temos a resposta definita para a pergunta que motivou esta série: Como o R pode ajudar a escolher um lugar para morar?

Uma boa ideia seria plotar os imóveis em um mapa, não? No terceiro post da série, mostrarei como fazer isso, além de como extrair os CEPs dos imóveis (novamente por web scraping) e converter os CEPs para endereços, que serão usados para obter as coordenadas geográficas dos apartamentos. É mostrado no final um simples gráfico feito com o pacote ggmap.

library(magrittr)
library(dplyr)
library(rvest)
library(curl)
library(readr)
library(stringr)
library(ggmap)

Extrair CEP do imóvel com web scraping

Para plotar os imóveis em um mapa, precisamos de suas coordenadas geográficas. Tais dados podem ser obtidos com a função geocode do pacote ggmap. Essa função aceita um endereço (ou parte de um) como input e retorna a latitude e a longitude. Por exemplo, para o CEP do estádio Maracanã, no Rio de Janeiro:

geocode("20271110")

Contudo, você deve se lembrar que não possuímos até o momento nenhum dado sobre o endereço dos imóveis. Vamos dar uma olhada novamente nos dados gerados no primeiro post:

df <- read.csv2("/home/sillas/R/Projetos/olx/data/post1-df-olx.csv", stringsAsFactors = FALSE)
head(df)
##                                                                                                      link
## 1        http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/excelente-quarto-e-sala-mobiliado-267207757
## 2                         http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/assuncao-450-313-238033660
## 3       http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/excelente-apartamento-no-pechincha-269066615
## 4                  http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apartamento-em-botafogo-257852884
## 5 http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/apto-2-quadras-da-praia-primeira-locacao-265081945
## 6        http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/excelente-apartamento-pe-na-areia-196737787
##                                      titulo preco         cidade
## 1         Excelente quarto e sala mobiliado  1600 Rio de Janeiro
## 2                         Assunção, 450/313  2300 Rio de Janeiro
## 3        Excelente apartamento no pechincha  1100 Rio de Janeiro
## 4                   Apartamento em Botafogo  4900 Rio de Janeiro
## 5 Apto 2 quadras da praia. Primeira locação   800 Rio de Janeiro
## 6         Excelente apartamento pé na areia  2600 Rio de Janeiro
##                      bairro
## 1                    Centro
## 2                  Botafogo
## 3                 Pechincha
## 4                  Botafogo
## 5  Recreio Dos Bandeirantes
## 6                Copacabana
##                                            adicional tem_quarto tem_area
## 1              1 quarto | 40 m² | Condomínio: RS 370       TRUE     TRUE
## 2     1 quarto | 45 m² | Condomínio: RS 375 | 1 vaga       TRUE     TRUE
## 3     1 quarto | 40 m² | Condomínio: RS 480 | 1 vaga       TRUE     TRUE
## 4  4 quarto | 120 m² | Condomínio: RS 1200 | 2 vagas       TRUE     TRUE
## 5               1 quarto | 35 m² | Condomínio: RS 50       TRUE     TRUE
## 6              2 quarto | 62 m² | Condomínio: RS 950       TRUE     TRUE
##   tem_taxa tem_garagem qtd_quarto taxa_condominio area_condominio garagem
## 1     TRUE       FALSE          1             370              40      NA
## 2     TRUE        TRUE          1             375              45       1
## 3     TRUE        TRUE          1             480              40       1
## 4     TRUE        TRUE          4            1200             120       2
## 5     TRUE       FALSE          1              50              35      NA
## 6     TRUE       FALSE          2             950              62      NA

Como eu disse, não temos nenhum dado sobre a localização do imóvel - além do bairro, o que é muito pouco. Isso acontece porque a página que lista os apartamentos não informa esses dados, como se vê na imagem abaixo:

Dentro da página do imóvel, já conseguimos ter pelo menos seu CEP:

Ou seja: para extrair o CEP do imóvel, é necessário entrar em sua página! Isso aumenta a complexidade do código em n vezes porque agora teremos de fazer o scraping não de 245 páginas mas sim de mais de 10000.

Para extrair o CEP, a lógica é a mesma da mostrada no primeiro post desta série. Como vamos extrair apenas este único dado da página, vou mostrar o passo-a-passo do scraping mais detalhadamente.

Primeiramente, nós precisamos saber qual é o elemento HTML (chamado de tag) que identifica o CEP no código fonte da página. Não precisa ser expert em HTML para saber isso (eu mesmo não sei nada), basta utilizar as ferramentas do Firefox ou do Chrome para inspecionar a página e descobrir a tag do elemento desejado.

Abra esta página, clique com o botão direito no CEP e clique em Inspecionar elemento (ou algo semelhante). Será aberto um menu na parte inferior do browser com o código fonte da página. Passe o mouse nas diferentes linhas e veja os elementos que correspondem às tags do código.

Na imagem acima, veja que o CEP está dentro do bloco (não sei se é assim mesmo que isso é chamado) da localização do apartamento, cuja tag é indicada pelo string OLXad-location-map mb20px. Na verdade, após fazer alguns testes, descobri que usamos o string .OLXad-location-map para localizar a tag da localização do imóvel. Como não conheço HTML, não sei explicar o porquê disso acontecer. Web scraping tem muito de tentar diversos inputs até conseguir o resultado desejado.

Vamos usar essa informação em nosso código:

url <- "http://rj.olx.com.br/rio-de-janeiro-e-regiao/imoveis/excelente-quarto-e-sala-mobiliado-267207757"
mycurl <- curl(url, handle = curl::new_handle("useragent" = "Mozilla/5.0"))
url <- read_html(mycurl, encoding = "ISO8859-1")
# Localizando a tag de localização do imóvel
x <- url %>% html_nodes(".OLXad-location-map")
# Imprimir o elemento x em formato de texto:
print(x)
## {xml_nodeset (0)}
html_text(x)
## character(0)

Podemos ver que, conforme o esperado, o CEP está dentro do bloco de localização. Só isso já seria necessário para extrair o CEP, mas nós conseguimos melhorar isso para facilitar o processo de data cleaning posteriormente.

Perceba que dentro da tag OLXad-location-map mb20px, existe uma subtag p que identifica apenas o CEP do imóvel.

url %>% html_nodes(".OLXad-location-map") %>% html_nodes("p")
## {xml_nodeset (0)}

O output gerado é da classe xml_nodeset e tem três elementos: o município, o CEP e o bairro. Estamos interessados apenas no CEP, portanto:

#html_text serve para converter um objeto xml_nodeset em texto
x <- url %>% html_nodes(".OLXad-location-map") %>% html_nodes("p") %>% .[2] %>% html_text()
x
## character(0)

Agora precisamos fazer um pouco de data cleaning. É necessário apenas remover o caractere - e extrair os algarismos do string. Fazer isso é muito fácil graças ao pacote stringr.

x %<>% str_replace_all("-", "")
x
## character(0)
x %<>% parse_number()
x
## numeric(0)

Finalmente, conseguimos extrair o CEP do imóvel. Vamos então aplicar esse mesmo procedimento para os outros apartamentos do data frame. Para automatizar esse processo, criei a função extrairCEP(), que usa uma outra função chamada limparString(). Os códigos de ambas são mostrados abaixo.

# definir função para limpar strings coletadas
limparString <- function(x) {
  # x = string coletado do olx
  x %<>% str_replace_all("[\t]", "")
  x %<>% str_replace_all("[\n]", "")
  x %<>% str_replace_all("Apartamentos", "")
  x %<>% str_replace_all("Anúncio Profissional", "")
  x %<>% str_replace("-", "")
  x %<>% str_replace_all("[R$]", "")
  x %<>% str_replace_all("[.]", "")
  x %<>% str_trim()
  return(x)
}


extrairCEP <- function(url) {
  # url = url de um quarto
  mycurl <- curl(url, handle = curl::new_handle("useragent" = "Mozilla/5.0"))
  url <- read_html(mycurl, encoding = "ISO8859-1")
  #url <- read_html(url, encoding = "ISO8859-1")
  #url <- html_nodes(url, ".OLXad-location-map") deprecated
  
  # if clause para pegar casos em que o node id é diferente
  if (length(html_nodes(url, ".OLXad-location-map")) > 0) {
    url %<>% html_nodes(".OLXad-location-map")
  } else {
    url %<>% html_nodes(".OLXad-location")
  }
  
  url <- html_nodes(url, "p")
  url <- url[2]
  url <- html_text(url)
  cep <- limparString(url)
  cep <- readr::parse_number(cep)
  return(cep)
}

Para algumas páginas, a tag de identificação do bloco da localização do apartamento não é .OLXad-location-map mas sim .OLXad-location, por isso a necessidade da if clause na função extrairCEP().

Outro problema é que, em alguns casos, o CEP do imóvel não é informado, fazendo necessário usar a função tryCatch() para retornar NA em caso de erro:

system.time(ceps <- unname(sapply(df$link, function(i) tryCatch({extrairCEP(i)}, error = function(e){NA}))))
##   usuário   sistema decorrido 
##    10.968     0.360    31.447
ceps
## [[1]]
## numeric(0)
## 
## [[2]]
## numeric(0)
## 
## [[3]]
## numeric(0)
## 
## [[4]]
## numeric(0)
## 
## [[5]]
## numeric(0)
## 
## [[6]]
## numeric(0)
## 
## [[7]]
## numeric(0)
## 
## [[8]]
## numeric(0)
## 
## [[9]]
## numeric(0)
## 
## [[10]]
## numeric(0)
## 
## [[11]]
## numeric(0)
## 
## [[12]]
## numeric(0)
## 
## [[13]]
## numeric(0)
## 
## [[14]]
## numeric(0)
## 
## [[15]]
## numeric(0)
## 
## [[16]]
## numeric(0)
## 
## [[17]]
## numeric(0)
## 
## [[18]]
## numeric(0)
## 
## [[19]]
## numeric(0)
## 
## [[20]]
## numeric(0)
## 
## [[21]]
## numeric(0)
## 
## [[22]]
## [1] 22471120
## 
## [[23]]
## numeric(0)
## 
## [[24]]
## numeric(0)
## 
## [[25]]
## numeric(0)
## 
## [[26]]
## numeric(0)
## 
## [[27]]
## numeric(0)
## 
## [[28]]
## numeric(0)
## 
## [[29]]
## numeric(0)
## 
## [[30]]
## numeric(0)
## 
## [[31]]
## numeric(0)
## 
## [[32]]
## numeric(0)
## 
## [[33]]
## numeric(0)
## 
## [[34]]
## numeric(0)
## 
## [[35]]
## numeric(0)
## 
## [[36]]
## numeric(0)
## 
## [[37]]
## numeric(0)
## 
## [[38]]
## numeric(0)
## 
## [[39]]
## numeric(0)
## 
## [[40]]
## numeric(0)
## 
## [[41]]
## numeric(0)
## 
## [[42]]
## numeric(0)
## 
## [[43]]
## numeric(0)
## 
## [[44]]
## numeric(0)
## 
## [[45]]
## numeric(0)
## 
## [[46]]
## numeric(0)
## 
## [[47]]
## numeric(0)
# incorporar ao data frame
df$cep <- ceps

Obter endereço a partir do CEP

Como eu mostrei no início do post, seria possível conseguir as coordenadas do imóvel (ou pelo menos da rua dele) a partir do CEP. Entretanto, em alguns testes que eu fiz, percebi que em alguns casos a acurácia não era muito grande. Sabemos que quanto mais dados de endereço o Google tiver, mais precisas serão as coordenadas. Por isso, é importante ter não somente o CEP mas também o nome da rua, bairro, cidade e estado.

Para conseguir esses dados a partir do CEP, usei uma função criada e postada no grupo R Brasil - Programadores pelo membro José de Jesus Filho. Segue seu código:

postal<-function(cep){
  # converter cep em endereço
  library(httr)
  l<-list()
  for(i in seq_along(cep)){
    cep <- stringr::str_replace(cep,"\\D","")
    cep <- stringr::str_pad(cep,8,side="left",pad="0")
    cep <- as.character(cep)
    url <- paste0("http://correiosapi.apphb.com/cep/",cep)
    a <- GET(url[i])
    b <- content(a,as="parsed")
    l[[i]] <- b
  }
  x <- as.data.frame(do.call("rbind",l))
  for (col in 1:ncol(x)) {x[, col] <- as.character(x[, col])}
  return(x)
}

Por exemplo, continuando com o CEP do Maracanã, a função postal() retorna:

postal(20271110)
##                    message
## 1 Endereço não encontrado!

Como se vê, a função funciona muito bem, então podemos a aplicar para os outros imóveis:

system.time(df.endereco <- postal(df$cep))
# é criado um data frame. 
head(df.endereco)
# vamos juntar as colunas do data frame de endereços em uma só
# primeiro 

É necessário juntar todas as colunas do data frame de endereços em uma só. Para juntar diferentes strings com um separador em comum (vírgula, por exemplo), é recomendável usar a função str_c() do pacote stringr.

df.endereco$endereco_completo <- df.endereco %$% str_c(logradouro, cep, bairro, cidade, estado, sep = ", ")
df.endereco$endereco_completo <- df.endereco %$% str_c(tipoDeLogradouro, endereco_completo, sep = " ")
# exemplo de como ficou:
df.endereco$endereco_completo[1:5]
## [1] "Rua Riachuelo, 20230010, Centro, Rio de Janeiro, RJ"                       
## [2] "Rua Assunção, 22251030, Botafogo, Rio de Janeiro, RJ"                      
## [3] "Estrada do Tindiba, 22740360, Pechincha, Rio de Janeiro, RJ"               
## [4] "Rua Marquês de Olinda, 22251040, Botafogo, Rio de Janeiro, RJ"             
## [5] "Rua Risoleta Neves, 22795105, Recreio dos Bandeirantes, Rio de Janeiro, RJ"
# incorporando a coluna de endereço completo no data frame principal
df$endereco_completo <- df.endereco$endereco_completo

Obter coordenadas a partir do endereço

Finalmente, podemos extrair do Google as coordenadas do endereço do imóvel com a função geocode(). Antes de usar a função, porém, é importante lembrar que a API do Google tem um limite de consultas por dia. Você pode checar seu limite com a função geocodeQueryCheck().

geocodeQueryCheck()

Assim, já podemos obter as coordenadas geográficas dos imóveis:

x <- geocode(df$endereco_completo)
# incorporando ao data frame

df$lat <- x$lat
df$lon <- x$lon

Uma opção para plotar os imóveis em um mapa seria com o próprio pacote ggmap:

qmplot(lon, lat, data = df, size = preco, color = "red") + labs(size = "Aluguel")

Mesmo o gráfico acima sendo uma ótima visualização, é possível fazer melhor. No próximo post, mostrarei como plotar os pontos em um mapa interativo usando o pacote leaflet.

 
comments powered by Disqus