Tutorial #5 Construccion de datasets (BORRADOR)

En este tutorial vamos a explorar distintas maneras de construir un dataset.

  1. Vamos a conectar con una API (Wikipedia) de la manera más básica;
  2. Mencionamos las limtaciones de la API de Facebook;
  3. Hacemos scrapping para recuperar contenido de páginas web tipo OJS.

Al final listamos algunos otros recursos donde se pueden obtener datasets.

5.1 APIs

Un API (Application Programming Interface) es una “puerta de entrada” que algunas aplicaciones ofrecen para interatuar con sus datos y sus funciones de forma ordenada. Se puede utilizar una API para recuperar contenido de las bases de datos de la aplicación (e.g., obtener tweets de Twitter) de forma legal y dentro de las condiciones de acceso y uso que la aplicación impone.

Generalmente, las API se utilizan a través del protocolo HTTP, de modo que se puede interactuar con ella a través desde distintos lenguajes y/o programas que nos permita hacer requests (acciones para leer, enviar y manipular datos) por internet, ya sea un navegador como Chrome, un programa específico como Postman, o R.

Cada API tiene su dirección URL, y una forma particular de requerir parámetros por medio de ella. Un ejemplo de un request simple que devuelve la información de una foto de Facebook es la siguiente URL, que se puede consultar desde el navegador: https://graph.facebook.com/facebook/picture?redirect=false.

El response o resultado del request es sólo datos, es decir, contenido de la aplicación sin los elementos de diseño o interfaces. Generalmente los datos se escriben en un formato flexible, como XML o JSON. En ocasiones, hay algunos metadatos en los encabezados de las responses que indican cómo resultó nuestros request por medio de un código (e.g., un status 200 significa que el request se procesó OK, mientras un 404 indica que el recurso que buscamos no está disponible).

Muchas APIs requieren una autenticación para poder procesar un request, esto les permite regular el acceso a la información. Para ello, en algunos casos basta con proveer algunas credenciales que obtenemos al registrarnos como usuarios de la aplicación. En otros casos, como en las APIs de Facebook y Twitter, además es necesario registrarse como “desarrollador” y registrar una “aplicación” para la cual se piden ciertos permisos particulares, como leer o postear contenido. Como resultado de este registro generalmente obtenemos algunas “llaves” que incluimos en nuestros requests como parámetros. Otras APIs, como la de Google, tienen algunos servicios pagos y requieren que registremos una tarjeta de crédito en nuestro perfil, para cobrarnos por consumo.

Por suerte, para algunas aplicaciones contamos con paquetes de R para interactuar con sus APIs. Estos paquetes se encargan de formatear los request de pedido de información, incluyendo las claves de seguridad que hayamos obtenido, y/o leer los datos que nos devuelven en un formato compatible con R, como un dataframe.

5.1.1 API de Wikipedia (Sin paquetes!)

La primera API que vamos a explorar es la de Wikipedia. Nos interesa esta API por 2 razones: (1) no requiere autenticación… por lo menos para el tipo de request que vamos a estar haciendo; y (2) no hay paquete de R para interactuar con ella, asi que vamos a usarla “sin rueditas”…. aunque vale la aclaración, si vamos a usar un paquete para hacer requests por HTTP, llamado httr.

Particularmente:

  1. vamos a hacer un request, componiendo una URL y llamandola por GET (un método del protocolo HTTP, que usás todo el tiempo para navegar);
  2. vamos a leer el response, que está en formato JSON, y vamos a ver cómo lo podemos convertir a un formato más cómodo con R.

Antes que nada, tenemos que saber qué clase de requests se pueden hacer (qué puedo preguntarle a la API). Todas las aplicaciones tienen una API Reference que aclara esto; otras, mas copadas, tienen una API Explorer que permite “componer” con formularios los parámetros de los requests y explorar los resultados. Wikipedia, por suerte es uno de ellos: https://es.wikipedia.org/wiki/Especial:Zona_de_pruebas_de_la_API.

Lo primero que vamos a hacer es buscar entradas que incluyan las palabras “big data”.

