skip to content
Intentando.dev

Captura de tabla de apagones

Como hacer un programa que almacene imagenes de la tabla de apagones

Uno de los proyectos al que le dedico tiempo es LuzPR. Esa página colecta y presenta datos sobre los apagones en Puerto Rico. Funciona para seguir cuantos puertorriqueños son sujetos a apagones diariamente, pero hay veces que una imagen habla mejor que mil gráficas:

captura de pantalla de la tabla de apagones de LUMAEsta es la tabla de “Estatus del servicio” presentada y actualizada por LUMA Energy cada 5 minutos. Sus datos ya son presentados en LuzPR, pero esta tabla le da mayor autenticidad:

Nuestra página de web oficial, lumapr.com, es la fuente de información sobre el estatus de las interrupciones de servicio y actualizaciones más confiable

- LUMA Energy, junio 20231

No obstante, hay obstáculos para capturar la tabla en momentos críticos. Los apagones son impredecibles, necesitas un dispositivo electrónico a la mano, solo tienes 5 minutos para tomar la captura, y eso si estas despierto a la hora que ocurra el apagón.

En el resto de este artículo, presento un código para automáticamente capturar y almacenar las tablas de apagones.

Los pedazos de la herramienta

Vamos a discutir a grandes rasgos los pedazos que forman el código, y luego iremos uno por uno a detalle.

El lenguaje de programación usado es Javascript, apto para navegar páginas web como la de LUMA. Sus motores son Node.js y Puppeteer. Esta última es un navegador web que podemos programar para accesar páginas en el internet y tomar capturas de imágenes.

Las imágenes pueden ser almacenadas en una laptop pero eso requeriría tener un dispositivo encendido 24/7. Usaremos la nube tanto para correr la herramienta como para almacenar las imágenes.

GitHub Actions corre programas como el nuestro gratuitamente y puede instruirse a repetir la tarea cada 5 minutos. Para almacenar las imágenes, optamos por usar un servicio como Amazon S3 o Cloudflare R2, que tiene capacidad “infinita”2.

El navegador: Puppeteer

Puppeteer es una interfaz para controlar el navegador de Google Chrome usando comandos en Node.js. Una versión resumida del código para accesar la página de LUMA es el siguiente:

import puppeteer from 'puppeteer';

// Abrir un navegador
const browser = await puppeteer.launch();

// Crear una nueva página
const page = await browser.newPage();

// Elegir tamaño de la ventana
await page.setViewport({
	width: 768,
	height: 680,
	deviceScaleFactor: 2,
});

// Abrir una URL
const website_url = 'https://miluma.lumapr.com/outages/serviceStatus';
await page.goto(website_url, { waitUntil: 'networkidle0' });

// Esperar por la tabla
await page.waitForSelector(
	'div.jss55', // identificador de la tabla
	{ visible: true }
)

// Tomar y guardar una captura de pantalla
await page.screenshot({ path: 'captura_tabla.png' });

// Cerrar el navegador
await browser.close();

Hay unos detalles que brincamos por ahora. El código completo también guarda la fecha de la captura, repite el ejercicio cuando la página no carga, y pasa información del archivo para almacenamiento en la nube. No obstante, lo presentado aquí es la esencia del código para capturar la tabla.

Almacenando en la nube

Este paso es opcional, pero hace el proyecto mucho más manejable. Nos permite guardar las capturas en una nube que no tiene los límites de una máquina local. Tiene espacio ilimitado y no está sujeta a apagones o desconexiones que interrumpan el proceso. La preparación requerida es mayor, pero vale la pena en mi opinión.

Su código resumido es el siguiente:

import fs from 'fs';
import {
    S3Client,
    PutObjectCommand
} from "@aws-sdk/client-s3";

// Credenciales para accesar la cubeta de S3
// (almacenados como environment variables)
const ENDPOINT = process.env.ENDPOINT;
const ACCESS_KEY_ID = process.env.ACCESS_KEY_ID;
const SECRET_ACCESS_KEY = process.env.SECRET_ACCESS_KEY;

// Nombre del archivo de la última captura
const screenshot_filepath = 'captura_tabla.png';

// Abrir cliente de S3
const S3 = new S3Client({
    region: "auto",
    endpoint: ENDPOINT,
    credentials: {
      accessKeyId: ACCESS_KEY_ID,
      secretAccessKey: SECRET_ACCESS_KEY,
    },
});

// Para subir el archivo a S3
async function uploadFile(filePath) {
    const bucketName = 'luma-estatus';
    const key = filePath;

    try {
      const fileStream = fs.createReadStream(filePath);
      const command = new PutObjectCommand({
        Bucket: bucketName, // cubeta donde almacenar el archivo
        Key: key, // s3 path
        Body: fileStream, // contenidos del archivo a almacenar
      });
  
      const response = await S3.send(command);
      console.log('File uploaded successfully:', response);
    } catch (error) {
      console.error('Error uploading file:', error);
    }
}

uploadFile(screenshot_filepath);

Este resumen obvia los mismos detalles que el código de captura. El código completo puede accederse aquí.

Accesando las capturas

Si estas almacenando las capturas localmente, solo abre el directorio donde elegiste guardarlas. Si usaste la nube, tendrás que buscar en su portal por fecha y manualmente descargarlas.

Hay oportunidades de mejorar el método de acceso. Quizás creando un API que pueda buscar las capturas según la fecha que le pidas. Sin embargo, esto cae fuera del scope de este proyecto por el momento. Cuando trabaje una herramienta así, escribiré sobre la implementación también.

Automatizando todo

GitHub Actions corre el código pero necesitamos algo para activar ese proceso en un horario como cron-job.org. Podemos usar cron-job para hacer un API call al endpoint de GitHub de /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches:

curl -L \ -X POST \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer <YOUR-TOKEN>" \ -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/repos/OWNER/luma-estatus/actions/workflows/WORKFLOW_ID/dispatches \ -d '{"ref":"main"}'

El WORKFLOW_ID puede ser hallado a través del endpoint de /repos/{owner}/{repo}/actions/workflows:

curl -L \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <YOUR-TOKEN>" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/OWNER/luma-estatus/actions/workflows

Todos los detalles para crear un personal access token y usar estos APIs de GitHub están descritos en “REST API endpoints for workflows”.

Resumiendo

Automatizar este código ayuda de muchas maneras que son difíciles realizar manualmente. Archivas copias de tablas que desaparecen cada 5 minutos. No tienes que estar despierto a las 3AM cuando ocurre un mega apagón. En resumen, nos ayuda a documentar la calidad del servicio eléctrico que recibimos continuamente.

Encontraras todo el código aquí.

Footnotes

  1. El Vocero. 8 de junio de 2023. “Apagón informativo sobre datos que LUMA Energy comparte sobre clientes sin servicio”

  2. Servicios S3 requieren más preparación que GitHub Actions pero a la larga te pueden salvar de mayores problemas causados por subir archivos a GitHub cada 5 minutos a lo largo de meses.