Selectores CSS: Una forma adicional de seleccionar los nodos adecuados para el Web Scraping.
Durante la sesión pasada, vimos que el Web-Scraping consiste en un proceso de recolección automatizada de información de sitios web (estáticos), y que para llevar esta a cabo teníamos que seguir un procedimiento como el que se describe a continuación
Explorar la página de la que queremos extraer información: ver que información me interesa, la estructura de la página, etc.
Extraer el código HTML de la página web con la función
read_html().Identificar, utilizando el inspector del navegador, la ruta de los datos de interés, copiando la Ruta del Selector seleccionando el código que contiene la información que necesitamos.
Guardamos esta ruta y la utilizamos en la función
html_node(), la cual extrae del código HTML obtenido en el paso dos, la rama que contiene la información particular que requerimos.Posteriormente, el contenido HTML de esa rama lo traducimos a texto con la función
rvest::html_text()o le sacamos los atributos (si es un enlace directo, el atributo podría ser la página de destino, por ejemplo) con la funciónrvest::html_attr()o, si hay tablas, extraemos directamente los datos con la funciónrvest::html_table().Por último, implementabamos un loop que extraiga, de todas las ramas, el dato preciso que estabamos buscando, de acuerdo a la estructura de la página.
De todos los pasos descritos previamente, el más complicado, a mi parecer, es el de seleccionar la rama correcta. Para seleccionar las ramas adecuadas dentro de una página de internet, tenemos varias opciones, como copiar la ruta del selector, utilizar selectores CSS o (una alternativa que no me gusta mucho usar) la selección del XPath del elemento en cuestión. En clase, utilizamos la primera.
El problema de usar la ruta del selector consiste en que es un método que solo te da un dato o valor a la vez. Regresando a la analogía de las manzanas en el árbol, es como decirle a quien las cosecha: “quiero que cortes esta manzana, y luego esta de mas arriba, y luego la que esta a la derecha, y asi hasta que acabes”. Este método es bueno para iniciar a explicar con el tema porque extraes exactamente lo que quieres extraer, y porque copiar la ruta del selector es más sencillo ubicar exactamente la ruta de lo que queremos descargar, pero hay un método más fácil para descargar información y, de paso, evitarnos un loop.
Dentro del código HTML existen unos pedazos de código llamados “Selectores CSS”. Desde el punto del diseño web, los selectores CSS identifican ciertas partes del contenido de una página web de tal forma que, cuando les aplicamos un estilo gráfico, este se reparta dependiendo cada selector (algo muy importante de saber para hacer tus Markdowns más bonitos) pero también, estos pedazos de código ayudan a un script de Web Scraping a clasificar las ramas en ciertos tipos y poder extraer información de un mismo tipo de nodos en un solo paso.
Los selectores más comunes son los de clase, class, y los de identificación, id, y a partir de estos podemos identificar ciertas secciones comunes. Podemos identificarlos en el código HTML que genera las páginas gracias a que generalmente dicen class= o id=.
Estos nombres los anotamos y los pasamos como los nuevos nodos para seleccionar la información que necesitamos. Cuando pasamos un nodo proveniente de un class tenemos que ponerle un punto “.” al principio, y cuando pasamos un nodo proveniente de un id tenemos que usar un símbolo de hashtag (#).
Vamos a realizar a continuación un ejemplo sencillo de como hacer un Web Scraping utilizando selectores CSS de clase.
Haciendo Web Scraping de los comunicados de prensa del Grupo parlamentario de Acción Nacional en el Senado.
Problema: Queremos tener en una base de datos estructurada los comunicados de prensa de Acción Nacional, para realizar un análisis de los temas que han estado en la agenda del partido en los años recientes.
Página que almacena información:
Paso 1. Analizo la página:
Fig. 1: Comunicados del PAN en el Senado
De la página anterior, checo la información que me interesa. Para este caso, solo van a ser los títulos de las notas y las fechas de publicación.
Recolecto, a partir del carrusel que se encuentra al final de la página, las urls de cada subpágina y llego a lo siguiente:
Fig. 2: Todas las subpáginas disponibles. Checar que llega hasta la 131.
Igualmente, checo de una vez los selectores CSS de los elementos que me interesan, como se muestra en las imágenes siguientes.
Fig. 3: Ubicación del selector CSS de los títulos de los comunicados
Fig. 4: Ubicación del selector CSS de los títulos de las fechas
Ya ubicados los selectores CSS, procedemos a extraer esos datos de interés, para el caso n = 1.
Caso n = 1
# Librerias
library(tidyverse)
library(rvest)
# Página n = 1
url <- "https://www.pan.senado.gob.mx/category/comunicados/page/2/"
# Selectores CSS
nodo_titulo <- ".entry-title"
nodo_fecha <- ".entry-date"
# Leemos el codigo HTML de la página
code <- read_html(url)
# Extraemos los títulos de los comunicados con el Selector CSS
titulo <- html_nodes(code, nodo_titulo) %>% html_text()
length(titulo)## [1] 11
# Extraemos los datos de las fechas con el selector CSS
fecha <- html_nodes(code, nodo_fecha) %>% html_text()
length(fecha)## [1] 10
Como podemos ver, se extrajeron más títulos que fechas.
Esto se debe a que el título general de la página comparte el mismo selector CSS que los títulos de los comunicados.
Fig. 5: Los títulos de los comunicados y el título general comparten el mismo selector CSS.
Para resolver esto, simplemente eliminamos el primer elemento del vector titulo.
# Como podemos ver, tenemos un titulo extra
# Inspeccionando, podemos ver que el primer título es el que sobra, y lo quitamos.
titulo <- titulo[-1]Ahora que las columnas son del mismo tamaño, pegamos las columnas con la función cbind.data.frame().
# Podemos juntar ambos vectores en dos columnas:
datos <- cbind.data.frame(titulo, fecha) %>%
mutate(pagina = url) # Esto genera una tercera columna
# Con el dato de la url donde se encuentra la nota. | titulo | fecha | pagina |
|---|---|---|
| Sí a consulta para apoyar a los ciudadanos, rechazo total a simulación del Presidente: Indira | 7 octubre, 2020 | https://www.pan.senado.gob.mx/category/comunicados/page/2/ |
| Con la consulta popular, Morena resultó una falsa transformación: Julen Rementería | 7 octubre, 2020 | https://www.pan.senado.gob.mx/category/comunicados/page/2/ |
| Incongruencia de Morena apoyar circo electoral del Presidente y rechazar apoyo económico a todos los mexicanos: GPPAN | 7 octubre, 2020 | https://www.pan.senado.gob.mx/category/comunicados/page/2/ |
| Con los 500 millones del estadio de béisbol podrían comprarse medicinas para los niños con cáncer: Indira Rosales | 6 octubre, 2020 | https://www.pan.senado.gob.mx/category/comunicados/page/2/ |
| Madero Muñoz demandó al secretario de Hacienda enderezar la economía del país | 6 octubre, 2020 | https://www.pan.senado.gob.mx/category/comunicados/page/2/ |
| Estados contraerán deuda a consecuencia de mala política hacendaria del gobierno: Minerva Hernández | 6 octubre, 2020 | https://www.pan.senado.gob.mx/category/comunicados/page/2/ |
| Rotundo rechazo del GPPAN a adelantar la consulta; tienen miedo de perder las elecciones: Mauricio Kuri | 6 octubre, 2020 | https://www.pan.senado.gob.mx/category/comunicados/page/2/ |
| Insuficiente, inequitativo y descafeinado, plan de infraestructura para reactivar la economía del país: Mauricio Kuri | 6 octubre, 2020 | https://www.pan.senado.gob.mx/category/comunicados/page/2/ |
| Exige Toño Martín del Campo información clara y concisa sobre vacuna para Covid-19 | 5 octubre, 2020 | https://www.pan.senado.gob.mx/category/comunicados/page/2/ |
| La crisis económica por el mal manejo de la pandemia ha golpeado más a las mujeres: Mayuli Martínez | 5 octubre, 2020 | https://www.pan.senado.gob.mx/category/comunicados/page/2/ |
¡Y ya! Hasta acá tenemos resuelta la extracción del caso n = 1. Ahora lo extrapolamos a las demás páginas.
Mismo proceso, pero para todas las páginas.
# Para las demás páginas
# 1. Creamos la tabla en blanco
datos_totales <- data.frame()
# 2. Creamos el vector de todas las páginas
todas_paginas <- paste0("https://www.pan.senado.gob.mx/category/comunicados/page/", 1:131, "/")
# Sabemos que son 131 páginas por el carrusel del final de la página.
# 2. Metemos en el loop al caso n = 1
for(pagina in todas_paginas){
# Guardamos cada pagina del loop dentro del objeto url
url <- pagina
# Selectores CSS
nodo_titulo <- ".entry-title"
nodo_fecha <- ".entry-date"
# Guardamos el codigo url
code <- read_html(url)
# Guardamos los titulos (recordemos que sobra el primero)
titulo <- html_nodes(code, nodo_titulo) %>% html_text()
titulo <- titulo[-1]
# Guardamos las fechas
fecha <- html_nodes(code, nodo_fecha) %>% html_text()
# Podemos juntar ambos vectores en dos columnas:
datos <- cbind.data.frame(titulo, fecha) %>%
mutate(pagina = url)
# Guardamos estos datos en nuestra bolsita
datos_totales <- rbind.data.frame(datos_totales, datos)
# Imprimimos un contador de avance:
print(paste0("Ya extrajiste de la pag: ", pagina))
}## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/1/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/2/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/3/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/4/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/5/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/6/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/7/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/8/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/9/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/10/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/11/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/12/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/13/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/14/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/15/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/16/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/17/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/18/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/19/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/20/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/21/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/22/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/23/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/24/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/25/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/26/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/27/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/28/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/29/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/30/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/31/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/32/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/33/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/34/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/35/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/36/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/37/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/38/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/39/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/40/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/41/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/42/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/43/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/44/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/45/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/46/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/47/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/48/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/49/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/50/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/51/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/52/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/53/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/54/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/55/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/56/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/57/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/58/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/59/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/60/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/61/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/62/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/63/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/64/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/65/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/66/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/67/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/68/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/69/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/70/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/71/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/72/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/73/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/74/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/75/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/76/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/77/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/78/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/79/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/80/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/81/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/82/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/83/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/84/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/85/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/86/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/87/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/88/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/89/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/90/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/91/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/92/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/93/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/94/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/95/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/96/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/97/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/98/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/99/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/100/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/101/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/102/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/103/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/104/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/105/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/106/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/107/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/108/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/109/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/110/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/111/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/112/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/113/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/114/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/115/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/116/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/117/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/118/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/119/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/120/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/121/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/122/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/123/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/124/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/125/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/126/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/127/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/128/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/129/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/130/"
## [1] "Ya extrajiste de la pag: https://www.pan.senado.gob.mx/category/comunicados/page/131/"
Una vez extraídos los datos de las páginas anteriores, procedemos a generar una visualización tabular bonita y ordenada para los datos seleccionados:
De esta manera, ya tenemos en nuestro poder una tabla con los comunicados del PAN en el senado para hacer análisis, visualizaciones u otras cosas.
La ventaja de usar selectores CSS fué, entonces, reducirnos un paso de iteración dentro de cada página individual, al jalar todos los elementos de interés en un solo paso y quitar la basura que se juntó.