Para hacer este request es necesario conocer como componer la URL. En el caso de Wikipedia esto se compone por una URL base (https://es.wikipedia.org/w/api.php?) seguido de la acción que nos interesa realizar: “query” porque vamos a buscar páginas (&action=query). Esta acción requiere un criterio (&srsearch=big%20data) y que especifiquemos qué devolver y en qué formato (&list=search&format=json): https://es.wikipedia.org/w/api.php?action=query&list=search&srsearch=big%20data&format=json

library(tidyverse)

criterio <- "big%20data" # como es una URL hay que codificar algunos caracteres, como el espacio (%20)

library(httr) # vamos usar este paquete para usar el protocolo HTTP
llamada <- httr::GET(paste0("https://es.wikipedia.org/w/api.php?action=query&list=search&format=json&srsearch=", criterio)) # hacemos el request via HTTP/GET

llamada$status_code # si es 200, todo OK
## [1] 200

Veamos un pedazo de la respuesta, que se encuentra en formato JSON:

{
  batchcomplete: "",
  continue: {
    sroffset: 10,
    continue: "-||"
  },
  query: {
    searchinfo: {
      totalhits: 6783
    },
    search: [
      {
        ns: 0,
        title: "Macrodatos",
        pageid: 5242736,
        size: 115077,
        wordcount: 13491,
        snippet: "también llamados datos masivos, inteligencia de datos, datos a gran escala o <span class="searchmatch">big</span> <span class="searchmatch">data</span> (terminología en idioma inglés utilizada comúnmente) es un término que",
        timestamp: "2021-06-03T16:23:33Z"
        },
      {
        ns: 0,
        title: "Big Bang",
        pageid: 6822,
        size: 71060,
        wordcount: 9270,
        snippet: "En cosmología, se entiende por <span class="searchmatch">Big</span> Bang,[1]​[2]​ también llamada la Gran Explosión (término proveniente del astrofísico Fred Hoyle a modo de burla de",
        timestamp: "2021-06-12T03:48:05Z"
        },
...

Este contenido se puede acceder a través del content del objeto generado por httr::GET. Esta función convierte el JSON es una lista, es decir, una colección de objetos en R.

Esta lista tiene 4 elementos: batchcomplete y continue se usan para paginar los resultados; query que a su vez tiene 2 elementos: primero informa cuantos resultados hubo; y despues en search incluye los resultados. Arriba mostramos 2 resultados, que incluyen un título, un pageid, y otra info.

Veamos como podemos acceder a este JSON:

respuesta <- httr::content(x = llamada) # httr nos lee el content del objeto que generamos

class(respuesta) # chequeemos que es una lista
## [1] "list"
str(respuesta, max=3) # veamos su estructura ... lo que nos interesa está en query > search
## List of 3
##  $ batchcomplete: chr ""
##  $ continue     :List of 2
##   ..$ sroffset: int 10
##   ..$ continue: chr "-||"
##  $ query        :List of 2
##   ..$ searchinfo:List of 1
##   .. ..$ totalhits: int 8945
##   ..$ search    :List of 10
##   .. ..$ :List of 7
##   .. ..$ :List of 7
##   .. ..$ :List of 7
##   .. ..$ :List of 7
##   .. ..$ :List of 7
##   .. ..$ :List of 7
##   .. ..$ :List of 7
##   .. ..$ :List of 7
##   .. ..$ :List of 7
##   .. ..$ :List of 7
resultados <- respuesta[["query"]][["search"]] # tomamos del objeto respuesta su objeto query, y luego su objeto "search"

La lista tiene dentro 1 lista por cada elemento (resultado). Como estas listas son iguales, podemos convertirlas a un formato tabla.

library(dplyr) # para transformar la lista vamos a usar dplyr
resultados_df <- dplyr::bind_rows(resultados) # vamos a separar las listas y unirlas en formato dataframe
glimpse(resultados_df) # veamos la tabla armada
## Rows: 10
## Columns: 7
## $ ns        <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
## $ title     <chr> "Macrodatos", "Big Cartoon DataBase", "Ética de los macrodat…
## $ pageid    <int> 5242736, 4124458, 10031789, 6822, 4102878, 8754307, 2648540,…
## $ size      <int> 114363, 2367, 12697, 82641, 11926, 2054, 8016, 3911, 2730, 3…
## $ wordcount <int> 13388, 216, 1771, 10554, 1122, 247, 588, 320, 209, 311
## $ snippet   <chr> "datos masivos, inteligencia de datos, datos a gran escala (…
## $ timestamp <chr> "2023-10-16T18:14:06Z", "2023-09-16T14:21:07Z", "2023-10-19T…

Ahora vamos a usar estos valores para recuperar los contenidos de las páginas.

Para esta acción la URL base es la misma que veniamos usando (https://es.wikipedia.org/w/api.php?) seguido de la acción que nos interesa realizar: “parse” porque le pedimos que imprima una pagina (&action=parse). Esta acción requiere un criterio (&page=) con el título que querramos recuperar, y finalmente, que especifiquemos qué en qué formatos (&list=search&format=json).

Veamos los títulos que podemos recuperar y compongamos la URL con los primeros.

resultados_df$title # veamos cuales son los primeros titulos
##  [1] "Macrodatos"              "Big Cartoon DataBase"   
##  [3] "Ética de los macrodatos" "Big Bang"               
##  [5] "Big Bear Lake"           "Tidy data"              
##  [7] "Río Big Sioux"           "Big Rapids"             
##  [9] "Big Pine"                "Big Bear City"
criterio2 <- str_replace(string = resultados_df$title[1], pattern = " ", replacement = "_")  # llamemos al primer elemento, reemplazando espacios por _ .... esto hay que mejorarlo para encodear otros elementos, como acentos

library(httr) # vamos usar este paquete para usar el protocolo HTTP
llamada2 <- httr::GET(paste0("https://es.wikipedia.org/w/api.php?action=parse&prop=text&formatversion=2&format=json&page=", criterio2)) # hacemos el request via HTTP/GET

llamada2$status_code # veamos si devuelve 200 == OK
## [1] 200
respuesta2 <- httr::content(x = llamada2) # httr nos lee el content del objeto que generamos

class(respuesta2) # chequeemos que es una lista
## [1] "list"
str(respuesta2, max=3) # veamos su estructura ... lo que nos interesa está en parse > text
## List of 1
##  $ parse:List of 3
##   ..$ title : chr "Macrodatos"
##   ..$ pageid: int 5242736
##   ..$ text  : chr "<div class=\"mw-parser-output\"><figure class=\"mw-halign-right\" typeof=\"mw:File/Thumb\"><a href=\"/wiki/Arch"| __truncated__
respuesta2[["parse"]][["text"]] %>% str_sub(start = 1, end = 500) # tomemos un fragmento
## [1] "<div class=\"mw-parser-output\"><figure class=\"mw-halign-right\" typeof=\"mw:File/Thumb\"><a href=\"/wiki/Archivo:Viegas-UserActivityonWikipedia.gif\" class=\"mw-file-description\"><img src=\"//upload.wikimedia.org/wikipedia/commons/thumb/6/69/Viegas-UserActivityonWikipedia.gif/250px-Viegas-UserActivityonWikipedia.gif\" decoding=\"async\" width=\"250\" height=\"188\" class=\"mw-file-element\" srcset=\"//upload.wikimedia.org/wikipedia/commons/thumb/6/69/Viegas-UserActivityonWikipedia.gif/375px-Viegas-UserActivityonW"

Lo que vemos es el código fuente (HTML) de esta página: https://es.wikipedia.org/wiki/Macrodatos

5.1.2 Una nota sobre la API de Facebook

Facebook impuso serias limitaciones a la información que es posible consultar via API.

Al igual que Twitter, Facebook requiere registrar una app. Pero, los tipos de apps que se sugieren y los permisos a otorgar, son indicativos del poco acceso que se puede obtener a los contenidos de la red social para investigación, dejando la API mayormente para interactuar y controlar campañas y negocios: https://developers.facebook.com/docs/development/create-an-app/app-dashboard/app-types.

Lo más cercano que hay al feed de contenido social publicado por los usuarios, se limita a las páginas de las empresas, para lo que hay que tener una cuenta de negocios asociada a la página, y además someterse a una revisión de la app. https://developers.facebook.com/docs/permissions/reference/pages_read_user_content/

En contrapartida, Facebook ofrece algunos datasets: https://research.fb.com/data/

Mas info: https://theconversation.com/facebooks-data-lockdown-is-a-disaster-for-academic-researchers-94533

Como resultado de estos cambios, uno de los paquetes más usados para interactuar con el API de Facebook para R (https://github.com/pablobarbera/Rfacebook) ya no se mantiene más…

5.2 Web Scraping

El proceso de recuperar contenido de manera automática a partir de páginas web se conoce como Web Scraping.

Para poder recuperar contenido de páginas web hay 2 requisitos: tenemos que averiguar:

  1. las URLs de las páginas que queremos leer: en muchos casos pueden ser URLs dinámicas, con parámetros y valores, que podemos manipular.
  2. la estructura HTML de la página donde está el contenido: como probablemente no nos interese todo el contenido de la página (logos,textos,botones) sino sólo algunos textos, tenemos que poder identificar en qué parte del template de la página se insertan los contenidos que nos interesa, para así poder mapearlos.

Ambas informaciones varían de sitio en sitio y de sistema en sistema. Para lo primero conviene entender lo básico de la sintáxis de las URLs: https://en.wikipedia.org/wiki/URL#Syntax; para lo segundo, comprender lo básico de HTML markup https://en.wikipedia.org/wiki/HTML#Markup, incluyendo como tomar los identificadores de un pedazo de sitio https://towardsdatascience.com/web-scraping-in-r-using-rvest-and-selectorgadget-5fc5124547e, algo para lo que el Dev.Tools del Chrome (F12 navegando la página) puede ser muy util.

5.2.1 Webscraping resultados de búqsqueda de OJS

Open Journal Systems (OJS) es un sistema de manejo editorial para revistas académicas. Es usado mayormente por universidades y por revistas que no tienen acuerdos editoriales con grandes empresas.

En este tutorial vamos a intentar recuperar los links a los artículos sobre “big data” que estén publicados en un listado de revistas que utilizan el sistema OJS:

sitios_ojs = c(
  "http://ojs.sociologia-alas.org/index.php/CyC/",
  #"http://ediciones.ucsh.cl/ojs/index.php/TSUCSH/", # se suele caer...
  #"https://revistachasqui.org/index.php/chasqui/", # tiene muchos resultados
  "http://revistamexicanadesociologia.unam.mx/index.php/rms/"
  )

Nuestro primer requisito es averiguar las URLs de las páginas que queremos leer.

Recordemos que queremos encontrar sólo artículos que contengan “big data”, de modo que necesitamos utilizar la función de búsqueda de cada OJS. Luego de realizar algunas búsqueda “a mano” y obsevar cómo el OJS construye sus URLs (https://docs.pkp.sfu.ca/dev/documentation/en/architecture-routes), podemos armar las de los resultados de búsqueda:

criterio <- "%22big+data%22"
url_scrapear <- paste( sitios_ojs , # vamos a contatenar pedazos de textos
                       "search/search?query=",
                       criterio,
                       sep = "")
url_scrapear
## [1] "http://ojs.sociologia-alas.org/index.php/CyC/search/search?query=%22big+data%22"            
## [2] "http://revistamexicanadesociologia.unam.mx/index.php/rms/search/search?query=%22big+data%22"

Ya tenemos listo el primer requisito: las URLs de las páginas a leer!

Nuestro segundo requisito es encontrar algún patrón para identificar el pedazo de contenido que nos interesa. En nuestro caso, se trata de los links a los artículos, de modo que, otra vez, tenemos que ver cómo se componen las URLs del OJS. Según su convención, los artículos en OJS tienen una URL con esta estructura: url_sitio + “/article/view/” + id_artículo. Con XPath, podemos escribir una expresión que filtre este tipo de link: //a[contains(@href, "/article/view/")].

Ya tenemos listo el primer requisito: el patrón para identificar el contenido!

Ahora vamos a podemos comenzar con el scraping. Aquí vamos a ayudarnos con el paquete rvest.

library(rvest)
## 
## Attaching package: 'rvest'
## The following object is masked from 'package:readr':
## 
##     guess_encoding
tomar_links_resultados <- function( url ) {
  url_con <- url(url, "rb") # abrimos una conexion para leer la pagina
  webpage <- xml2::read_html(url_con) # leemos la pagina
  close(url_con) # cerramos la conexion
  xpath <- '//a[contains(@href, "/article/view/")]' # XPath para distinguir links a artículos
  links <- rvest::html_nodes(webpage, xpath = xpath) %>% # leemos los pedazos de codigo de links
    html_attr('href') %>% # nos quedamos solo con la URL de los links
    return()
}

links_articulos <- character()

for (i in 1:length(url_scrapear)) { # vamos a ejecutar un bucle, donde i toma el valor del indice del array de URLs a visitar
  message("scrapeando ",url_scrapear[i]) # que nos muestre en pantalla por donde anda
  links_articulos <- c(links_articulos, tomar_links_resultados(url_scrapear[i]))
}
## scrapeando http://ojs.sociologia-alas.org/index.php/CyC/search/search?query=%22big+data%22
## scrapeando http://revistamexicanadesociologia.unam.mx/index.php/rms/search/search?query=%22big+data%22
links_articulos
## [1] "http://ojs.sociologia-alas.org/index.php/CyC/article/view/245"                    
## [2] "http://ojs.sociologia-alas.org/index.php/CyC/article/view/219"                    
## [3] "http://ojs.sociologia-alas.org/index.php/CyC/article/view/131"                    
## [4] "http://ojs.sociologia-alas.org/index.php/CyC/article/view/213"                    
## [5] "http://ojs.sociologia-alas.org/index.php/CyC/article/view/151"                    
## [6] "http://revistamexicanadesociologia.unam.mx/index.php/rms/article/view/57723"      
## [7] "http://revistamexicanadesociologia.unam.mx/index.php/rms/article/view/57723/51185"

Listo, tenemos los links de los artículos donde dice “big data” en varias revistas distintas. Ahora, seguro nos interese hacer un nuevo proceso de scraping para recuperar algunos metadatos de esos artículos. Pero para eso, mejor usemos el paquete ojsr.

library(ojsr)
## Warning: package 'ojsr' was built under R version 4.2.3
metadata <- ojsr::get_html_meta_from_article(input_url = links_articulos, verbose = TRUE)
## trying url 1/7 http://ojs.sociologia-alas.org/index.php/CyC/article/view/245
## scrapped http://ojs.soci ... found 53 elements using criteria .//meta
## trying url 2/7 http://ojs.sociologia-alas.org/index.php/CyC/article/view/219
## scrapped http://ojs.soci ... found 61 elements using criteria .//meta
## trying url 3/7 http://ojs.sociologia-alas.org/index.php/CyC/article/view/131
## scrapped http://ojs.soci ... found 53 elements using criteria .//meta
## trying url 4/7 http://ojs.sociologia-alas.org/index.php/CyC/article/view/213
## scrapped http://ojs.soci ... found 54 elements using criteria .//meta
## trying url 5/7 http://ojs.sociologia-alas.org/index.php/CyC/article/view/151
## scrapped http://ojs.soci ... found 52 elements using criteria .//meta
## trying url 6/7 http://revistamexicanadesociologia.unam.mx/index.php/rms/article/view/57723
## scrapped http://revistam ... found 51 elements using criteria .//meta
## trying url 7/7 http://revistamexicanadesociologia.unam.mx/index.php/rms/article/view/57723
## scrapped http://revistam ... found 51 elements using criteria .//meta
glimpse(metadata)
## Rows: 375
## Columns: 5
## $ input_url         <chr> "http://ojs.sociologia-alas.org/index.php/CyC/articl…
## $ meta_data_name    <chr> NA, "viewport", "generator", "DC.Creator.PersonalNam…
## $ meta_data_content <chr> NA, "width=device-width, initial-scale=1.0", "Open J…
## $ meta_data_scheme  <chr> NA, NA, NA, NA, NA, "ISO8601", "ISO8601", "ISO8601",…
## $ meta_data_xmllang <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, "en", "es", "pt"…
metadata %>% 
  filter(
      meta_data_name=="citation_keywords", 
      trimws(meta_data_content)!=""
    ) %>% # filtering keywords
  mutate(keyword = trimws(tolower(meta_data_content))) %>%
  count(keyword, sort = TRUE) 
##                                                                     keyword n
## 1    big data, epistemology, methodology, social sciences, social networks. 2
## 2  big data, epistemología, metodología, ciencias sociales, redes sociales. 2
## 3                                                       relaciones públicas 2
## 4                                                            automatización 1
## 5                                                civilización transcultural 1
## 6                                                              colonialidad 1
## 7                                                competencias profesionales 1
## 8                                                              comunicación 1
## 9                                                  comunicación estratégica 1
## 10                                                                 covid-19 1
## 11                                                            crisis raigal 1
## 12                                                      cultura del trabajo 1
## 13                                                          cyberdemocracia 1
## 14                                                        huellas digitales 1
## 15                                                         industria láctea 1
## 16                                                               modernidad 1
## 17                                                         perfil de egreso 1
## 18                                                       políticas públicas 1
## 19                                                             sindicalismo 1
## 20                                                    tecnologías digitales 1
## 21                                                                     vida 1

Además de recuperar metadatos del HTML de los OJS, el paquete permite:

?ojsr::get_issues_from_archive() # busca numeros de la revista
?ojsr::get_articles_from_issue() # busca artículos de un número de la revista
?ojsr::get_galleys_from_article() # toma las galeradas (los pdf, xml, epub, y demas)

5.3 Recursos

5.3.2 Repositorios de datasets:

5.3.3 Herrameintas de Scraping: