7 min read

Web scraping II

Segunda entrega.

Scraping de un sitio completo

En la entrega anterior revisamos los aspectos básicos del web scrapping: por qué, para qué y cómo.

Cargar una página web a la vez ya es un ahorro de tiempo y en algunos casos es todo lo que necesitamos: leer una tabla o algún texto. Sin embargo es común que nos interese leer y almacenar de manera estructura el contenido de varias páginas web. Para hacerlo tenemos que llevar a cabo algunos pasos previos:

  1. Identificar una página en el sitio web de interés que contenga el índice de las entradas que queremos consultar.
  2. Crear una lista con todas la direcciones (URL) de las que nos interesa extraer información.
  3. Pasar esa lista a la función que extrae la información de cada página, generando una estructura de datos para cada página.
  4. Reunir la información de cada página en una estructura de datos general.

Aproximación map-reduce

Los pasos 3 y 4 corresponden a una aproximación muy útil de programación que se llama map-reduce. Es una aproximación al problema de repetir una operación muchas veces, lo que técnicamente se llama iterar.1 En este caso iteramos una lista de direcciones (URL) en una función que lee el contenido de las páginas en esa dirección y almacenamos el resultado en una lista. Esta es la parte map. Posteriormente convertimos esa lista a una data.frame con todas las entradas. Es la parte reduce.

Aplicación a la Revista Secuencia

Generar una lista de direcciones a cada número de la revista

En el caso de la Secuencia la dirección contiene el archivo de la revista. En esta página hay en enlaces a cada número. Visitando esos enlaces encontramos, a su vez, los enlaces para cada artículo y allí encontraremos, finalmente, los resúmenes.

El proceso en general no es muy distinto al que usamos para encontrar dentro de la estructura de la página a los resumes y que vimos en la entrada 1 de web scraping. Utilizamos el modo desarrollar del navegador para dar con las etiquetas y atributos de los enlaces. En este caso encontramos los enlaces a cada número son un título de nivel 4 h4, con la etiqueta a del que queremos el atributo href, que es el nombre interno que tienen los enlaces.

library(rvest)
## Loading required package: xml2
library(stringr)
library(knitr)
library(purrr) # Para iterar sobre listas
## 
## Attaching package: 'purrr'
## The following object is masked from 'package:rvest':
## 
##     pluck
# Hay dos páginas con el índice de la revista.
# Obtego los enlaces por separado y luego los uno. 

numeros_secuencia1 <- read_html("http://secuencia.mora.edu.mx/index.php/Secuencia/issue/archive") %>%
  html_nodes("h4") %>% 
  html_nodes("a") %>% 
  html_attr("href")  

numeros_secuencia2 <- read_html("http://secuencia.mora.edu.mx/index.php/Secuencia/issue/archive?issuesPage=2#issues") %>%
  html_nodes("h4") %>% 
  html_nodes("a") %>% 
  html_attr("href")

numeros_secuencia <- c(numeros_secuencia1, numeros_secuencia2)

kable(head(numeros_secuencia))
x
http://secuencia.mora.edu.mx/index.php/Secuencia/issue/view/118
http://secuencia.mora.edu.mx/index.php/Secuencia/issue/view/117
http://secuencia.mora.edu.mx/index.php/Secuencia/issue/view/116
http://secuencia.mora.edu.mx/index.php/Secuencia/issue/view/115
http://secuencia.mora.edu.mx/index.php/Secuencia/issue/view/113
http://secuencia.mora.edu.mx/index.php/Secuencia/issue/view/112

Generar una lista con las direcciones de cada artículo

Con las direcciones de cada número de la revista podemos pasar al suguiente paso: crear la lista con las URL de cada artículo. Para eso creamos una función que lea cada número de revista y recolecte los enlaces de cada artículo. La llamaremos listar_enlaces y le daremos como input la lista de números de revistas. La estructura de datos final de esta etapa será un data.frame con dos columnas, uno con la dirección del artículo y otro con el número de la revista a la que pertenece.

listar_enlaces <- function(x) { 
  pagina_cruda <- read_html(x)
html_nodes(pagina_cruda, "div.tocTitle") %>% 
  html_nodes("a") %>% 
  html_attr("href") -> enlaces
html_node(pagina_cruda, "h2") %>% html_text() -> numero
as.data.frame(cbind(enlaces, numero))}

# Test: 

listar_enlaces("http://secuencia.mora.edu.mx/index.php/Secuencia/issue/view/112") 
##                                                              enlaces
## 1 http://secuencia.mora.edu.mx/index.php/Secuencia/article/view/1464
## 2 http://secuencia.mora.edu.mx/index.php/Secuencia/article/view/1396
## 3 http://secuencia.mora.edu.mx/index.php/Secuencia/article/view/1416
## 4 http://secuencia.mora.edu.mx/index.php/Secuencia/article/view/1400
## 5 http://secuencia.mora.edu.mx/index.php/Secuencia/article/view/1439
## 6 http://secuencia.mora.edu.mx/index.php/Secuencia/article/view/1542
## 7 http://secuencia.mora.edu.mx/index.php/Secuencia/article/view/1398
## 8 http://secuencia.mora.edu.mx/index.php/Secuencia/article/view/1430
##           numero
## 1 Núm. 99 (2017)
## 2 Núm. 99 (2017)
## 3 Núm. 99 (2017)
## 4 Núm. 99 (2017)
## 5 Núm. 99 (2017)
## 6 Núm. 99 (2017)
## 7 Núm. 99 (2017)
## 8 Núm. 99 (2017)

