27 de junio de 2020

Programas para analizar la evolución de las notas de IMDb con el tiempo


A continuación voy a explicar paso a paso cómo realicé el análisis sobre la evolución de las votaciones de las películas en IMDb que comenté en la anterior entrada del blog por si queréis realizar análisis similares.

La técnica que utilicé para realizar este análisis se denomina Web scraping y consiste en diseñar un programa para extraer información de páginas web y estructurarlo en una base de datos.



Instalación software

La herramienta principal que usé para ello es Puppeteer, una librería de node.js que permite automatizar acciones en Google Chrome. Por tanto, para que os resulte fácil seguir este tutorial os vendría bien ciertos conocimientos de programación con JavaScript. Si no es así voy a intentar ser lo más claro posible para que cualquiera con un mínimo interés por la informática lo pueda entender, pero si estáis interesados en aprender a programar os recomiendo los magníficos tutoriales de Píldoras Informáticas.

Para instalar Puppeteer descargamos Node.j con Node Package Manager (NPM), que nos permitirá instalar la librería con facilidad. Para ello, y con este tutorial en inglés a mano, seguimos lo siguientes pasos:

  1. Descargar en Windows el instalador desde la página web de Node.js
  2. Iniciar el instalador descargado (archivo .msi)
  3. Seguir las indicaciones del instalador asegurándonos que NPM está incluido.
  4. Reiniciar el ordenador

A continuación comprobamos que todo se ha instalado correctamente, para ello iremos a la consola de windows (simbolo de sistema) y teclearemos node -v. La máquina debería devolvernos la versión de node.js instalada (en mi caso v12.2.0). Para comprobar que también se instaló NPM teclearemos npm -v, lo que imprimirá en pantalla el número de versión 6.9.0 o posterior.

Una vez instaladas estas herramientas básicas creamos en nuestro ordenador la carpeta en la que trabajaremos, accedemos a ella a través de la consola (en mi caso cd FilmRatingEvolution) y pulsamos los siguientes comandos:

npm init -y  para genererar un paquete .json en la carpeta
npm install puppeteer para instalar puppeteer junto a Chromium (lleva un rato)

Con esto tendríamos todo lo necesario instalado. A partir de ahí ya podemos trabajar en el programa que nos permitirá recolectar los datos que queramos de Internet. Para iniciarnos utilizaré las bases de esta página web.



Sacando información de una web

Empezaremos creando un primer programa llamado notasimple.js que servirá para tomar la valoración en IMDB de una sola película en una fecha concreta tal y como la registra archive.org, y para ello utilizaremos el editor de código Sublime.

Para sacar el valor concreto de la nota se necesita el selector que la identifica en esa página. Para ello lo que haremos será inspeccionar el código de la página web siguiendo los siguientes pasos:
  1. Pulsar Ctrl + Mayúscula + I
  2. En la consola que se abre se pulsa al símbolo de la flecha que hay arriba a la izquierda
  3. Se recorre con el ratón la página web hasta situarse justo encima de la nota
  4. Al situarse sobre la nota y pulsar el botón izquierdo en la consola se señala dónde está el selector
  5. Con el ratón encima de la línea de código que se destacó en la consola se pulsa botón derecho/copiar/copiar selector.

Por tanto el selector para las nota de IMDb en Archive.org para el 28 de abril del 2019 sería: 

#title-overview-widget > div.vital > div.title_block > div > div.ratings_wrapper > div.imdbRating > div.ratingValue > strong > span

Con esto ya podríamos sacar la nota de una página web y guardarla en un archivo mediante el programa notasimple.js que tendrá la siguiente estructura:

