Objetivo

Vamos a “scrapear” (recuperar) los discursos del sitio web de Casa Rosada

Empecemos por ver un poco el sitio:

Cuando ingresamos en https://www.casarosada.gob.ar/informacion/discursos vemos una página con un índice de discursos, donde sólo se indica la fecha y el título:

Una cuestión importante a considerar es que el sitio no muestra todos los discursos en la misma página sino que estos están paginados (hay 35 páginas al momento de hacer este tutorial). Podemos ver también que hay 40 discursos en cada página. Se avanza de página con una navegación ubicada al pie:

Cuando navegamos hacia la segunda página, podemos ver que la URL cambia hacia https://www.casarosada.gob.ar/informacion/discursos?start=40.

Como vemos la URL utiliza un parámetro (que identificamos por el símbolo ? y el =), seguido de un valor numérico. Esto nos da la pauta de que la página utiliza un template para popular los discursos, y que tiene un offset, es decir, un parámetro que indica a partir de qué punto debe comenzar a mostrar los discursos: en el caso de la 2da página desde el discurso número 40; en la 3ra desde el discurso número 80, y asi hasta llegar al final de páginas.

Si vamos a la última pagina, vemos que la URL es https://www.casarosada.gob.ar/informacion/discursos?start=1360. Es decir, que el último valor de ?start= es 1360.

Luego, para ver un discurso tenemos que hacer click en uno de estos títulos:

Notemos que la URL de los discursos siguen otro patrón: tienen como base https://www.casarosada.gob.ar/informacion/discursos/ (a la base anterior se agregó /discursos/) y luego un identificador numérico y el título del discurso separado por “-”. Por ejemplo:

Si quisiéramos recuperar todos los discursos del sitio vamos a tener que ir a cada una de las páginas y tomar los links de cada discurso. Conviene entonces hacer el código en dos partes:

  1. leer todas las páginas del índice de discursos, recuperando la URL de cada discurso

  2. leer cada discurso en particular.

Comencemos!

Preparemos el ambiente

Empecemos por setear las librerias:

# si es la primera vez que corren codigo R seguro tengan que instalar librerias
# install.packages(c("tidyverse","foreach","rvest"))

library(tidyverse) # para uso general
library(rvest) # para recuperar contenido de la web
library(foreach) # para trabajar con bucles

Parte 1) Hagamos un índice de discursos

Una posible manera de conseguir todos los links de los discursos sería hacer que nuestro código vaya recorriendo cada link en el paginador del pie de página. Pero dado que ya sabemos que cada uno de estos links lo único que modifica es el valor del offset (es decir, que solo cambia el valor de ?start=40, ?start=80…), podemos generar directamente de manera programática estos links.

url_base_paginado <- "https://www.casarosada.gob.ar/informacion/discursos?start="

offset <- seq(from=0, to=1360, by=40) # secuencia de 0 a 1360, con pasitos de 40

offset
##  [1]    0   40   80  120  160  200  240  280  320  360  400  440  480  520  560
## [16]  600  640  680  720  760  800  840  880  920  960 1000 1040 1080 1120 1160
## [31] 1200 1240 1280 1320 1360

Ahora concatenando url_base_paginado y cada valor del vector offset ya tendríamos las URLs de cada página. Por ejemplo, la 5ta pagina se podría componer de esta manera:

paste0(url_base_paginado, offset[5])
## [1] "https://www.casarosada.gob.ar/informacion/discursos?start=160"

Ahora si, empecemos con el webscraping

Ahora que sabemos que podemos armar las direcciones de las páginas a leer, veamos cómo haríamos el webscraping.

Nos interesa cada link de los discursos, para ello tenemos que encontrar algún patrón en el marcado de la página que nos permita identificarlos. Para esto conviene inspeccionar el código web de la página, con el panel de desarrollador del navegador (por ejemplo, en Chrome presionando F12):

En el caso de esta página, tenemos varias opciones:

Ahora bien, tenemos un problema si nos quedamos con sólo el primer criterio, y es que los links del paginador (con los que cambiados de página del índice) también siguen el patron. Conviene entonces sumar ambos criterios. En CSS esto se puede expresar así:

"a[href^='/informacion/discursos'].panel"

Que se traduce como: links que incluyan /informacion/discursos en su propiedad href y que tengan la class .panel.

Ahora que sabemos como identificarlas, vamos a recorrerlo con R:

