El día de hoy quiero hablarles sobre un pequeño proyecto que comencé hace ya unos cuantos meses con la idea de analizar lo que el entonces presidente de México en sus conferencias diarias. El objetivo de este proyecto era recolectar las transcripciones disponibles en el sitio web del gobierno, en esta sesión en vivo de YouTube les muestro cómo hice parte de este proyecto.
⚠️ Esta es una solución rápida sin mucho pensamiento detrás ni muy buenas prácticas de desarrollo de software, y solamente lo presento como algo interesante que es un buen punto de inicio para tus proyectos.
1. Python – Web Scraping
Obteniendo las URLs
Cada una de las transcripciones existe en urls individuales, el primer paso es obtenerlas de la página de la Presidencia de México, la dirección URL es , toda la magia de este código sucede en el archivo mananeras/dataset/download_urls.py. A resumidas cuentas, dentro de ese archivo tenemos una función llamada get_new_urls
, que a resumidas cuentas:
- Abre un archivo (llamado
urls.txt
) en donde previamente almacenamos las urls que ya descargamos (paso crucial si no queremos visitar una misma URL dos veces) - Consulta página por página el sitio del gobierno (
https://www.gob.mx/presidencia/es/archivo/articulos
) para encontrar nuevas URLs a transcripciones que no tengamos - Almacena las nuevas URLs que no hemos visitado en el archivo
urls.txt
- Regresa una lista con estas nuevas URLs
Descargando las páginas web
El siguiente paso es recuperar las páginas en HTML de cada una de las nuevas URLs recuperadas en el paso anterior, esto sucede dentro de la función download_articles
dentro del archivo mananeras/dataset/download_articles.py, en la definición de esta función no hay mucha ciencia, lo único que hacemos es utilizar urlretrieve
para descargar al disco local cada una de las páginas deseadas
Extrayendo la información del HTML
Hasta este paso ya tenemos la información en forma “cruda”, el siguiente paso es utilizar un parser de HTML (como BeautifulSoup en Python) para pasar del formato HTML a otro que sea más amigable de trabajar. Esto se hace en la función extract
dentro del archivo mananeras/dataset/extract_dialogs.py. Este archivo es tal vez la parte más compleja y frágil del código en Python y es que en donde leemos las etiquetas y conseguimos la información sobre los diálogos y hablantes en cada conferencia. Al terminar de ejecutarse, la función extract
generará un archivo txt
por cada html
que tengamos.
Publicando a Kaggle
Como yo cargo este dataset a Kaggle es necesario comprimir la información en un archivo zip
, esto se hace con una sola línea de código en Python: shutil.make_archive("data/articulos", "zip", "./articulos")
.
Una vez comprimido uso la biblioteca que la misma gente de Kaggle publica para crear una nueva versión de mi dataset usando el método dataset_create_version
.
api.dataset_create_version("data", "Daily dataset update", dir_mode="zip", quiet=False)
Tras estas líneas se ha creado una nueva versión en Kaggle, el siguiente paso es automatizar todo este proceso, y aún mejor, agendarlo para que suceda una vez al día desde nuestro CI de Gitlab.
2. Configurando el web scraping desde Gitlab
Lo primero que vamos a definir es el popular archivo .gitlab-ci.yml
en donde existe la configuración de nuestro pipeline, voy a mostrar sección y al final les mostraré todo el archivo:
Imagen base
La primera cosa que especifico es la imagen base que el runner de CI debe usar, yo me decidí por una de Python 3.9
image: python:3.9
Dos etapas
Como buen desarrollador de software, también he incluido unas pruebas, es por eso que el pipeline consta de dos etapas: una para realizar algunas pruebas, y la otra, más importante, en donde ejecuto el código de Python descrito.
stages:
- test
- create-datasset
Preparación
Con la llave before_script
lo que hacemos es especificar un conjunto de acciones que se deben llevar a cabo dentro de nuestra imagen python:3.9
antes de que se ejecute cualquiera de nuestras instrucciones, a detalle, lo que hacemos es:
- Configurar git en el runner, específicamente quiero ponerle un correo y un nombre de usuario para que los commits que haga tengan esta información
- Instalar las dependencias necesarias para que mi código funciona, en mi caso, estoy usando Poetry.
before_script:
- git config --global user.email "runner@gitlab.com"
- git config --global user.name "Antonio Feregrino"
- pip install poetry
- poetry config virtualenvs.create false --local
- poetry install
Ejecuta pruebas
El siguiente paso es ejecutar las pruebas usando pytest. Este paso es muy sencillo, simplemente ejecutamos pytest tests/
.
test:
stage: test
script:
- pytest tests/
Ejecuta el web scraping
Ahora sí, la parte más interesante, ejecutar el web scraping. Lo primero es que yo quiero ejecutar este paso únicamente cuando una ejecución ha sido agendada, es por eso que especifico una regla de ejecución en rules
; Lo segundo es el conjunto de scripts que se deben ejecutar:
- Primero ejecutamos el módulo
mananeras
que justamente se encarga de coordinar todo el código de Python que expliqué anteriormente; este código modifica archivos dentro de la carpetaarticulos
y el archivourl.txt
. - Usamos
git add
para agregar estos dos archivos modificados al árbol de los cambios. - Usamos
git commit
para confirmar los cambios a los archivos que acabamos de agregar. - Usamos
git push
para cargar nuestros nuevos cambios al repositorio remoto de GitLab (sí, estamos haciendo un nuevo commit a Gitlab desde Gitlab)
De este modo nos aseguramos que los archivos que usamos para almacenar las urls que ya visitamos estén actualizados en este repositorio.
crawl:
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
stage: create-datasset
script:
- KAGGLE_KEY=$KAGGLE_KEY KAGGLE_USERNAME=ioexception python -m mananeras
- git add articulos/ urls.txt
- git diff --quiet && git diff --staged --quiet || git commit -m 'Nuevos archivos'
- git push http://fferegrino:${CI_ACCESS_TOKEN}@gitlab.com/thatcsharpguy/datasets/mananeras.git HEAD:main
3. Configurando el pipeline
Hay algunas configuraciones que son necesarias para ejecutar el pipeline definido anteriormente, desde el menu de tu repositorio en Gitlab navega a CI/CD > Schedules. De ahí, da click en el botón que dice algo como New schedule. Esto te solicitará cierta información sobre cómo quieres que se llame, qué tan seguido quieres que se ejecute (mi pipeline de web scraping se ejecuta diario a las 19:20, usando el cron 20 19 * * *
) y en qué rama debe ser esta ejecución.
Y pues eso es todo, así es como mi dataset en Kaggle se actualiza diariamente y un nuevo commit es creado en este repositorio, se que no es tan detallado (ni limpio) como podría haber sido, pero espero que te sirva para darte una idea de lo que se puede hacer con Gitlab y pon poco de imaginación.
Encuentra el código para este post en Gitlab para que tu también juegues con él, recuerda que me puedes encontrar en Twitter @feregri_no en donde estoy más que feliz de responder preguntas.