// carga puppeteer:
const puppeteer = require('puppeteer'
// ejecuta el módulo:
void (async () =>
    try { // en caso de que no haya errores:
// crea un navegador virtual:
const browser = await puppeteer.launch() 
// crea una pestaña en el navegador:
const page = await browser.newPage()
        // evita el límite de 30000ms para que se cargue la página:
        await page.setDefaultNavigationTimeout(0);
/// página de la que se va a extraer el valor:
await page.goto('https://web.archive.org/web/20190427190042/https://www.imdb.com/title/tt1392170/')
// Espera a que se cargue el selector que nos interesa:
await page.waitForSelector("#title-overview-widget > div.vital > div.title_block > div > ...
div.ratings_wrapper > div.imdbRating > div.ratingValue > strong > span");
// guarda en la variable "rates" el dato de salida:
const rates = await page.evaluate(() =>
            // guarda el valor del selector en la variable "RATE_SELECTOR":
    const RATE_SELECTOR = "#title-overview-widget > div.vital > div.title_block > div > ...
div.ratings_wrapper > div.imdbRating > div.ratingValue > strong > span" 
    // guarda la nota en formato texto evitando espacios en blanco en la variable "MovieRating"::
    const MovieRating = document.querySelector(RATE_SELECTOR).innerText.trim() 
            //devuelve la variable definida como "MovieRagint" en la variable "rates":
            return MovieRating 
})
// muestra el valor devuelto:
console.log(JSON.stringify(rates, null, 2)) 
        //crea un archivo usando el módulo filesystem:
const fs = require('fs') 
fs.writeFile( // el archivo json se crea en la ubicación indicada con el siguiente nombre:
    './json/rates.json',

    // convierte a formato JSON la variable "rates":
    JSON.stringify(rates, null, 2),
            // indica si se ha escrito el archivo correctamente o no:
    (err) => err ? console.error('Data not written', err) : console.log('Data written'
)
//cierra el navegador:
        await browser.close()
    } catch (error) { // en caso de que haya errores
console.log(error) // escribe error
    }
})()

Para ejecutarlo hay que escribir node notasimple.js, que muestra el valor en pantalla y lo guarda en el archivo rates.json. Ese archivo se puede abrir con TextPad y guardarlo como .txt si lo consideras necesario.

Por tanto ya tenemos un programa que te saca la nota en un día concreto. Lo mismo podemos hacer para extraer el número de votos y el título de la película si añadimos sus respectivos selectores.

El problema de este programa es que solo te sacaría la nota para las fechas comprendidas entre el 23 de enero del 2016 y la fecha en la que estoy escribiendo estas líneas, ya que si modifica el diseño de la página estos selectores pueden cambiar. Como yo realicé el estudio de los últimos diez años tuve que buscar cuándo habían sido realizado cambios en el diseño durante ese periodo y encontré que las fechas en las que cambiaron los selectores fueron aproximadamente el 23/01/2016, 20/02/2013, 01/09/2011 y 10/10/2010. De esta manera, dependiendo del rango de fechas en el que uno quiera sacar la nota, se usarán unos selectores u otros.

Pero para automatizarlo queda un detalle, y es que el programa tiene que recorrer fecha a fecha entre los registros de archive.org, que no necesariamente registra los datos todos los días. A continuación explicaré cómo hacerlo.



Recolectar los registros temporales de una página web

Si se introduce una página web en la Wayback Machine de archive.org, esta muestra un calendario del año actual y coloreadas las fechas de las que existe registro de esa página. En la parte superior de éste se puede ver un histograma del número de registros que existen para cada mes y año hasta 1996 y justo encima el número de registros entre la primera fecha en la que se guardó la web hasta la última. 


Como lo que se quiere es analizar todas las notas registradas en la web durante la última década lo primero que hay que hacer es sacar cual es la primera fecha en la que existen registros y a partir de ahí ir año a año hasta la actualidad. Así que el primer paso consiste en sacar esa fecha con el programa 0.FirstDate.js, que tiene exactamente la misma estructura que notasimple.js pero modificando el selector y añadiendo un línea de código que permite extraer el año del selector de archive.org que tiene el formato Mes Día, Año.
     const year=firstYear.slice(firstYear.length - 4, firstYear.length)
El programa guarda ese año en un archivo llamado FirstData.json ¿Y por qué solo extrae el año cuando nos interesa la fecha exacta del primer registro? Porque para recorrer todos los años en archive.org es necesario ir modificando la dirección de la página añadiendo el año de la siguiente manera:
https://web.archive.org/web/2013*/https://www.imdb.com/title/tt2911666/
https://web.archive.org/web/2014*/https://www.imdb.com/title/tt2911666/
...
https://web.archive.org/web/2020*/https://www.imdb.com/title/tt2911666/
y una vez en la página de cada año, con el siguiente selector 

#react-wayback-search > div.calendar-layout > div.calendar-grid div > div > a

se extraen todos los enlaces que llevan a todos los días registrados, que presentan el siguiente formato: 
https://web.archive.org/web/20130604083143/http://www.imdb.com/title/tt2911666/
donde la parte resaltada se corresponde con el año, el mes, el día, la hora, el minuto y el segundo registrado (08:31:43 del 04 de Junio del 2013 en el ejemplo).

Todo este proceso se realiza con el programa 1.LinksDays.js, que tiene la siguiente estructura:

// cargar en puppeteer:
const puppeteer = require('puppeteer')
// ejecutar el módulo:
void (async () =>
    //crea un archivo usando el módulo filesystem:
    var fs = require('fs');
    // guarda en "ReleaseYear" el primer año registrado en el archivo FirstData.json:
    var ReleaseYear = JSON.parse(fs.readFileSync('Datos_Notas/FirstData.json', 'utf8'));
    // define el año de la última fecha que tendremos en cuenta
    i=2020
    // crea una variable en la que guardaremos los enlaces de todos los días registrados:
    var AllLinks = []
    try { // en caso de que no haya errores
//mientras el año "i" sea igual o posterior al primer año de registro y no anterior a 2010 entra en el bucle
while (i>=ReleaseYear&&i>=2010) { 
    // crea un navegador:
    const browser = await puppeteer.launch() 
    // crea una pestaña en el navegador:
    const page = await browser.newPage()
    // define la parte inicial de la página web:
    var header = 'https://web.archive.org/web/'
    // define la parte final de la página web:
    var ending = '*/https://www.imdb.com/title/tt1598778/'
    // define la página web: 
    var WebSite=header+i+ending
    // evita el límite de 30000ms para que se cargue la página:
    await page.setDefaultNavigationTimeout(0);
    // vamos al calendario de un año del que se van a extraer las fechas en las que hay registro:
    await page.goto(WebSite) 
    // espera a que se cargue el selector que nos interesa:
    await page.waitForSelector("#react-wayback-search > div.calendar-layout > div.calendar-grid ...
                    div > div > a");
    // guarda en la variable "links" el dato de salida del año "i":
    const links = await page.evaluate(() =>
        // guarda en la variable "DataDate" el valor del selector correspondiente a un día registrado:
const DataDate = "#react-wayback-search > div.calendar-layout > div.calendar-grid div > div > a"
// guarda en la lista "Dates" todos los "DataDate" que encuentra en la página web
const Dates = document.querySelectorAll(DataDate) 
// define el vector de salida "Linkdates"
const Linkdates = [] 
// recorre todos los registros de la lista "Dates" con la variable "day"
for (const day of Dates) {
    // introduce en la variable "Linkdates" cada enlace registrado la variable "day"
    Linkdates.push({
        link: day.getAttribute("href"),
    })
}
//devuelve el vector definido como "Linkdates" en la variable "links":
return Linkdates 
    })
    // añade a "AllLinks" los enlaces registrados en la variable "links" para el año "i":
    AllLinks=AllLinks.concat(links)
    // resta 1 al año del que extraeremos datos:
    i--
    //cierra el navegador:
    await browser.close() 
        }
// muestra el valor devuelto
console.log(JSON.stringify(AllLinks, null, 2))
        //crea un archivo usando el módulo filesystem: 
const fs = require('fs')
fs.writeFile( // el archivo json se crea en la ubicación indicada con el siguiente nombre:
    './Datos_Notas/alldates.json',
    // convierte a formato JSON la variable "AllLinks":
    JSON.stringify(AllLinks, null, 2), 
    // indica si se ha escrito el archivo correctamente o no: 
    (err) => err ? console.error('Data not written', err) : console.log('Data written')
)
    } catch (error) { // en caso de que haya errores
console.log(error) // escribe error
    }
})()

