Webscraping con R - descargando noticias sobre petróleo

Publicado por Johan Rosa , 2019-11-06

En esta ocasión tengo la intención de darles un paseo breve por la primera parte de un proyecto que inicié con una compañera del trabajo. La idea es navegar por las noticias de ciertos portales como Reuters y descargar las noticias referentes a diferentes tópicos, como markets y business, al igual que descargar noticias de otros portales que contengan artículos sobre petróleo, conflictos geopolíticos, sobre la guerra comercial entre Estados Unidos y China, ect. Todo con la finalidad de construir un indicador de incertidumbre global y, con el sentimiento de los textos, determinar la fuente coyuntural de incertidumbre.

Como el primer paso para realizar el trabajo es obtener la data y para esto debí recurrir a una habilidad que todo científico de datos o investigador moderno debe cultivar, quise hacer una breve introducción al webscraping.

En este tutorial descargaremos las noticias relacionadas a energía y precios del petróleo contenidas en el portal Oilprice.com.

En general, este artículo simplemente detallará la manera en la que extraje la data y la parte del análisis la veremos en otra publicación.

Advierto que todo lo aquí presento puede que sea un poco denso para principiantes, la verdad diría que este tutorial es principalmente útil para personas que ya saben usar R con fluidez.

Definición y consideraciones generales

Webscraping es la técnica de extraer datos contenidos en un formato no estructurado en una página web y llevarlos a una estructura fácil de usar.

A diferencia de lo que se pueda pensar, hasta el hecho de navegar por una página web y anotar a mano o haciendo copy paste a los datos que sean de su interés puede ser llamado webscraping. Ahora bien, hacerlo de esta manera es poco eficiente ya que R y muchos otros software tienen librerías que te permiten hacerlo casi de forma automática, simplemente manejando algunos conceptos de HTML y CSS.

En R, el paquete que uso principalmente para hacer webscraping es rvest, desarrollado por el gran Hadley Wickham (Este pana es mi ídolo), el cual provee funciones para descargar los html de las páginas, identificar los nodos en los que está lo que queremos y llevar el contenido a texto, tablas, números, ect.

Prerequisitos

Antes de entrar de lleno al contenido que nos trae por aquí, debo advertirles que es importante aprender algo de HTML y CSS para dominar por completo esta técnica de recolección de datos. Yo tengo unos meses aprendiendo y en la medida en la que avanzo logro entender más rápido qué debo hacer para sacar las informaciones que necesito. De todas formas, en este ejemplo estaremos utilizando un complemento para navegadores que se llama Selector Gadget, que permite, haciendo clic en un elemento, encontrar el CSS selector a utilizar para extraer dicho elemento con las funciones de rvest.

En el enlace anterior pueden seguir los pasos para descargar e instalar el complemento, pero igual vayan entreteniéndose con esto que les ayudará a aprender un poco de HTML y CSS.

Cuando tengan el selector instalado, verán un el ícono de la lupa en la esquina superior derecha de su navegador y estarán listos para empezar.

Breve explicación de la estructura html

Debido a que no siempre el selector gadget será suficiente para completar las tareas, sería bueno que dedique unas líneas para explicar rápidamente la estructura de un documento html.

En una página web, todos los elementos visibles, incluso los generados dinámicamente, están etiquetados de alguna forma en el código fuente con la finalidad de organizar el contenido de la página. Acceder y explorar el código fuente es sencillo, con la herramienta de desarrollador de los navegadores.

La data html en sentido general está compuesta por marcadores y contenido. Los marcadores describen la estructura de la data, mientras que el contenido es la data en sí misma. La mayoría de los marcadores están en forma de tags, los cuales se pueden identificar en el código porque están contenidos entre símbolos de menor que y mayor que.

Un ejemplo de tag puede ser el siguiente, <tag> </tag> donde la primera parte abre el tag y la segunda lo cierra.

El contenido suele estar ubicado entre el tag de inicio y el tag de cierre, de esta manera <tag> Hola mundo </tag>.