Luego iteramos esa función a lo largo de la lista con los URL de los números de revista. Al resultado lo llamaremos enlaces y el que usaremos para obtener, en su momento los resúmenes. En esta parte vamos a aplicar el enfoque map-reduce con la función map_df() de la librería purrr. Esta función aplica una función a una lista (en este caso la función listar_enlaces() a la lista numeros_secuencia) y nos regresa el resultado “reducido” a un data.frame.

# Aquí aplicamos la función que preparamos y testeamos en el bloque anterior.
enlaces <- map_df(numeros_secuencia, listar_enlaces)

Leer los resúmenes y organizarlos como un data.frame

Este es el último paso, que producirá la estructura de datos que estamos buscando. Para eso, una vez más creamos una función personalizada llamada leer_resumenes() que accede a cada artículo y captura el título del artículo, texto del resumen, autores y los datos del número en que se publicó. Para cada una de estas columnas extraemos la información relevante con cadenas de html_node().

# Leer los resúmenes. 

leer_resumenes <- function (x) {
  print(x)
  pagina_cruda = read_html(x)
  html_nodes (pagina_cruda, 'div#articleTitle') %>% 
    html_text() -> titulo
  html_nodes(pagina_cruda, 'div#authorString') %>% 
    html_text() -> autores
  html_nodes(pagina_cruda, 'div#articleAbstract') %>%
    .[1] %>% 
    html_text() %>%
    str_remove_all("\\r") %>%
    str_remove_all('\\t') %>% 
    str_replace_all("\\n", " ") %>% 
    str_remove_all('\\- ') -> resumen
html_nodes (pagina_cruda, 'div#breadcrumb') %>% 
    html_nodes('a') %>% 
    .[2] %>% 
    html_text() -> numero  
    data.frame(autores, titulo, resumen, numero)
}

# Test con algunos enlaces

map_df(enlaces[100:102,1], leer_resumenes) -> resumenes
## [1] "http://secuencia.mora.edu.mx/index.php/Secuencia/article/view/1377"
## [1] "http://secuencia.mora.edu.mx/index.php/Secuencia/article/view/1391"
## [1] "http://secuencia.mora.edu.mx/index.php/Secuencia/article/view/1379"
# Para extraer todos los enlaces: 
# map_df(enlaces$enlace, leer_resumenes) -> resumenes
# Para guardar los resultados: 
# write_csv(resumenes, "./resumenes_secuencia.csv")

kable(resumenes)
autores titulo resumen numero
Gisela Moncada González La gestión municipal: ¿cómo administrar las plazas y los mercados de la ciudad de México? 1824-1840 Resumen El texto analiza la gestión de los servicios públicos en la ciudad de México, y en particular, la administración de las plazas y los mercados a través de contratas a particulares. La problemática se centra en las dificultades que enfrentó la administración municipal para regular el comercio urbano. La fuente documental empleada es la Ordenanza Municipal de 1840 y el estudio detallado de las comisiones que integraban al Ayuntamiento de México entre 1810 y 1840. La investigación concluye que la falta de un orden jurídico que regulara la gestión municipal provocó una mala organización y planeación en los servicios públicos de la ciudad. Por ello, las contratas, que parecían una salida eficiente en la administración municipal, terminaron por convertirse en negocios entre particulares, debido a la incapacidad del gobierno para vigilarlos y regularlos, así como por la falta de recursos económicos para sostenerlas.  Núm. 95 (2016)
Ma. Eugenia Chaoul Pereyra Un aparato ortopédico para el magisterio: la Dirección General de Educación Primaria y los maestros en el Distrito Federal, 1896-1913 Resumen Al convertirse en empleados federales en 1896, los maestros de primaria del Distrito Federal y de los territorios (Nayarit y Baja California) ocuparon un papel singular para poner en marcha la política educativa del régimen porfiriano. En el marco de la anhelada centralización educativa, las autoridades promovieron que los docentes se desempeñaran como agentes modernizadores y pudieran difundir los nuevos contenidos pedagógicos. Sin embargo, la estructura burocrática diseñada para tal fin derivó en un aparato que supervisó, controló e inmovilizó a los maestros relegándolos a ocupar un papel receptivo, secundario y dependiente de los favores de la autoridad. Este desempeño fue crucial para entender las características que adquirió la organización magisterial al despuntar la revolución Núm. 95 (2016)
Carlos Sola Ayape Al rescate de Franco y del franquismo: el hispanismo mexicano en la encrucijada de la Segunda Guerra Mundial Resumen En 1945, y una vez terminada la segunda guerra mundial, los países vencedores –México, entre ellos– identificaron al régimen franquista como un saldo de guerra y, entre otras medidas en contra, se prohibió el ingreso de España en las Naciones Unidas. En defensa propia, y con un Franco acorralado, los arquitectos del franquismo diseñaron un plan estratégico para aguantar los embates del exterior, contando para ello con la ayuda de hispanistas mexicanos como Alfonso Junco o Jesús Guisa y Azevedo. Así, y teniendo en cuenta los perfiles de esta encrucijada histórica, el objetivo del presente artículo es analizar el discurso del hispanismo mexicano, concebido instrumentalmente para salir en defensa de Franco y del franquismo.   Núm. 95 (2016)

Para no abusar del servicio que nos da la Revista Secuencia es recomendable que, una vez que hemos recoletado toda la información, la guardemos en un archivo .csv. De este modo la tendremos disponible para el análisis posterior, sin tener que esperar que la descarga y sin saturar el servicio.

En una entrega siguiente comenzaremos a analizar estos resúmenes usando procesamiento de lenguaje natural.


  1. Otra aproximación para hacer iteraraciones consiste en utilizar estructuras de control del tipo for o while y manejar de manera explícita la operación a través de índices. La ventaja de la aproximación map-reduce es que nos ahorramos el proceso tedioso de escribir la estructura de control.