Por tanto con este programa ya tendríamos un archivo llamado alldates.json que contendría un enlace por cada día registrado en la Wayback Machine durante el periodo 2010-2020. Esos enlaces se pueden meter en el programa notasimple.js e ir sacando las notas de IMDb para una película para cada día. Para no tenerlo que hacer uno se programa 2.ExtractRates.js, que automatiza lo que hacía notasimple.js añadiendo los siguientes cambios:
  • Lee los enlaces del archivo alldates.json
  • Evita que se cargue toda la página para no ralentizar la captura de datos
  • Extrae el título de la película, la nota, número de voto y día que se registra cada dato
  • Aplica unos selectores u otros en función del diseño de IMDb (ya comenté anteriormente que en los últimos 10 años lo han modificado en cuatro ocasiones).
  • Calcula cuantos días han transcurrido entre cada registro tomado y el primero disponible (en los primeros registros no habrá nota porque suelen realizarse meses o años antes de que se estrene la película)
Este programa, al ser bastante extenso para publicarlo en el blog, lo tenéis disponible, al igual que los dos anteriores,en mi cuenta de GitHub.



Cómo analizar la evolución de las notas de IMDb 

Con los tres programas explicados en los apartados anteriores ya se pueden sacar las notas de IMDb a lo largo del tiempo para una película. Los pasos que hay que seguir son:
  1. Id a IMDb y buscar la película que os interesa
  2. Copiad el enlace de dicha película
  3. En los programas 0.FirstDate.js y 1.LinksDays.js modificad el enlace de la película
  4. Ejecutad 0.FirstDate.js, que crea una archivo llamado FirstData.json
  5. Ejecutad 1.LinksDays.js, que crea un archivo llamado alldates.json
  6. Ejecutad 2.ExtractRates que crea un archivo llamado allRates.json
Ese último archivo tiene el siguiente formato:

Título,      Nota,      Número de votos,      Fecha, Días trascurridos 

Para leer el archivo en excel y realizar las gráficas que pudisteis ver en mi análisis, podéis abrirlo con TextPad, eliminar los saltos de línea reemplazando ,\n y al abrir en excel delimitar por " y recordad que los decimales están con punto y los miles con comas.

Los programas podrían mejorarse o automatizarse todavía más extrayendo los códigos de las películas directamente de la web de IMDb, introduciendo los títulos a través de un input, unificando los tres programas o incluso diseñando una página de este estilo. Son mejoras que igual implemento más adelante.

Por último os dejo una serie de enlaces que os pueden resultar útiles para entender toda esta parte de programación que he explicado:

Con esto doy por terminado este especial sobre las notas de IMDb y continuaré con el repaso al cine de la última década.



No hay comentarios:

Publicar un comentario