discursos_links <- list() # Inicializamos una lista vacía donde vamos a guardar los enlaces de los discursos de las distintas páginas

for (i in 1:length(offset[1:2])) { # por cada elemento del vector offset (VER NOTA ABAJO!) ...
  pagina_con_links <- paste0(url_base_paginado, offset[i]) # ... construimos la URL completa ...
  message(pagina_con_links) # Mostramos en consola la URL que estamos leyendo, para ver como venimos
  discursos_links[[i]] <- read_html(pagina_con_links) %>% # Leemos el contenido HTML de la página
    html_nodes("a[href^='/informacion/discursos'].panel") %>% # Seleccionamos los nodos (pedazos de codigo) que corresponden a enlaces de discursos
    html_attr("href") # Extraemos los atributos 'href' (enlaces) de los nodos seleccionados
}
## https://www.casarosada.gob.ar/informacion/discursos?start=0
## https://www.casarosada.gob.ar/informacion/discursos?start=40
discursos_links <- unlist(discursos_links) # Convertimos la lista en un vector

NOTA! offset es un vector de varios elementos (35 al momento en que hicimos este tutorial), y en cada una de estas páginas vamos a encontrar 40 links. Es decir que al final tendríamos aprox. 1400 links! Esto es un montón para este ejemplo, vamos a limitarnos a sólo recorrer las 2 primeras páginas del índice. Por eso en nuestro código hemos indicado que sólo queremos recorrer los elementos 1 a 2 (en R: offset[1:2]).

