Saltar al contenido principal

Web scraping con R y PhantomJS

Tutorial breve sobre cómo extraer datos generados por Javascript con R usando PhantomJS.
· 5 min leer

Explora con IA

Cuando necesitas hacer web scraping, lo normal es utilizar el paquete rvest de Hadley Wickham. Este paquete ofrece una solución sencilla y directa para obtener el código HTML que genera una página web. Sin embargo, cuando el sitio o la página utiliza JavaScript para mostrar los datos que te interesan, al paquete rvest le falta la funcionalidad necesaria. Una solución es usar PhantomJS.

(¿Quieres practicar la importación de más datos en R? Prueba este tutorial.)

Carga los paquetes necesarios

Como primer paso, tendrás que cargar en el espacio de trabajo todos los paquetes que necesitamos para este análisis (si aún no los has instalado en tu sistema, usa install.packages() para tenerlos disponibles):

library(rvest)
library(stringr)
library(plyr)
library(dplyr)
library(ggvis)
library(knitr)
options(digits = 4)

Scraping de datos generados por Javascript con R

El siguiente paso es recopilar los datos de TechStars usando PhantomJS. Echa un vistazo al siguiente archivo .js básico:

// scrape_techstars.js

var webPage = require('webpage');
var page = webPage.create();

var fs = require('fs');
var path = 'techstars.html'

page.open('http://www.techstars.com/companies/stats/', function (status) {
  var content = page.content;
  fs.write(path,content,'w')
  phantom.exit();
});

Básicamente, el script renderiza la página HTML después de que el código javascript subyacente haya hecho su trabajo, lo que te permite obtener la página HTML con todas sus tablas. Para mantenernos en R durante el resto del análisis, te sugerimos usar la función system() para invocar PhantomJS (tendrás que descargar e instalar PhantomJS y colocarlo en tu directorio de trabajo):

# Deja que PhantomJS haga scraping de TechStars; la salida se guarda en techstars.html
system("./phantomjs scrape_techstars.js")

Tras este pequeño rodeo, por fin tienes un archivo HTML, techstars.html, en tu sistema local, que puede extraerse con rvest. Una inspección de la página de Techstars revela que las tablas que nos interesan están en divs con la clase CSS batch:

batches <- html("techstars.html") %>%
  html_nodes(".batch")

class(batches)
[1] "XMLNodeSet"

Ahora tienes una lista de objetos XMLNodeSet: cada objeto contiene los datos de un batch de TechStars. Ahí podemos encontrar información sobre la ubicación del batch, el año, la estación, y también sobre las empresas, su sede actual, su estado actual y la financiación total que han conseguido. No entraremos en detalle en los pasos de recopilación y limpieza de datos que verás a continuación; puedes ejecutar el código y revisar qué hace. Verás que se aplica una limpieza a medida para que cada fragmento de información quede bien formateado:

batch_titles <- batches %>%
  html_nodes(".batch_class") %>%
  html_text()

batch_season <- str_extract(batch_titles, "(Fall|Spring|Winter|Summer)")
batch_year <- str_extract(batch_titles, "([[:digit:]]{4})")
# la ubicación es todo lo del título del batch que no es ni el año ni la estación
batch_location <- sub("\\s+$", "",
                      sub("([[:digit:]]{4})", "",
                          sub("(Fall|Spring|Winter|Summer)","",batch_titles)))

# crea un data frame con la información del batch
batch_info <- data.frame(location = batch_location,
                         year = batch_year,
                         season = batch_season)

breakdown <- lapply(batches, function(x) {
  company_info <- x %>% html_nodes(".parent")
  companies_single_batch <- lapply(company_info, function(y){
    as.list(gsub("\\[\\+\\]\\[\\-\\]\\s", "", y %>%
       html_nodes("td") %>%
       html_text()))
  })
  df <- data.frame(matrix(unlist(companies_single_batch),
                   nrow=length(companies_single_batch),
                   byrow=T,
                   dimnames = list(NULL, c("company","funding","status","hq"))))
  return(df)
})

# añade la información del batch a breakdown
batch_info_extended <- batch_info[rep(seq_len(nrow(batch_info)),
                                  sapply(breakdown, nrow)),]
breakdown_merged <- rbind.fill(breakdown)

# fusiona toda la información
techstars <- tbl_df(cbind(breakdown_merged, batch_info_extended)) %>%
  mutate(funding = as.numeric(gsub(",","",gsub("\\$","",funding))))

Con una combinación de funciones base de R y de rvest, plyr y dplyr, ahora tenemos el data frame techstars; un conjunto de datos de todas las empresas de TechStars, con toda la información pública bien formateada:

techstars
## Source: local data frame [535 x 7]
##
##          company funding   status                hq location year season
## 1    Accountable  110000   Active    Fort Worth, TX   Austin 2013   Fall
## 2          Atlas 1180000   Active        Austin, TX   Austin 2013   Fall
## 3        Embrace  110000   Failed        Austin, TX   Austin 2013   Fall
## 4  Filament Labs 1490000   Active        Austin, TX   Austin 2013   Fall
## 5        Fosbury  300000   Active        Austin, TX   Austin 2013   Fall
## 6          Gone!  840000   Active San Francisco, CA   Austin 2013   Fall
## 7     MarketVibe  110000 Acquired        Austin, TX   Austin 2013   Fall
## 8           Plum 1630000   Active        Austin, TX   Austin 2013   Fall
## 9  ProtoExchange  110000   Active        Austin, TX   Austin 2013   Fall
## 10       Testlio 1020000   Active        Austin, TX   Austin 2013   Fall
## ..           ...     ...      ...               ...      ...  ...    ...
names(techstars)
## [1] "company"  "funding"  "status"   "hq"       "location" "year"
## [7] "season"

Conclusión

Usando la combinación anterior de herramientas y código, conseguimos extraer datos de un sitio web que utiliza JavaScript para generar su contenido. Como ves, es un proceso muy estructurado que puede repetirse fácilmente una vez que tienes el código inicial. ¿Quieres aprender R y buscas tutoriales y cursos de ciencia de datos y R? Echa un vistazo a DataCamp

Temas

Relacionado

Tutorial

Tutorial sobre cómo importar datos a R