Encontrando todos os Starbucks do Brasil

Neste post mostro como encontrar todos os Starbucks do Brasil usando webscrapping dentro do R
starbucks
web-scrapping
data-science
brasil
tutorial-R
Author

Vinicius Oike

Published

March 23, 2024

Starbucks

Web scraping

O processo de web scraping consiste em extrair informação de uma página na internet. A dificuldade ou facilidade em extrair esta informação depende da qualidade de construção da página. Em alguns casos mais complexos, a informação pode estar atrás de um captcha ou num painel interativo que depende de outros comandos do usuário.

Neste exemplo simples vou mostrar como conseguir encontrar a localização de todas as unidades da Starbucks no Brasil. A lista completa das lojas em atividade da Starbucks pode ser encontrada no site do Starbucks Brasil. Como de praxe, vamos utilizar o tidyverse em conjunto com os pacotes rvest e xml2.

library(rvest)
library(xml2)
library(tidyverse)

O site

A lista completa das lojas em atividade da Starbucks pode ser encontrada no site do Starbucks Brasil. Para ler a página usa-se read_html.

url = "https://starbucks.com.br/lojas"

page = xml2::read_html(url)

O “xpath” mostra o caminho até um determinado elemento na página. Para encontrar o logo do Starbucks, no canto superior-esquerdo da página, por exemplo podemos usar o código abaixo.

page %>%
  html_element(xpath = "/html/body/div[1]/div[1]/header/nav/div/div[1]/a/img")
{html_node}
<img alt="Starbucks Logo" src="/public/img/icons/starbucks-nav-logo.svg">

Para ver mais sobre xpaths consulte este cheatsheet.

Em geral, em páginas bem construídas, o nome dos elementos será bastante auto-explicativo. No caso acima, o atributo “alt” já indica que é objeto é o logo da starbucks e o “src” direciona para um arquivo em formato svg (imagem) chamado starbucks-nav-logo. Infelizmente, isto nem sempre será o caso. Em algumas páginas os elementos podem ser bastante confusos.

Para puxar um atributo específico usamos a função html_attr.

page %>%
  html_element(
    xpath = "/html/body/div[1]/div[1]/header/nav/div/div[1]/a/img"
    ) %>%
  html_attr("src")
[1] "/public/img/icons/starbucks-nav-logo.svg"

Se você combinar este último link a “www.starbucks.com.br” você deve chegar numa imagem com o logo da empresa1.

Para encontrar a grande lista de lojas no painel da esquerda vamos aproveitar o fato de que o div que guarda esta lista tem uma classe única chamada “place-list”. É fácil verificar isto no próprio navegador. Se você usar o Chrome, por exemplo, basta clicar com o botão direito sobre o painel e clicar em Inspect.

Como comentei acima, nem sempre as coisas estarão bem organizadas. Note que como queremos puxar múltiplos elementos e múltiplos (todos) os atributos usamamos as variantes: html_elements e html_attrs.

list_attr <- page %>%
  html_elements(xpath = '//div[@class="place-list"]/div') %>%
  html_attrs()

O objeto extraído é uma lista onde cada elemento é um vetor de texto que contém as seguintes informações. Temos o nome da loja, a latitude/longitude, e o endereço.

pluck(list_attr, 1)
                  class           data-latitude          data-longitude 
"place-item r-place-15"           "-23.5658059"           "-46.6508012" 
              data-name             data-street              data-index 
  "Shopping Top Center" "Avenida Paulista, 854"                     "0" 

A esta altura, o processo de webscrapping já terminou. Novamente, o processo foi fácil, pois os dados estão muito bem estruturados na página da Starbucks. Agora, precisamos apenas limpar os dados.

Limpeza de dados

Não vou me alongar muito nos detalhes. Basicamente precisamos converter cada elemento da lista em um data.frame, empilhar os resultados e aí converter os tipos de cada coluna.

# Convert os elementos em data.frame
dat <- map(list_attr, \(x) as.data.frame(t(x)))
# Empilha os resultados
dat <- bind_rows(dat)

clean_dat <- dat %>%
  as_tibble() %>%
  # Renomeia as colunas
  rename_with(~str_remove(.x, "data-")) %>%
  rename(lat = latitude, lng = longitude) %>%
  # Seleciona as colunas de interesse
  select(index, name, street, lat, lng) %>%
  # Convert lat/lng para numérico
  mutate(
    lat = as.numeric(lat),
    lng = as.numeric(lng),
    index = as.numeric(index),
    name = str_trim(name)
    )

clean_dat
# A tibble: 142 × 5
   index name                            
   <dbl> <chr>                           
 1     0 Shopping Top Center             
 2     1 Shopping Cidade São Paulo       
 3     2 Paulista Trianon                
 4     3 Paulista 500                    
 5     4 Jardim Pamplona Shopping        
 6     5 Shopping Patio Paulista         
 7     6 Shopping Center 3               
 8     7 Hospital Beneficência Portuguesa
 9     8 Eliseu Guilherme                
10     9 Haddock Lobo                    
   street                                    lat   lng
   <chr>                                   <dbl> <dbl>
 1 Avenida Paulista, 854                   -23.6 -46.7
 2 Avenida Paulista, 1154                  -23.6 -46.7
 3 Avenida Paulista, 1499                  -23.6 -46.7
 4 Avenida Paulista, 500                   -23.6 -46.6
 5 Rua Pamplona, 1704                      -23.6 -46.7
 6 Rua Treze de Maio, 1933                 -23.6 -46.6
 7 Avenida Paulista, 2064                  -23.6 -46.7
 8 Rua Maestro Cardim, 769                 -23.6 -46.6
 9 Rua Desembargador Eliseu Guilherme, 200 -23.6 -46.6
