Cómo elegir un MOOC usando Data Science. Parte I: Web Scraping

Luis Ramirez
Ciencia y Datos
Published in
8 min readSep 5, 2018

Uno de los temas que más me apasiona es la educación, campo en el que los cursos online masivos juegan un rol importante, sobre todo porque representan un cambio de paradigma en el acceso a la educación de calidad y a bajo costo. Desde que comenzaron en el 2011 hasta hoy, la oferta se ha incrementado exponencialmente y la cantidad de cursos disponibles es mucho mayor a la que cualquiera podría completar, lo que hace que en muchos casos debamos elegir entre decenas o hasta cientos de alternativas similares.

Afortunadamente, para muchos de los cursos podemos conseguir en línea, reviews de otros estudiantes que resultan útiles para evaluar su calidad y contenido. Ahora bien, un curso puede tener decenas de reviews y leerlos todos tampoco resulta ser una tarea factible, lo que hace un poco más difícil escoger el MOOC más acertado a realizar.

Para facilitar esta tarea y no tener que hacer el proceso de revisión manualmente, lo abordaremos como un problema de data science. El propósito de este artículo es cubrir la primera etapa que comprende la creación de nuestro dataset, para lo que haremos web scraping del sitio course talk, una de las plataformas con mayor cantidad de información disponible sobre los moocs incluyendo la descripción del curso, su proveedor y los reviews de los usuarios.

Una vez definido el objetivo, el siguiente paso es evaluar la dificultad del problema y elegir la herramienta más adecuada para resolverlo.

En primer lugar debe estimarse la cantidad de información a ser recolectada, para este proyecto tomaremos los cursos de las diez categorías más populares, lo que cubre cerca de 30 mil cursos. Como punto de partida, tomaremos un estimado de 50 mil páginas a visitar, es decir, cerca de dos páginas por curso.

En segundo lugar, es necesario verificar que el contenido de interés sea fácilmente accesible, esto es, que no requiera solicitudes adicionales del lado del cliente. Para comprobarlo podemos simplemente deshabilitar Java Script (JS) en el navegador y ver si el contenido aún es visible.

Una alternativa a esto es usar scrapy shell.

Con esta información podemos elegir la herramienta que mejor se adapte a la tarea. En Python, por ejemplo, las principales librerías para realizar web scraping son: Scrapy, Selenium y Beautiful Soup. Beautiful Soup es la más sencilla para comenzar, sin embargo, Scrapy es más completa y es una mejor opción si piensas desarrollar más de un proyecto. Por otro lado, Selenium sólo es recomendable para sitios que dependan de JS y este no es el caso.

En este proyecto usaremos scrapy, debido a que el número de solicitudes es significativo y la página no cuenta con contenido dinámico (JS).

Una de las principales ventajas que posee Scrapy, es que gracias a su diseño modular permite crear un proyecto sencillo e irlo mejorando sin realizar modificaciones importantes a las partes previas.

FASE 1. CREACIÓN DEL PROYECTO

La manera estándar de comenzar a trabajar con Scrapy es creando un proyecto, lo cual se hace con el comando startproject :

scrapy startproject moocs 

Así, se creará la carpeta del proyecto con la siguiente estructura:

La carpeta spiders por defecto no cuenta con ningún archivo, el siguiente paso es crear la araña que se encargará de recorrer la página y extraer la información. Para esto, usamos el comando genspider seguido del nombre de la araña y de la página de donde comenzaremos a navegar:

scrapy genspider MoocsSpiderSpider "https://www.coursetalk.com/subjects/business/courses"

FASE 2. REVISIÓN DE LOS DATOS

Este es un proceso de prueba y error en el que usaremos scrapy en un Jupyter Notebook, para hacerlo deben importarse los siguientes módulos:

import requests
from scrapy.http import TextResponse
from urlparse import urljoin
import re
from scrapy.loader.processors import Compose, MapCompose, Join, TakeFirst

