Webscraping no R com rvest

VIII SEMIC - ENCE

Guilherme dos Santos

DME-UFRJ

Introdução

  • Na maioria dos casos, iniciamos a análise de dados a partir de uma tabela. (Planilha do excel, consulta de um banco de dados, etc…)

  • Contudo, em alguns casos, nossos dados podem não estar disponíveis diretamente.

  • Exemplo:

    • Tabela disponível em algum site sem a opção de download.
    • Corpo de texto de uma página de interesse.

O que é web scraping?

  • Nos cenários citados anteriormente, necessitamos de algumas forma para extrair os dados.

  • O conjunto de técnicas para extrair dados da internet ou automatizar procedimentos de estração é chamado de web scraping (ou “raspagem de dados”).

Então vamos raspar!

Duas abordagens

  • Extrair dados do código-fonte de um site
    • Pacote rvest em R
  • APIs da Web: um conjunto de requisições HTTP estruturadas que retornam dados em JSON ou XML
    • Pacote httr para construir requisições de API
    • Pacotes de API: weatherData, vagalumeR, RSpotify
  • Neste minicurso vamos focar na primeira abordagem.

Mas como fazer isso?

  • Precisamos entender algumas coisas
  • Como o site funciona.
  • Como os dados estão dispostos no site.
  • Como podemos extrair as informações que precisamos.
  • Como transformar a informação extraída em dados estruturados.

Sites

  • Ao fazer um site há a possibilidade de utilizar algumas linguagens

  • Em especial, 3 delas são comuns

    • HTML: descreve a infraestrutura de uma página da web;
    • CSS: define a aparência de um site;
    • JavaScript: determina o comportamento da página.
  • HTML e CSS são usados para determinar a estrutura e aparência da página, enquanto javascript é usado para determinar comportamentos de mudança de conteúdo.

HTML

<!DOCTYPE html>
<html lang="en">
<body>

<h1 href="https://en.wikipedia.org/wiki/Carl_Friedrich_Gauss"> Carl Friedrich Gauss</h1>
<h2> Biography </h2>
<p> Johann Carl Friedrich Gauss was born on 30 April 1777 in Brunswick. </p>
<h2> Profession </h2>
<p> Gauss is considered as one of the greatest mathematician, statistician and physicist of all time. </p>

</body>
</html>

Resultado

Carl Friedrich Gauss

Biography

Johann Carl Friedrich Gauss was born on 30 April 1777 in Brunswick.

Profession

Gauss is considered as one of the greatest mathematician, statistician and physicist of all time.

Procedimento de extração

  • Leitura do código HTML da página.
  • Indentificação dos elementos (nós) de interesse.
  • Extração desses elementos.
  • Transformação para uso e análise no R.

Setup

  • Instalar o pacote rvest
install.packages("rvest")
install.packages("dplyr")
  • Instalar um seletor de css que vai nos ajudar a encontrar os pontos da página que precisamos na hora de fazer a extração, o Selector gadget.

rvest

  • Principais funções
    • read_html: Ler o código do site para um objeto no nosso workspace.
    • html_elements: Extrair elementos de acordo com uma tag css ou xpath
    • html_text: Extrair texto do elemento
    • html_table: Extrair tabela do elemento

Operador pipe (%>%)

  • O operador pipe (|> no R base ou %>% no pacote maggritr) é um operador que pega o elemento anterior e utiliza como o primeiro argumento da próxima função.

  • Isto é:

x <- 1:5

x %>% mean()
[1] 3

Exemplo simples

library(rvest)
# Documento html
html <- minimal_html("
  <ul>
    <li><b>C-3PO</b> is a <i>droid</i> that weighs <span class='weight'>167 kg</span></li>
    <li><b>R2-D2</b> is a <i>droid</i> that weighs <span class='weight'>96 kg</span></li>
    <li><b>Yoda</b> weighs <span class='weight'>66 kg</span></li>
    <li><b>R4-P17</b> is a <i>droid</i></li>
  </ul>
  ")

html %>% html_elements("b") %>% html_text2()
[1] "C-3PO"  "R2-D2"  "Yoda"   "R4-P17"
html %>% html_elements("i") %>% html_text2()
[1] "droid" "droid" "droid"
html %>% html_elements(".weight") %>% html_text2()
[1] "167 kg" "96 kg"  "66 kg" 

Exemplo

  • Vamos tentar extrair as informações dos pilotos que venceram corridas na fórmula 1.

  • Podemos obter esta informação do wikipedia.

  • Neste caso, podemos estar interessados em extrair a tabela deste site.

Extração do wikipedia

url <- "https://pt.wikipedia.org/wiki/Lista_de_vencedores_de_corridas_da_F%C3%B3rmula_1"

site <- read_html(url) # Leitura do site

tabela <- site %>% 
  html_elements(".plainrowheaders") %>% # Elementos que quero extrair
  html_table() # Transformando em tabela

head(tabela[[1]])
# A tibble: 6 × 7
    `#` País      Piloto Vitórias Temporadas `Primeira vitória` `Última vitória`
  <int> <chr>     <chr>     <int> <chr>      <chr>              <chr>           
1     1 Reino Un… Lewis…      105 2007–pres… Canadá 2007        Bélgica 2024    
2     2 Alemanha  Micha…       91 1991–2006… Bélgica 1992       China 2006      
3     3 Países B… Max V…       61 2015–pres… Espanha 2016       Espanha 2024    
4     4 Alemanha  Sebas…       53 2007–2022  Itália 2008        Singapura 2019  
5     5 França    Alain…       51 1980–1991… França 1981        Alemanha 1993   
6     6 Brasil    Ayrto…       41 1984–1994  Portugal 1985      Austrália 1993  

Tratamento e análise

library(dplyr)

tabela <- tabela[[1]]

tabela <- janitor::clean_names(tabela)

ranking_paises <- tabela %>% 
  group_by(pais) %>% # Agrupando por pais
  summarise(vitorias = sum(vitorias)) %>% # Número de vitórias por pais
  arrange(desc(vitorias)) %>% # Ordenando de forma decrescente
  top_n(10) # Pegando os 10 primeiros 

Resultado

knitr::kable(ranking_paises)
pais vitorias
Reino Unido 315
Alemanha 179
Brasil 101
França 81
Países Baixos 61
Finlândia 57
Austrália 45
Itália 43
Áustria 41
Argentina 38

Exemplo de extração

  • Vamos tentar extrair as informações do site boxoffice mojo.

  • Este é um site que agrega informação sobre bilheteria de filmes.

  • Suponha que queremos extrair os filmes que arrecadaram mais em 2024.

Exemplo de extração

url <- "https://www.boxofficemojo.com/year/2024/?ref_=bo_lnav_hm_shrt"

site <- read_html(url) # Leitura do site

site %>% 
  html_elements(".mojo-cell-wide .a-link-normal") %>% # Elementos que quero extrair
  html_text() # Transformando em texto
  [1] "Inside Out 2"                                            
  [2] "Deadpool & Wolverine"                                    
  [3] "Despicable Me 4"                                         
  [4] "Beetlejuice Beetlejuice"                                 
  [5] "Dune: Part Two"                                          
  [6] "Twisters"                                                
  [7] "Godzilla x Kong: The New Empire"                         
  [8] "Kung Fu Panda 4"                                         
  [9] "Bad Boys: Ride or Die"                                   
 [10] "Kingdom of the Planet of the Apes"                       
 [11] "It Ends with Us"                                         
 [12] "A Quiet Place: Day One"                                  
 [13] "The Wild Robot"                                          
 [14] "Ghostbusters: Frozen Empire"                             
 [15] "IF"                                                      
 [16] "Alien: Romulus"                                          
 [17] "Bob Marley: One Love"                                    
 [18] "The Fall Guy"                                            
 [19] "The Garfield Movie"                                      
 [20] "Wonka"                                                   
 [21] "Longlegs"                                                
 [22] "Migration"                                               
 [23] "Mean Girls"                                              
 [24] "Civil War"                                               
 [25] "Furiosa: A Mad Max Saga"                                 
 [26] "The Beekeeper"                                           
 [27] "Anyone But You"                                          
 [28] "Venom: The Last Dance"                                   
 [29] "Transformers One"                                        
 [30] "Joker: Folie à Deux"                                     
 [31] "Challengers"                                             
 [32] "Aquaman and the Lost Kingdom"                            
 [33] "Terrifier 3"                                             
 [34] "Argylle"                                                 
 [35] "Smile 2"                                                 
 [36] "Madame Web"                                              
 [37] "Trap"                                                    
 [38] "Speak No Evil"                                           
 [39] "The Strangers: Chapter 1"                                
 [40] "Coraline"                                                
 [41] "Night Swim"                                              
 [42] "The Boys in the Boat"                                    
 [43] "Reagan"                                                  
 [44] "Horizon: An American Saga - Chapter 1"                   
 [45] "The Forge"                                               
 [46] "Imaginary"                                               
 [47] "Abigail"                                                 
 [48] "Monkey Man"                                              
 [49] "Arthur the King"                                         
 [50] "Poor Things"                                             
 [51] "Blink Twice"                                             
 [52] "The Bikeriders"                                          
 [53] "The Ministry of Ungentlemanly Warfare"                   
 [54] "Fly Me to the Moon"                                      
 [55] "Unsung Hero"                                             
 [56] "The First Omen"                                          
 [57] "Cabrini"                                                 
 [58] "American Fiction"                                        
 [59] "Ordinary Angels"                                         
 [60] "The Iron Claw"                                           
 [61] "The Watchers"                                            
 [62] "Tarot"                                                   
 [63] "Demon Slayer: Kimetsu No Yaiba - To the Hashira Training"
 [64] "Harold and the Purple Crayon"                            
 [65] "The Color Purple"                                        
 [66] "Immaculate"                                              
 [67] "Borderlands"                                             
 [68] "MaXXXine"                                                
 [69] "The Substance"                                           
 [70] "The Chosen: S4 Episodes 1-3"                             
 [71] "Star Wars: Episode I - The Phantom Menace"               
 [72] "Kalki 2898 AD"                                           
 [73] "Am I Racist?"                                            
 [74] "We Live in Time"                                         
 [75] "Sound of Hope: The Story of Possum Trot"                 
 [76] "The Boy and the Heron"                                   
 [77] "Godzilla Minus One"                                      
 [78] "The Chosen: S4 Episodes 4-6"                             
 [79] "Never Let Go"                                            
 [80] "Late Night with the Devil"                               
 [81] "Lisa Frankenstein"                                       
 [82] "The Crow"                                                
 [83] "Conclave"                                                
 [84] "Piece by Piece"                                          
 [85] "Saturday Night"                                          
 [86] "Thelma"                                                  
 [87] "Love Lies Bleeding"                                      
 [88] "The Zone of Interest"                                    
 [89] "Spy x Family Code: White"                                
 [90] "Ferrari"                                                 
 [91] "The Chosen: S4 Episodes 7-8"                             
 [92] "Megalopolis"                                             
 [93] "Sight"                                                   
 [94] "Haikyu!! The Dumpster Battle"                            
 [95] "Afraid"                                                  
 [96] "The Hunger Games: The Ballad of Songbirds & Snakes"      
 [97] "I.S.S."                                                  
 [98] "Cuckoo"                                                  
 [99] "Back to Black"                                           
[100] "The Book of Clarence"                                    
[101] "Columbia 100th Anniversary Series"                       
[102] "The Nightmare Before Christmas"                          
[103] "Someone Like You"                                        
[104] "One Life"                                                
[105] "Devara Part 1"                                           
[106] "Trolls Band Together"                                    
[107] "The Killer's Game"                                       
[108] "My Old Ass"                                              
[109] "Kinds of Kindness"                                       
[110] "Drive-Away Dolls"                                        
[111] "I Saw the TV Glow"                                       
[112] "My Hero Academia: You're Next"                           
[113] "Wicked Little Letters"                                   
[114] "Dìdi"                                                    
[115] "Origin"                                                  
[116] "Land of Bad"                                             
[117] "The Exorcism"                                            
[118] "White Bird"                                              
[119] "In a Violent Nature"                                     
[120] "The Apprentice"                                          
[121] "Babes"                                                   
[122] "Perfect Days"                                            
[123] "Oppenheimer"                                             
[124] "All of Us Strangers"                                     
[125] "Shrek 2"                                                 
[126] "Wish"                                                    
[127] "God's Not Dead: In God We Trust"                         
[128] "The Lord of the Rings: The Fellowship of the Ring"       
[129] "2024 Oscar Nominated Short Films: Documentary"           
[130] "The Front Room"                                          
[131] "Strange Darling"                                         
[132] "The Long Game"                                           
[133] "Jesus Thirsts: The Miracle of the Eucharist"             
[134] "1992"                                                    
[135] "The Fabulous Four"                                       
[136] "My Penguin Friend"                                       
[137] "The Taste of Things"                                     
[138] "GHOST: Rite Here Rite Now"                               
[139] "Ezra"                                                    
[140] "Sight & Sound Presents: Daniel LIVE"                     
[141] "Boy Kills World"                                         
[142] "Escape from Germany"                                     
[143] "Jung Kook: I Am Still"                                   
[144] "The Lord of the Rings: The Two Towers"                   
[145] "The Blue Angels"                                         
[146] "Problemista"                                             
[147] "The American Society of Magical Negroes"                 
[148] "The Lord of the Rings: The Return of the King"           
[149] "Summer Camp"                                             
[150] "Sing Sing"                                               
[151] "Howl's Moving Castle"                                    
[152] "Alien"                                                   
[153] "Exhuma"                                                  
[154] "In the Land of Saints and Sinners"                       
[155] "The Lion King"                                           
[156] "Stree 2"                                                 
[157] "The Holdovers"                                           
[158] "Average Joe"                                             
[159] "The Ark and the Darkness"                                
[160] "Between the Temples"                                     
[161] "Yolo"                                                    
[162] "Queen Rock Montreal"                                     
[163] "Anora"                                                   
[164] "SUGA: Agust D Tour 'D-DAY' the Movie"                    
[165] "Out of Darkness"                                         
[166] "Lee"                                                     
[167] "Look Back"                                               
[168] "Tillu Square"                                            
[169] "Blue Lock: Episode Nagi"                                 
[170] "The Firing Squad"                                        
[171] "Sam and Colby: The Legends of the Paranormal"            
[172] "City of Dreams"                                          
[173] "Hocus Pocus"                                             
[174] "The Mummy"                                               
[175] "Jatt & Juliet 3"                                         
[176] "Crew"                                                    
[177] "Turning Red"                                             
[178] "You Gotta Believe"                                       
[179] "Vindicating Trump"                                       
[180] "Something to Stand for with Mike Rowe"                   
[181] "Anatomy of a Fall"                                       
[182] "Luca"                                                    
[183] "Neon Genesis Evangelion: The End of Evangelion"          
[184] "Goodrich"                                                
[185] "Oddity"                                                  
[186] "Indian 2"                                                
[187] "Kill"                                                    
[188] "The Roundup: Punishment"                                 
[189] "Kneecap"                                                 
[190] "Sting"                                                   
[191] "Touch"                                                   
[192] "Article 370"                                             
[193] "Bad Newz"                                                
[194] "DAN DA DAN: First Encounter"                             
[195] "Widow Clicquot"                                          
[196] "Sasquatch Sunset"                                        
[197] "Fighter"                                                 
[198] "La Chimera"                                              
[199] "Napoleon"                                                
[200] "Daddio"                                                  

Exercício rápido

  • Extrair a tabela inteira do boxoffice mojo, e não somente os títulos.

  • Qual foi a maior bilheteria nesse ano? E foi correspondente a qual filme?

Incrementando um pouco mais

  • Muitas vezes, podemos estar interessados em mais de uma página sobre um mesmo assunto.

  • No caso anterior, por exemplo, poderíamos estar interessados em extrair as informações para vários anos diferentes.

  • Neste caso, temos que iterar ao longo das URL’s

  • Se tivermos sorte, conseguimos fazer isso de um jeito prático.

Exemplo

  • Vamos iterar ao longo dos anos e pegar a tabela de maiores bilheterias
tabelas <- list()

for(i in 1977:2024){
url <- paste0("https://www.boxofficemojo.com/year/", i, "/?ref_=bo_lnav_hm_shrt")

site <- read_html(url) # Leitura do site

tabela_atual <- site %>% 
  html_elements("#table") %>% 
  html_table() 

tabelas <- c(tabelas, tabela_atual)
Sys.sleep(1)
}
# saveRDS(tabelas, file = "tabelas_boxoffice.rds")

Podemos fazer um breve tratamento e análise

library(dplyr)

tabelas <- readRDS("tabelas_boxoffice.rds")

# Adicionando ano
years <- 1977:2024
tabelas <- lapply(seq_along(tabelas), function(i) {
  tabelas[[i]] %>%
    mutate(year = years[i])
})

## Fazendo um tratamento de dados
sucessos <- do.call(rbind, tabelas) ## Juntando todas as tabelas

sucessos <- sucessos %>% 
  janitor::clean_names() %>% # limpeza de nomes
  mutate(across(contains("gross"), 
                readr::parse_number)) # Transformando receitas em numéricas

Como a maior bilheteria do ano evoluiu ao longo do tempo?

sucessos %>% 
  group_by(year) %>% 
  summarise(receita_maxima = max(total_gross)) %>% 
  ggplot() +
  geom_line(aes(x = year, y = receita_maxima)) +
  theme_bw()

E se quisermos mais informações?

  • Um possível cenário que pode acontecer é o seguinte
    • Temos uma lista de possíveis objetos de interesse.
    • Há mais informações necessárias sobre estes objetos.
    • Estas informações estão em páginas próprias para cada objeto.

Exemplo IMDB

  • Suponha que queremos informações do top 250 Filmes do IMDB

  • Na página temos informação do título, ano e duração.

  • No entanto, não temos informação do elenco, por exemplo.

Exemplo IMDB

  • Podemos usar a função html_attr para saber econtrar as urls de cada filme.

  • Esta função busca por atributos nomeados no HTML.

url <- "https://www.imdb.com/chart/top/"
site <- read_html(url)

links <- site %>% 
  html_elements(".ipc-title-link-wrapper") %>% 
  html_attr("href") 

links
 [1] "/title/tt0111161/?ref_=chttp_t_1"  "/title/tt0068646/?ref_=chttp_t_2" 
 [3] "/title/tt0468569/?ref_=chttp_t_3"  "/title/tt0071562/?ref_=chttp_t_4" 
 [5] "/title/tt0050083/?ref_=chttp_t_5"  "/title/tt0167260/?ref_=chttp_t_6" 
 [7] "/title/tt0108052/?ref_=chttp_t_7"  "/title/tt0110912/?ref_=chttp_t_8" 
 [9] "/title/tt0120737/?ref_=chttp_t_9"  "/title/tt0060196/?ref_=chttp_t_10"
[11] "/title/tt0109830/?ref_=chttp_t_11" "/title/tt0167261/?ref_=chttp_t_12"
[13] "/title/tt0137523/?ref_=chttp_t_13" "/title/tt1375666/?ref_=chttp_t_14"
[15] "/title/tt0080684/?ref_=chttp_t_15" "/title/tt0133093/?ref_=chttp_t_16"
[17] "/title/tt0099685/?ref_=chttp_t_17" "/title/tt0073486/?ref_=chttp_t_18"
[19] "/title/tt0816692/?ref_=chttp_t_19" "/title/tt0114369/?ref_=chttp_t_20"
[21] "/title/tt0038650/?ref_=chttp_t_21" "/title/tt0047478/?ref_=chttp_t_22"
[23] "/title/tt0102926/?ref_=chttp_t_23" "/title/tt0120815/?ref_=chttp_t_24"
[25] "/title/tt0317248/?ref_=chttp_t_25"

Extração do IMDB

  • Vamos extrair primeiro os dados da página geral e depois tentar entrar em cada url para extrair as informações adicionais.

Extração do IMDB

url <- "https://www.imdb.com/chart/top/"
site <- read_html(url)

titulos <- site %>% 
  html_elements(".ipc-title-link-wrapper") %>% 
  html_text()

outras <- site %>%
  html_elements(".cli-title-metadata-item") %>%
  html_text2()

outras
 [1] "1994"   "2h 22m" "16"     "1972"   "2h 55m" "14"     "2008"   "2h 32m"
 [9] "12"     "1974"   "3h 22m" "14"     "1957"   "1h 36m" "Livre"  "2003"  
[17] "3h 21m" "14"     "1993"   "3h 15m" "14"     "1994"   "2h 34m" "18"    
[25] "2001"   "2h 58m" "12"     "1966"   "2h 41m" "14"     "1994"   "2h 22m"
[33] "14"     "2002"   "2h 59m" "12"     "1999"   "2h 19m" "18"     "2010"  
[41] "2h 28m" "14"     "1980"   "2h 4m"  "Livre"  "1999"   "2h 16m" "14"    
[49] "1990"   "2h 25m" "14"     "1975"   "2h 13m" "16"     "2014"   "2h 49m"
[57] "10"     "1995"   "2h 7m"  "14"     "1946"   "2h 10m" "Livre"  "1954"  
[65] "3h 27m" "10"     "1991"   "1h 58m" "14"     "1998"   "2h 49m" "14"    
[73] "2002"   "2h 10m" "18"    

Extração IMDB

outras <- matrix(outras, ncol = 3, byrow = TRUE)
colnames(outras) <- c("ano", "duracao", "classificacao")

dados <- cbind.data.frame("titulo" = titulos, outras)
dados <- dados %>% mutate(ano = as.numeric(ano))

head(dados)
                                   titulo  ano duracao classificacao
1                1. Um Sonho de Liberdade 1994  2h 22m            16
2                    2. O Poderoso Chefão 1972  2h 55m            14
3       3. Batman: O Cavaleiro das Trevas 2008  2h 32m            12
4          4. O Poderoso Chefão: Parte II 1974  3h 22m            14
5             5. 12 Homens e uma Sentença 1957  1h 36m         Livre
6 6. O Senhor dos Anéis: O Retorno do Rei 2003  3h 21m            14

Extração dos atores

for(i in seq_along(links)){
  url <- links[i]
  url_completa <- glue::glue("https://www.imdb.com{url}")
  
  site <- read_html(url_completa)
  
  atores <- site %>% 
    html_nodes(".sc-cd7dc4b7-1") %>% 
    html_text2()
  
  # personagens <-  site %>% 
  #   html_nodes(".title-cast-item__characters-list") %>% 
  #   html_text2()
  
  atores <- paste0(atores, collapse = ",")
  dados$atores[i] <- atores
  Sys.sleep(1)
}

saveRDS(dados, file = "dados_atores.rds")

Quais atores aparecem mais?

dados <- readRDS("dados_atores.rds")

glimpse(dados)
Rows: 25
Columns: 5
$ titulo        <chr> "1. Um Sonho de Liberdade", "2. O Poderoso Chefão", "3. …
$ ano           <dbl> 1994, 1972, 2008, 1974, 1957, 2003, 1993, 1994, 2001, 19…
$ duracao       <chr> "2h 22m", "2h 55m", "2h 32m", "3h 22m", "1h 36m", "3h 21…
$ classificacao <chr> "16", "14", "12", "14", "Livre", "14", "14", "18", "12",…
$ atores        <chr> "Tim Robbins,Morgan Freeman,Bob Gunton,William Sadler,Cl…
strsplit(dados$atores, ",") %>%
  unlist() %>%
  table() %>%
  sort(decreasing = TRUE) %>% 
  .[1:20]
.
     Billy Boyd  Cate Blanchett     Elijah Wood    Ian McKellen   Michael Caine 
              3               3               3               3               3 
 Morgan Freeman   Orlando Bloom      Sean Astin Viggo Mortensen       Al Pacino 
              3               3               3               3               2 
   Bernard Hill       Brad Pitt Christopher Lee  Cillian Murphy     David Aston 
              2               2               2               2               2 
   Diane Keaton    Frank Sivero       John Bach     John Cazale   Marton Csokas 
              2               2               2               2               2 

Limitações e outros pacotes

  • O rvest não consegue lidar com sites que dependem de interação com a pessoa que está navegando. (Em geral, feitos com javascript ou algo do tipo.)
  • Na prática, é um pacote válido para lidar com sites mais “estáticos”.
  • Para lidar com sites que necessitam de interação com o usuário para revelar conteúdo, existe o pacote RSelenium
  • No python o pacote Beautiful Soup faz coisas similares ao rvest.

Agora é com vocês!

  • Procure um site do qual você queira extrair informações e tente aplicar o que foi aprendido até aqui!
  • Além de realizar a extração, tente fazer algum tipo de análise, mesmo que breve.