Los tags pueden tener atributos, los cuales son información complementaria y, en ocasiones, distintivas de los marcadores (metadatos). los atributos pueden ser identificados rápidamente en la estructura HTML, pues vienen seguidos al tag y siempre tienen un nombre y un valor <tag atributo = valor> </tag>.

 # Forma General -------------------------------------------------------------------
   
    < Tag  atributo_1 = valor_1 atributo_2 = valor_2 ...>
        data del nodo
        <\Tag>
`        

No hay un formato general o estándar para la estructura html de una página web, en ocasiones hay datos que pueden venir en forma de atributos y otras veces pueden venir como un elementos dentro de otro elementos. Para ilustrar esto vemos el siguiente ejemplo, donde tenemos dos estructuras html distintas, al tiempo que contienen la misma información.

El primer ejemplo consiste de un tag class = estudiantes, que a su vez tiene dos elementos class = persona, cada elemento class = persona tiene como atributo la fecha de nacimiento.

El segundo ejemplo contiene por igual un tag class = estudiantes con elementos class = persona, que contienen a su vez dos tags, uno con el nombre y otro con la fecha de nacimiento.

# Ejemplos --------------------------------------------------------------------------

# Ejemplo 1 ----------

<div class = "estudiantes">
        <a class = "persona" birth_date = "1992-11-21">Johan Rosa <\a>
        <a class = "persona" birth_date = "1995-01-11">Fulano de Tal<\a>
</div>

# Ejemplo 2 ---------

<div class = "estudiantes">
        <a class = "persona">
          <nombre>Johan Rosa</nombre>
          <birth_date>1992-11-21</birth_date>
        <\a>
        <a class = "persona">
          <nombre>Fulano de Tal</nombre>
          <birth_date>1995-01-11</birth_date>
        <\a>
</div>

En ambos casos tenemos la misma información pero estructurada de forma distinta. Este tipo de cosas son las que hay que ver cuando exploramos el código fuente de una página web.

Breve explicación de los selectores CSS

CSS es un lenguaje que describe el estilo de un documento HTML, o bien, determina cómo los elementos html se van a mostrar a los usuarios.

El componente CSS de una página web permite que dado el tag, la clase, el id u otro atributo de un elemento html, asociar a este configuraciones especificas como el tipo de fuente, tamaño, color, ect.

Ahora bien, para asociar estas configuraciones a un elemento, es necesario utilizar un identificador o selector, debido a que los selectores declaran a qué tags o atributos se le aplicaran los estilos creados en el documento CSS.

Para poner un ejemplo de lo descrito, volvamos a los ejemplos de estructuras html que vimos en el acápite anterior e imaginemos que nos gustaría mostrar el nombre de la persona con un color y un tamaño diferente al que mostraremos la fecha de nacimiento. Para hacer eso necesitaría declarar el tamaño y color de cada uno de estos tags en un documento CSS y vincularlos a lo que existe en el HTML.

Ejemplo:

nombre {
  background-color: lightblue;
}

birth_date {
  color: white;
  text-align: center;
}

Lo importante del ejemplo anterior es que asociamos un formato a contenido, basados en tags. Pero también podría asociar formatos a contenido, basado en un atributos, como la clase. Para hacer eso simplemente debo poner el nombre de la clase precedido de un punto .persona{font-size: 20px;}.

Como ven, el selector CSS se llama así porque permite seleccionar elementos o tipos de elementos y asignarle una configuración.

En el caso del webscraping, nos servimos de los selectores CSS para, en vez de asignarle un formato a un elemento de la página web, descargar su contenido.

Aquí les doy una breve lista de como es la nomenclatura de los selectores dependiendo el atributo seleccionado para extraer la información

  • tag: el nombre del tag tag_name
  • class: un punto seguido del valor del atributo .class_name
  • ID: signo de número seguido del valor del atributo .id_name

Esto es lo más básico, pues los selectores se pueden combinar para extraer elementos de manera más específica, pero esa parte la dejo para que la amplíen por su cuenta.

Empecemos

Librerías

# para manejar contenido html y extraer información 
library(rvest) 
# manipular strings
library(stringr) 
# de quí usaremos el pipe (Tambipen podríamos usar library(magrittr))
library(dplyr) 
# para operaciones múltiples sin usar loops
library(purrr) 

1. Generando el URL de las páginas generales

Si entran a las noticias contenidas en la categoría de energía y petróleo o cualquiera de las otras en que hay en oilprice.com, accederán a una página en la que al fondo podrán ver el índice de ventanas con noticias a las que pueden acceder. Con este índice, cuándo cambiamos de una ventana a otra, el URL de la página simplemente varía al final, y varía indicando en qué ventana, de las disponibles, el usuario se encuentra en el momento.

Intenten cambiar de una en una, fíjense cómo varía el URL y una vez observada la lógica con la que se navega por la página creemos, emulando la variación observada, las diferentes url que podemos visitar.

paginas_generales <- paste0(
  "https://oilprice.com/Latest-Energy-News/World-News/Page-",
  1:1024,
  ".html")
head(paginas_generales)
## [1] "https://oilprice.com/Latest-Energy-News/World-News/Page-1.html"
## [2] "https://oilprice.com/Latest-Energy-News/World-News/Page-2.html"
## [3] "https://oilprice.com/Latest-Energy-News/World-News/Page-3.html"
## [4] "https://oilprice.com/Latest-Energy-News/World-News/Page-4.html"
## [5] "https://oilprice.com/Latest-Energy-News/World-News/Page-5.html"
## [6] "https://oilprice.com/Latest-Energy-News/World-News/Page-6.html"

2. Extraer el URL de las noticias individuales

Una vez que se cuenta con la colección de URL por las que vamos a navegar, necesitamos acceder a cada publicación contenida en ellas. Esta parte ya es un poco distinta, y se puede hacer de dos maneras:

  • Usando la herramienta de desarrollador del navegador
    1. Hacer clic derecho en la página y presionar opción inspect
    2. Hacer Ctrl + F en sidebar que salió y escribir un fragmento de alguno de los titulares
    3. Ver en el resultado en qué tag de la página está el url del anuncio


  • Usando el selector gadget
    1. Activar el selector en el ícono de la lupa
    2. Hacer clic sobre un titular
    3. copiar el css selector que aparece en la barra de herramientas

Con lo que ya sabemos sobre la estructura de un HTML podemos notar rápidamente que el url de la noticia está contendido en el nodo de clase class = categoryArticle y el url del artículo es un atributo del subnodo .categoryArticle, a de nombre href =.

Con esto tenemos información suficiente para crear una función que, recibiendo como argumento un url de las paginas_generales pueda extraer el url de las noticia individuales, pero antes quiero señalar la principales funciones del paquete rvest que estaremos utilizando y qué hace cada una.

Funciones útiles*

  • rvest::read_html para leer las páginas web
  • rvest::html_nodes para extraer nodos específicos teniendo un selector css o un xpath
  • rvest::html_attr para extraer atributos de uno o varios nodos
  • rvest::html_text para extraer el contenido de un nodo en forma de string


Función para extraer los url individuales

# Función para extraer los url de las niticias individuales
get_news_url <- function(url) {
  
  read_html(url) %>% #lee el html
    # Para concentramos en las noticas de cuerpo de la página
    html_nodes('#pagecontent .category') %>%
    # extrae el nodo con los artículos de cada página
    html_nodes('.categoryArticle__content, a') %>% 
    # extrae el atributo que contienen el url 
    html_attr('href') %>% 
    # elimina duplicados, ya que el url de cada noticia
    # está asociciado a varios elementos 
    unique() %>% 
    # excluye los url que no son de noticias (los url
    # que no terminan en .html)
    str_subset("html$") %>% 
    # excluye otros url que no interesan
    str_subset(fixed("Page-"), negate = TRUE)
}

Teniendo esta función simplemente hay que hacer un loop por todas las páginas generales y extraer el url de las noticias individuales.

# Para este ejemplo lo haré para las primeras 10 páginas generales
url_news <- map(paginas_generales[1:10], get_news_url)

# Como el resultado es una lista de lista, hacemos el unpack
url_news <- unlist(url_news)
head(url_news, 3)
## [1] "https://oilprice.com/Latest-Energy-News/World-News/The-Real-Reason-Big-Oil-Bailed-On-Brazil.html"     
## [2] "https://oilprice.com/Latest-Energy-News/World-News/The-Ethanol-Crisis-Has-Claimed-Another-Victim.html"
## [3] "https://oilprice.com/Latest-Energy-News/World-News/Anti-Fracking-Protests-Turn-Violent-In-The-UK.html"

3. Extraer el contenido de las noticias individuales

Ya que tenemos los URL de las noticias individuales, sólo resta extraer de cada página algunos elementos: el texto de la noticia, la fecha y el título. Finalmente tendríamos un dataframe, en el que cada fila corresponda a una noticia y estos elementos formen las columnas o campos.

Para realizar lo antes descrito, seguiremos aplicando functional programming, de manera que crearemos una función que extraiga el todo los elementos de interés de las noticias y evaluaremos dicha función con cada uno de los URL.

Primero vamos a identificar el css selector de cada elemento de la página de la noticia, pero esta vez lo haremos con el selector gadget. Para esto simplemente hacemos clic sobre el elemento que queremos extraer, obviamente después de activar el complemento, y copiamos el css selector que sale en la barra de herramientas.


Selector para el título Selector para el título

Selector para la fecha Selector para la fecha

Selector para el texto Selector para texto

Ya que tenemos el selector para cada uno de los elementos de las noticias, creemos la función que lo extraiga. La función tendrá una forma sencilla y una secuencia lógica: 1. Leemos el html 2. Se extrae el nodo identificado en los pasos anteriores 3. Extraemos el contenido de cada uno en forma de texto 4. Se crea un dataframe con estos elementos

Función para extraer el contenido de las noticias

get_news_text <- function(url) {

# Lee el html  
news_html <- read_html(url)

# Extrae el título 
header <- news_html %>%
  # selecciona el nodo con el título
  html_node("h1") %>% 
  # extrae el contenido del nodo
  html_text() 

# Extrae la fecha    
date <- news_html %>%
    html_node('.article_byline') %>% 
    html_text() %>%
    str_remove("^.*- ")  

# Para el texto agregamos unas cositas extra,
# aquí el texto viene separado por párrafos,
# de modo que se útil pegarlo todo junto.
# Para esto usa la función past0
text <- news_html %>%
  html_nodes("#news-content p") %>%
  html_text() %>%
  paste0(collapse = " ") %>%
  str_trim()

# Creo un dataframe con todos los elementos 
news <- data.frame(
  date = date,
  header = header,
  text = text,
  stringsAsFactors = FALSE
)

return(news)

}

Ya que tenemos la función, hagamos la descarga de las primeras 10 noticias. En este paso seguiremos el mismo enfoque que usamos para extraer los url de las noticias individuales.

# descargar los elementos de cada noticia
news <- map(url_news[1:10], get_news_text)

# Combinar las filas del data frame
news <- bind_rows(news)
# Ver las primeras observaciones
head(news, 2) %>%
  glimpse()
## Rows: 2
## Columns: 3
## $ date   <chr> "Nov 07, 2019, 2:30 PM CST", "Nov 07, 2019, 1:30 PM CST"
## $ header <chr> "The Real Reason Big Oil Bailed On Brazil", "The Ethanol Crisis~
## $ text   <chr> "This year’s final oil auction in Brazil ended in a flop on Thu~

Resumen y conclusión

El resultado final del ejercicio que realizamos fue un dataframe con una fila por cada noticia de la página oilprice.com, con campos que incluyen la fecha, el título y el texto de las noticias.

Durante el proceso se resaltaron varias cosas:

  • Definición de webscraping y sus ventajas
  • Una breve introducción al las estructuras HTML y a los selectores CSS
  • Explicación del workflow de un ejercicio de webscraping
  • Crear funciones propias que resuman los pasos necesarios para obtener lo que queremos de las páginas
  • Diseñar operaciones iterativas para extraer las noticias

Aún hay muchas cosas que quedan pendientes de explicación. El mundo del webscraping es muy amplio y hay elementos que no se cubrierorn aquí por el tipo de información que descargamos y el tipo de página web que exploramos. Pero hay páginas que son más complejas y el paquete rvest no es suficiente para manejarlas, obligandonos a utilizar automatizadores de navegadores como Selenium y paquetes más potentes como RSelenium.


comments powered by Disqus