Ahora, comenzaremos con una de las páginas que listan los cursos por categoría: https://www.coursetalk.com/subjects/engineering/courses. De ella debemos extraer el enlace donde se encuentra la descripción detallada de cada uno de los cursos. Para cargar la página en python usamos request y la convertimos en un objeto de Scrapy con TextResponse():

r = requests.get(‘https://www.coursetalk.com/subjects/engineering/courses')
response = TextResponse(r.url, body=r.text, encoding=’utf-8')

Una vez hecho esto, el contenido de la página estará disponible en la variable response.body:

Para extraer la información de interés del codigo html, usaremos los xpaths, los cuales podemos obtener haciendo click derecho en el navegador y seleccionando la opción inspect.

Una alternativa a los Xpath es usar los selectores de css

De esta manera obtenemos el xpath @class=”course-listing-card” con el que comenzaremos a experimentar.

En este caso nos interesa extraer las url por lo que agregaremos el atributo @href al xpath anterior

Ahora obtenemos todas las urls a las que hace referencia el bloque “course-listing-card” así que seguimos refinando la búsqueda. Como se observa en la imagen, las urls de los cursos contienen el texto “/courses/” de manera que lo agregaremos como condición:

response.xpath(‘//*[@class=”course-listing-card”]//a[contains(@href, “/courses/”)]/@href’).extract()

Un proceso análogo se seguirá para el resto de campos de interés: Título, instructores, descripción, conceptos clave, proveedor, costo, universidad y review. Estos campos corresponden a los ítems en scrapy que deben ser agregados al archivo items.py

En https://devhints.io/xpath puedes encontrar un excelente resumen de Xpaths para extraer la información de los campos restantes.

FASE 3. PROCESAMIENTO DE LA INFORMACIÓN.

En la clase MoocsItem, que es creada por defecto, agregamos el nombre de los ítems que usaremos, por ejemplo course_title, la manera más sencilla es simplemente instanciar objetos de la clase scrapy.Field:

Sin embargo, es común que en la información extraída en esta etapa los datos estén en un formato inconsistente o problemático. Por ejemplo, la descripción para uno de los cursos es:

In this mathematics course, we will examine optimization through a Business Analytics lens. You will be introduced to the to the theory, algorithms, and applications of optimization.

El problema con este campo es la puntuación, ya que las comas son los separadores en los archivos CSV y si el texto los contiene, las celdas se desplazan. Para evitarlo y preservar la mayor cantidad de información posible en esta etapa, encerramos el texto entre comillas triples, de esta manera los separadores son ignorados dentro del texto. Es justo para conseguir esto que haremos uso de los ítem loaders, que nos permiten al instanciar los ítems, asignarles una función.

De esta manera la función quote_field, que se encargará de colocar el texto entre comillas, será aplicada automáticamente a todos los ítems que indiquemos al instanciarlos. Otra diferencia, es que estas funciones deben ser definidas en el archivo items.py en lugar de hacerlo en la araña directamente, lo que también ayuda a mantener el código más ordenado.

Así, en la araña moocs_spider.py simplemente debe indicarse, para cada ítem, su nombre y correspondiente xpath, ya que la aplicación de las funciones es manejada automáticamente por los items loaders:

FASE 4. ALMACENAMIENTO DE LA INFORMACIÓN

Ahora que sabemos cómo extraer la información, el siguiente paso es guardarla; para esto tenemos varios opciones.

En este punto podemos correr el script con el comando crawl más el nombre de la araña y el programa se ejecutara correctamente:

scrapy crawl Moocs_spider

Adicionalmente puede indicarse el nombre y el formato del archivo de salida como parámetro:

scrapy crawl Moocs_spider -o output.csv

Esto creará un archivo con todos los ítems extraídos. Ahora, ¿qué ocurre si queremos separar la información en más de un archivo o validar algunos campos antes de guardar los registros? Para esos casos podemos hacer uso de los pipelines.

Quick question ¿Por qué usar pipelines?

Para separar la descripción de los cursos de sus reviews. Como la mayoría de los cursos cuentan con más de un review, para los cuales los detalles del curso se mantienen, guardarlos en tablas diferentes ayuda a evitar datos duplicados y facilita su posterior análisis.

Ahora debemos realizar tres modificaciones al script actual:

  1. Guardar la información de los reviews y los detalles del curso en objetos diferentes, para lo que simplemente creamos 2 instancias de item loader:
l = ItemLoader(item=MoocsItem(), response=response)
r = ItemLoader(item=MoocsReviewItem(), response=response, selector=review)

2. Habilitar el uso de pipelines en settings.py

Este paso, al igual que el resto de las modificaciones en settings.py consiste en simplemente agregar una línea indicando la clase donde se definirán las reglas del pipeline, en este caso, MultiCSVItemPipeline

ITEM_PIPELINES = {‘moocs.pipelines.MultiCSVItemPipeline’: 300,}

3. Definir el pipeline.

Aquí se realizarán modificaciones sobre el ejemplo de la documentación de scrapy https://doc.scrapy.org/en/0.14/topics/exporters.html, principalmente indicando el nombre, formato y ubicación de los archivos de que serán creados para almacenar los datos.

Ahora, al correr el script:

scrapy crawl Moocs_spider

Los archivos de salida moocs.csv y moocsreviews.csv serán creados automáticamente.

AJUSTES FINALES

De esta forma ya tenemos un proyecto funcional, si lo ejecutamos extraerá la información deseada y la guardará en los archivos correspondientes. Sin embargo, aún hay aspectos por mejorar; Uno de los más importantes es evitar que bloqueen la araña de la página debido al inusual número de solicitudes.

El primer paso para evitarlo es limitar la velocidad de las solicitudes. En scrapy esto puede hacerse modificando la variable DOWNLOAD_DELAY, de formar tal que si definimos por ejemplo:

DOWNLOAD_DELAY=3

Se realizará como máximo una solicitud cada 3 segundos.

Aunque limitar el número de solicitudes es una buena práctica, para la mayoría de los casos no será suficiente.

El próximo paso es utilizar un proxy; en este proyecto utilicé el servicio webshare.io que ofrece un plan gratuito con un ancho de banda de 1GB mensual. El usuario y la contraseña que serán necesarios para configurar el proxy en scrapy están disponibles en la configuración como se muestra a continuación:

Si bien existen listas de proxys gratuitos como https://free-proxy-list.net/, estos suelen estar activos por periodos cortos, pudiendo generar más problemas con la conexión de los que resuelven.

Una vez que dispongas de las credenciales, configurar el proxy en scrapy requerirá dos pasos:

  • Descomentar la variable custom_settings en settings.py:
custom_settings = { ‘DOWNLOADER_MIDDLEWARES’: { ‘jobs.middlewares.ProxyMiddleware’: 100 } }
  • Crear un archivo llamado middlewaers.py, donde se especificarán los datos de la cuenta:
class ProxyMiddleware(object): # overwrite process request def process_request(self, request, spider): # Set the location of the proxy request.meta[‘proxy’] = “http://sarnencj:CONTRASEÑA@proxyserver.webshare.io:3128"

Eso es todo, si se realizaron los pasos correctamente, el log de scrapy debería ser como el siguiente:

Lo que indica que la solicitud se está realizando a través del proxy.

En este punto ya tenemos un script bastante robusto para realizar web scraping. En la segunda parte del artículo revisaremos los resultados obtenidos y se realizarán modificaciones al código de esta parte de ser necesarias. . Por ahora, el código completo de este proyecto está disponible en: https://github.com/Luis-ramirez-r/moocs_reviews_scraping/tree/master/moocs

--

--

Luis Ramirez
Ciencia y Datos

Ingeniero electricista con dos años de experiencia en procesamiento de datos.