Si quisiéramos recorrer todas las páginas deberíamos cambiar esto: for (i in 1:length(offset[1:2])) { por el vector completo: for (i in 1:length(offset)) {

Veamos en qué resulto nuestro código:

length(discursos_links) # cuantos links tenemos?
## [1] 80
head(discursos_links) # veamos los primeros
## [1] "/informacion/discursos/50622-palabras-del-presidente-javier-milei-en-el-acto-por-el-140-aniversario-de-la-bolsa-de-comercio-de-rosario"                                                                                                 
## [2] "/informacion/discursos/50614-palabras-del-presidente-javier-milei-en-la-cena-de-camaraderia-de-las-fuerzas-armadas"                                                                                                                     
## [3] "/informacion/discursos/50612-palabras-del-presidente-de-la-nacion-en-congreso-de-inversiones-inmobiliarias"                                                                                                                             
## [4] "/informacion/discursos/50609-palabras-del-presidente-de-la-nacion-javier-milei-en-la-21-conferencia-anual-del-council-of-the-americas-titulada-argentina-perspectivas-economicas-y-politicas-en-el-hotel-alvear-caba"                   
## [5] "/informacion/discursos/50604-discurso-del-presidente-javier-milei-en-el-acto-conmemorativo-por-el-primer-trillon-de-pies-cubicos-transportados-de-gas-natural-a-traves-de-la-cordillera-por-el-gasoducto-gas-andes-en-santiago-de-chile"
## [6] "/informacion/discursos/50593-palabras-del-presidente-de-la-nacion-javier-milei-en-la-exposicion-de-ganaderia-agricultura-e-industria-internacional-en-la-rural"

Parte 2) Vamos a leer cada discurso

Ahora que tenemos un listado de URLs con cada discurso tenemos que repetir el proceso de lectura y recuperación de la información. Para esto, otra vez, exploremos la página buscando un patrón en el marcado:

Como vemos en el código de la página, el div que contiene los discursos tiene la class .col-md-8.col-md-offset-2 y el contenido de los discursos se incluye como párrafos (tag p) dentro suyo. Esto en CSS se puede expresar así:

.col-md-8.col-md-offset-2 > p

Así que otra vez, hagamos un for para nuestro webscraping. Las tareas que debería repetir este for son:

url_base_discurso <- "https://www.casarosada.gob.ar/informacion/discursos" # URL base para acceder a cada discurso

discursos_contenido <- list() # Inicializamos una lista vacía para colectar el contenido de los discursos

for (i in 1:length(discursos_links[1:10])) { # Iteramos sobre los primeros 10 enlaces de discursos (VER NOTA ABAJO!) ...
  pagina_con_discurso <- paste0(url_base_discurso, discursos_links[i]) # Construimos la URL completa para cada discurso
  message(pagina_con_discurso) # Mostramos en consola la URL del discurso actual

  discursos_contenido[[i]] <- read_html(pagina_con_discurso) %>% # Leemos el contenido HTML de la página del discurso
    html_nodes(".col-md-8.col-md-offset-2 > p") %>% # Seleccionamos los párrafos dentro de la columna principal de la página
    html_text() %>% # Extraemos el texto de los nodos seleccionados
    paste(., collapse = "") # Unimos el texto extraído en un solo string
}
## https://www.casarosada.gob.ar/informacion/discursos/informacion/discursos/50622-palabras-del-presidente-javier-milei-en-el-acto-por-el-140-aniversario-de-la-bolsa-de-comercio-de-rosario
## https://www.casarosada.gob.ar/informacion/discursos/informacion/discursos/50614-palabras-del-presidente-javier-milei-en-la-cena-de-camaraderia-de-las-fuerzas-armadas
## https://www.casarosada.gob.ar/informacion/discursos/informacion/discursos/50612-palabras-del-presidente-de-la-nacion-en-congreso-de-inversiones-inmobiliarias
## https://www.casarosada.gob.ar/informacion/discursos/informacion/discursos/50609-palabras-del-presidente-de-la-nacion-javier-milei-en-la-21-conferencia-anual-del-council-of-the-americas-titulada-argentina-perspectivas-economicas-y-politicas-en-el-hotel-alvear-caba
## https://www.casarosada.gob.ar/informacion/discursos/informacion/discursos/50604-discurso-del-presidente-javier-milei-en-el-acto-conmemorativo-por-el-primer-trillon-de-pies-cubicos-transportados-de-gas-natural-a-traves-de-la-cordillera-por-el-gasoducto-gas-andes-en-santiago-de-chile
## https://www.casarosada.gob.ar/informacion/discursos/informacion/discursos/50593-palabras-del-presidente-de-la-nacion-javier-milei-en-la-exposicion-de-ganaderia-agricultura-e-industria-internacional-en-la-rural
## https://www.casarosada.gob.ar/informacion/discursos/informacion/discursos/50583-palabras-del-presidente-de-la-nacion-javier-milei-tras-recibir-el-premio-a-la-investigacion-politica-entregado-por-la-fundacion-ilan
## https://www.casarosada.gob.ar/informacion/discursos/informacion/discursos/50580-palabras-del-presidente-javier-milei-en-la-conferencia-internacional-sobre-seguridad-y-antiterrorismo
## https://www.casarosada.gob.ar/informacion/discursos/informacion/discursos/50574-palabras-del-presidente-javier-milei-en-la-bolsa-de-comercio
## https://www.casarosada.gob.ar/informacion/discursos/informacion/discursos/50568-palabras-del-presidente-javier-milei-en-la-firma-del-pacto-de-mayo
discursos_contenido <- unlist(discursos_contenido) # Convertimos la lista de discursos en un vector plano de textos

NOTA! dado que discursos_links podría tener 1400 elementos, otra vez introdujimos una limitación en el for: sólo queremos recorrer los elementos 1 a 10 (en R: discursos_links[1:10]).

Si quisiéramos recorrer todas las páginas deberíamos cambiar esto: for (i in 1:length(discursos_links[1:10])) { por el vector completo: for (i in 1:length(discursos_links)) {

Vemos que pudimos armar?

length(discursos_contenido) # cuantos discursos tenemos?
## [1] 10
head(substring(discursos_contenido, 1, 100)) # muestra los primeros 50 caracteres de los primeros elementos
## [1] "Palabras del presidente Javier Milei en el acto por el 140 ° Aniversario de la Bolsa de Comercio de "
## [2] "Palabras del Presidente Javier Milei en la Cena de Camaradería de las Fuerzas ArmadasCelebramos el a"
## [3] "Palabras del Presidente de la Nación en el Congreso de Inversiones InmobiliariasVeo que se están not"
## [4] " Buenos días a todos. Quiero transmitir mi agradecimiento a Susan Segal, Natalio Grinman y a los dem"
## [5] "Discurso del Presidente Javier Milei en el acto conmemorativo por el primer trillón de pies cúbicos "
## [6] "Palabras del Presidente de la Nación, Javier Milei, en la Exposición de Ganadería, Agricultura e Ind"

Lo conseguimos!

Ahora sólo nos queda empezar a hacer text-mining pero eso es tema para otro tutorial.

Quieren otro ejemplo, con otro caso? Chusmeen por acá: https://bookdown.org/gaston_becerra/curso-intro-r/construccion-de-datasets-borrador.html#web-scraping