Web scraping em R: Dois Por Um
Dois por um é um livrinho de ofertas para a cidade de São Paulo. Inclui, principalmente, restaurantes mas também é possível encontrar spas e até um motel. Como o nome já diz, nos estabelecimentos selecionados você leva dois (pratos, lanches, massagens, etc…) por um.
Eu sempre quis fazer um Webscrapping e escolhi esse site por sem bem simples e eu ter interesse nas informações coletadas. Tem dois objetivos que eu queria atingir com esse programa, sendo eles:
- Criar um mapa com todas as ofertas;
- Cruzar as informações com a base de algum cartão de benefício alimentício (VR, Ticket, Sodexo, Alelo). Isso ficará para o próximo artigo.
Bibliotecas
As bibliotecas a serem utilizadas são:
- httr: utilização na coleta de informações da página web no formato html;
- xml2: manipulação de arquivos xml;
- rvest: auxílio na coleta de informações da página web;
- leaflet: criação de mapas;
- stringr: manipulação de strings;
- htmlwidgets: criação de objetos html;
Scraping
Ao entrar no site https://www.doisporum.net, vemos que todos os estabelecimentos estão representados por quadradinhos e logo ao apertar um deles, aparece uma caixa com mais informações, que também podem ser encontradas em uma página individual de cada uma das ofertas, com a extensão /home/details/<id>. Dessa forma, é necessário obter o id de cada uma para poder acessar essas páginas.
A primeira coisa a se fazer é puxar os dados do site, isso é feito com o método GET que retornará o arquivo html da página requisitada. Após isso, utilizamos a função read_html que converterá para um arquivo XML que facilitará a busca dos objetos.
Utilizamos então a função html_nodes especificando various como a classe dos objetos que estamos buscando, para então selecionar apenas os links especificados pelo atributo href. Isso nos retornará um vetor contendo as urls para a página de cada oferta.
dois_um_url <- "https://www.doisporum.net"
dois_um <- httr::GET(dois_um_url) %>%
httr::content('text') %>%
xml2::read_html() %>%
rvest::html_nodes(".various") %>%
rvest::html_attr("href")
Com base nas urls, é necessário fazer um loop sobre esse vetor afim de acessar cada página e coletar o endereço de cada estabelecimento. Isso é feito novamente com o método GET para acessar a página e convertendo para um arquivo XML.
Então, pela tag h1 sabemos qual o nome do lugar e pela tag p dentro do div de classe refdesc, as informações como: um pequeno resumo, telefone, site e endereço(s). Como para alguns lugares, o endereço se encontrava na última linha e para outros, na penúltima, no dataframe criado para armazenamento das informações, foi mantido tanto a última quanto a penúltima linha. Os dados foram salvos em um arquivo csv.
enderecos <- data.frame("Nome" = numeric(0),
"Endereço" = numeric(0),
stringsAsFactors = F)
for (url in dois_um) {
page <- httr::GET(paste0(dois_um_url, url)) %>%
httr::content('text') %>%
xml2::read_html()nome <- page %>%
html_nodes("h1") %>%
html_text()end <- page %>%
html_nodes(".refdesc p") %>%
html_text()enderecos <- rbind(enderecos,
data.frame("Nome" = nome,
"Endereço" = end[length(end)],
"Endereço 2" = end[length(end) - 1],
stringsAsFactors = F))
}write.csv(enderecos, file = "enderecos.csv", row.names = F)
Mapa
Com base nos endereços obtidos na seção anterior, é necessário fazer uma limpeza de forma a manter apenas os dados relevantes.
Primeiro, juntamos as colunas Endereço e Endereço.2. Como é possível perceber que o telefone do estabelecimento sempre se encontra logo após o endereço, se ele não for encontrado na coluna Endereço, então estará na coluna Endereço.2.
enderecos$Endereço[grep("tel|Tel|(11)|R.", enderecos$`Endereço`, invert = T)] <- enderecos$`Endereço.2`[grep("tel|Tel|(11)|R.", enderecos$`Endereço`, invert = T)]
enderecos$Endereço.2 <- NULL
Após isso, é preciso remover os telefones, e-mails e outras informações irrelevantes que se encontram após o endereço, ou seja, após São Paulo — SP. Com o uso da biblioteca stringr fica fácil a manipulação de strings ainda mais com o uso do pipe (%>%).
ende <- enderecos$Endereço %>%
str_remove_all("(Tel:|Tel\\.:|Telefone:)|[(11)]\\S+\\s+\\d{4}(\\s+|\\S+)\\d{4}") %>%
str_remove_all("w{3}\\.(.*)") %>%
str_remove_all("\\.com") %>%
str_remove_all("\\.net") %>%
str_remove("\\.br") %>%
str_remove_all("(?<=(São Paulo - SP))(.*)|(?<=(São Paulo SP))(.*)")
Muitos estabelecimentos têm mais de um endereço cadastrado e estão separados por uma barra (/), outros não tem nenhuma separação, nesses casos foi inserido para facilitar a divisão. Assim, foi criado um novo data frame com os endereços limpos e, posteriormente, removidos algumas ocorrências que não fazem sentido.
ende[67] <- sub("(56)", "\\1/", ende[67])
ende[67] <- sub("(387)", "\\1/", ende[67])
x <- strsplit(ende, "/")
y <- sapply(x, length)
enderecos <- data.frame("Nome" = rep(enderecos$Nome, y),
"Endereço" = unlist(x),
stringsAsFactors = F)
enderecos <- enderecos[-c(44, 127, 155, 156), ]
Para poder criar o mapa pelo leaflet, é preciso ter as coordenadas de latitude e longitude de cada estabelecimento. Esses dados podem ser adquiridos pela API do site http://www.mapquestapi.com. Você pode se cadastrar gratuitamente e fazer 15,000 requisições por mês sem custo.
Assim, é preciso adequar os endereços para serem inseridos na url da API. Primeiro, os bairros que estão no começo dos endereços e seguidos por dois pontos foram movidos para depois do número do estabelecimento. Depois, foi adicionado a string “Sao Paulo SP” para os lugares que não tinham essa informação. Depois, retirados “-”, “:”, “.” e “,”. E R., Av. e Dr. foram substituídos por Rua, Avenida e Doutor respectivamente.
Posteriormente, foram mantido apenas os dados que começassem com Rua, Alameda, Praça, Largo, Avenida, sendo retiradas as informações sobre andar, piso e CEP (8 números). Alguns casos especiais não estavam funcionando devido ao bairro especificado no endereço como Parque Ibirapuera, Cidade Jardim, Brooklin Novo e Centro, logo esses foram removidos ou substituídos. E por último, os espaços extras foram eliminados.
enderecos$Endereço[grepl(":", enderecos$Endereço)] <- paste(str_remove(enderecos$Endereço[grepl(":", enderecos$Endereço)], ".*:"),
str_extract(enderecos$Endereço[grepl(":", enderecos$Endereço)], ".*:"))
enderecos$Endereço[!grepl("SP", enderecos$Endereço)] <- enderecos$Endereço[!grepl("SP", enderecos$Endereço)] %>%
str_remove("-") %>%
str_c(" Sao Paulo SP")enderecos$Endereço <- enderecos$Endereço %>%
str_remove_all(":") %>%
str_remove_all("-") %>%
str_remove_all(",") %>%
str_replace("R\\. ", "Rua ") %>%
str_replace("Av\\. ", "Avenida ") %>%
str_replace("Dr\\. ", "Doutor ") %>%
str_remove_all("\\.") %>%
str_extract("(Rua).*|(Alameda).*|(Praca).*|(Largo).*|(Avenida).*") %>%
str_remove("\\d+º andar") %>%
str_remove("Piso \\d+") %>%
str_remove("Parque do Ibirapuera ") %>%
str_replace("Brooklin Novo", "Cidade Moncoes") %>%
str_replace("Cidade Jardim", "Morumbi") %>%
str_replace("Centro", "República") %>%
str_replace("VN", "Vila Nova") %>%
str_replace("Min", "Ministro") %>%
str_remove("\\d{8}") %>%
str_squish()
As ocorrências cujos endereços resultaram em NA foram removidos. E para dois casos específicos foi necessário fazer uma manipulação personalizada pois, por algum motivo, o maprequest não estava fornecendo a localização correta.
enderecos <- enderecos[!is.na(enderecos$Endereço), ]
enderecos$Endereço[127] <- str_remove(enderecos$Endereço[127], " \\d{1} .*USP")
enderecos$Endereço[52] <- "Instituto Tomie Ohtake rua corope 88"
Então, foi utilizada a função URLencode para converter as strings em um formato url. E os espaços, codificados como “%20” foram convertidos para “+”.
enderecos$url <- unname(sapply(enderecos$Endereço, URLencode))
enderecos$url <- gsub("%20", "+", enderecos$url)
Podemos então fazer a requisição pela API de cada um dos lugares especificando a key de acesso do maprequest, o endereço e limitando o resultado a apenas um. Os dados de latitude e longitude são então anexados ao data frame principal.
url <- paste0('http://www.mapquestapi.com/geocoding/v1/address?key=', key, '&location=', enderecos$url, "+brasil&maxResults=1")latlong <- lapply(url, function(x) content(GET(x))$results[[1]]$locations[[1]]$latLng)
latlong <- data.frame(matrix(unlist(latlong),
nrow=length(latlong), byrow=T))
names(latlong) <- c("Lat", "Long")
enderecos <- cbind(enderecos, latlong)
Agora, finalmente, podemos criar o mapa no leaflet e salvar como um arquivo html.
map <- leaflet(enderecos) %>% addTiles() %>%
addControl(paste("Dois por Um <br> Quantidade de Ofertas:", nrow(enderecos)), position = "topright") %>%
addAwesomeMarkers(~Long, ~Lat,
popup=~paste0("Loja: ", Nome, "<br>",
"Endereço: ", `Endereço`))
saveWidget(map, "dois-por-um_2.html")
Ao analisar de perto alguns dos pontos marcados, é possível perceber que não estão com a localização exata. Mas numa análise geral nenhum dos locais está muito longe do endereço correto.
Conclusão
O processo inteiro demorou mais do que o esperado devido a principalmente à API do maprequest não ser muito inteligente (pelo menos para endereços brasileiros), sendo necessário vários ajustes para poder fornecer as informações corretas.
Primeiramente, havia sido considerado o uso da biblioteca RSelenium para extrair as informações de latitude e longitude do site https://www.latlong.net/, porém ele permite apenas 25 requisições por dia, sendo inviável nesse caso.
De qualquer modo, por meio desse projeto pude aprender muito sobre regular expressions e manipulação de strings. Além de preparar os dados necessários para a próxima etapa: cruzamento de informações com o banco de dados de VR, Ticket, Alelo ou Sodexo (ainda estou pesquisando).
O código completo pode ser encontrado aqui: https://github.com/gabrielakoreeda/dois-por-um/tree/master