10 Rua Haddock Lobo, 608                   -23.6 -46.7
# ℹ 132 more rows

Mapa

A tabela acima já está em um formato bastante satisfatório. Podemos verificar os dados construindo um mapa simples.

library(sf)
library(leaflet)

starbucks <- st_as_sf(clean_dat, coords = c("lng", "lat"), crs = 4326, remove = FALSE)

leaflet(starbucks) %>%
  addTiles() %>%
  addMarkers(label = ~name) %>%
  addProviderTiles("CartoDB") %>%
  setView(lng = -46.65590, lat = -23.561197, zoom = 12)

Vale notar que dados extraídos a partir de webscraping quase sempre apresentam algum ruído. Neste caso, os dados parecem relativamente limpos após um pouco de limpeza. Os endereços nem sempre são muito instrutivos, como no caso “Rodovia Hélio Smidt, S/N”, mas isto acontece porque muitas unidades estão dentro de hospitais, shoppings ou aeroportos.

Com estes dados já podemos fazer análises interessantes. Podemos descobrir, por exemplo, que há 5 unidades da Starbucks apenas na Avenida Paulista.

starbucks %>%
  filter(str_detect(street, "Avenida Paulista"))
Simple feature collection with 5 features and 5 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -46.65895 ymin: -23.56784 xmax: -46.64809 ymax: -23.55785
Geodetic CRS:  WGS 84
# A tibble: 5 × 6
  index name                      street                   lat   lng
* <dbl> <chr>                     <chr>                  <dbl> <dbl>
1     0 Shopping Top Center       Avenida Paulista, 854  -23.6 -46.7
2     1 Shopping Cidade São Paulo Avenida Paulista, 1154 -23.6 -46.7
3     2 Paulista Trianon          Avenida Paulista, 1499 -23.6 -46.7
4     3 Paulista 500              Avenida Paulista, 500  -23.6 -46.6
5     6 Shopping Center 3         Avenida Paulista, 2064 -23.6 -46.7
               geometry
*           <POINT [°]>
1  (-46.6508 -23.56581)
2  (-46.65438 -23.5631)
3  (-46.6558 -23.56226)
4 (-46.64809 -23.56784)
5 (-46.65895 -23.55785)

Podemos também contar o número de unidades em cada um dos aeroportos. Aparentemente, há 8 unidades no aeroporto de Guarulhos, o que me parece um número muito alto.

starbucks %>%
  st_drop_geometry() %>%
  filter(str_detect(name, "Aeroporto")) %>%
  mutate(
    name_airport = str_remove(name, "de "),
    name_airport = str_extract(name_airport, "(?<=Aeroporto )\\w+"),
    name_airport = if_else(is.na(name_airport), "Confins", name_airport),
    .before = "name"
  ) %>%
  count(name_airport, sort = TRUE)
# A tibble: 9 × 2
  name_airport      n
  <chr>         <int>
1 GRU               8
2 Brasília          3
3 Florianópolis     3
4 Confins           2
5 Galeão            2
6 Viracopos         2
7 Congonhas         1
8 Curitiba          1
9 Santos            1

Por fim, podemos notar que muitas das unidades do Starbucks se localizam dentro de shoppings. Uma conta simples mostra que cerca de 75 unidades estão localizadas dentro de shoppings, perto de 50% das unidades2.

starbucks %>%
  st_drop_geometry() %>%
  filter(str_detect(name, "Shopping|shopping")) %>%
  nrow()
[1] 75

Construindo

A partir destes dados podemos acrescentar mais informação. A partir do geobr podemos identificar em quais cidades as unidades se encontram.

dim_city = geobr::read_municipality(showProgress = FALSE)
dim_city = st_transform(dim_city, crs = 4326)
sf::sf_use_s2(FALSE)

starbucks = starbucks %>%
  st_join(dim_city) %>%
  relocate(c(name_muni, abbrev_state), .before = lat)

Agora podemos ver quais cidades tem mais Starbucks.

starbucks %>%
  st_drop_geometry() %>%
  count(name_muni, abbrev_state, sort = TRUE) 
# A tibble: 43 × 3
   name_muni      abbrev_state     n
   <chr>          <chr>        <int>
 1 São Paulo      SP              45
 2 Rio De Janeiro RJ              11
 3 Guarulhos      SP               9
 4 Curitiba       PR               8
 5 Brasília       DF               6
 6 Campinas       SP               6
 7 Florianópolis  SC               5
 8 Jundiaí        SP               4
 9 Porto Alegre   RS               4
10 Ribeirão Preto SP               3
# ℹ 33 more rows

Ou seja, há mais Starbucks somente na Paulista do que em quase todas as demais cidades do Brasil.

Conclusão

O web scraping é uma técnica de extração de dados bastante popular que nos permite rapidamente construiur bases de dados interessantes. Neste caso, o processo foi bastante simples, mas como comentei, ele pode ser muito complexo. Em posts futuros vou explorar mais esta base de dados e mostrar com juntar estas informações com dados do Google Maps.

Footnotes

  1. https://starbucks.com.br/public/img/icons/starbucks-nav-logo.svg↩︎

  2. Aqui, estamos assumindo que o a tag “name” sempre inclui a palavra shopping se a unidade estiver dentro de um shopping. Eventualmente, este número pode estar subestimado se houver unidades dentro de shoppings que não tem a palavra “shopping” no seu nome. A rigor, também não verificamos se, de fato, a tag shopping sempre está associada a um shopping em atividade.↩︎