Cree una API extra铆da de la web con Express y Cheerio

    El crecimiento de la World Wide Web durante las 煤ltimas dos d茅cadas ha llevado a que se recopile y pegue una enorme cantidad de datos en las p谩ginas web de Internet. Un corolario de esta producci贸n y distribuci贸n hiperb贸licas de contenido en la web es la conservaci贸n de una gran cantidad de informaci贸n que se puede utilizar de formas enumerables si se puede extraer y agregar de manera eficaz.

    Las formas m谩s comunes de recopilar y agregar datos disponibles en la web son (1) solicitarlo desde una API con tecnolog铆as como DESCANSO o JAB脫N y (2) escribir un programa para analizarlo o extraerlo de datos poco estructurados, como HTML. El primero es, con mucho, el m茅todo preferible para el programador que consume la informaci贸n, pero a menudo esto no es una posibilidad debido al tiempo de desarrollo y los recursos necesarios por parte del productor. Por lo tanto, la mayor铆a de las veces, el 煤nico medio disponible para obtener datos valiosos es rasparlos.

    Este art铆culo presentar谩 una t茅cnica para crear una aplicaci贸n independiente Node.js que recopila (raspa) datos de impuestos para el estado de Nebraska y presenta esa informaci贸n al usuario o calcula los impuestos seg煤n una ciudad y la cantidad suministrada.

    Las tecnolog铆as a utilizar son:

    • Node.js: un tiempo de ejecuci贸n de JavaScript basado en el motor V8 de Chrome
    • Express: un marco web de Node.js
    • Cheerio: una biblioteca de an谩lisis HTML que refleja la API familiar de la biblioteca jQuery

    El c贸digo fuente se puede encontrar en GitHub. aqu铆.

    Configuraci贸n del proyecto base

    Este tutorial utilizar谩 Node Package Manager (npm) para inicializar el proyecto, instalar bibliotecas y administrar dependencias. Antes de comenzar, aseg煤rese de haber configurado npm para su entorno.

    Inicialice el proyecto aceptando las opciones b谩sicas predeterminadas:

    Instalar dependencias:

    Estructura b谩sica del proyecto:

    R谩pido

    Nosotros usaremos R谩pido para construir nuestra API RESTful para la aplicaci贸n de c谩lculo de impuestos. Express es un marco de trabajo de aplicaci贸n web para aplicaciones Node que es flexible en el sentido de que impone pocas restricciones en la forma de desarrollar sus aplicaciones, pero muy poderoso porque proporciona varias caracter铆sticas 煤tiles que se utilizan en una multitud de aplicaciones web.

    Configuraci贸n de Express

    En server.js incluiremos un c贸digo de configuraci贸n Express repetitivo que crear谩 la aplicaci贸n Express, luego registraremos el m贸dulo de rutas que haremos en la siguiente subsecci贸n. Al final del archivo, le indicaremos a la aplicaci贸n Express que escuche el puerto proporcionado o el 3500, que es un puerto codificado.

    En server.js copie y pegue el siguiente c贸digo:

    'use strict';
    
    const express = require('express');
    const app = express();
    const port = process.env.PORT || 3500;
    
    const routes = require('./api/routes');
    routes(app);
    
    app.listen(port);
    
    console.log("Node application running on port " + port);
    

    Rutas

    Configuraremos el enrutamiento en nuestra aplicaci贸n para responder a las solicitudes realizadas a rutas URI espec铆ficas y significativas. 驴A qu茅 me refiero con significativo que pueda estar preguntando? Bueno, en el paradigma REST, las rutas de ruta est谩n dise帽adas para exponer recursos dentro de la aplicaci贸n de manera autodescriptiva.

    En el archivo routes / index.js, copie y pegue el siguiente c贸digo:

    'use strict';
    
    const taxCtrl = require('../controllers');
    
    module.exports = (app) => {
        app.use(['/calculate/:stateName/:cityName/:amount', '/taxrate/:stateName/:cityName'], (req, res, next) => {
            const state = req.params.stateName;
            if (!taxCtrl.stateUrls.hasOwnProperty(state.toLowerCase())) {
                res.status(404)
                        .send({message: `No state info found for ${state}`});
            } else {
                next();
            }
        });
    
        app.route('/taxrate/:stateName/:cityName')
            .get(taxCtrl.getTaxRate);
    
        app.route('/calculate/:stateName/:cityName/:amount')
            .get(taxCtrl.calculateTaxes);
      
        app.use((req, res) => {
            res.status(404)
                .send({url: `sorry friend, but url ${req.originalUrl} is not found`});
        });
    }
    

    Las dos rutas que se definen en este m贸dulo son / taxrate /: stateName /: cityName y / calculate /: stateName /: cityName /: amount. Est谩n registrados en el app objeto que se pas贸 al m贸dulo desde el script server.js descrito anteriormente llamando al m茅todo de ruta en el app. Dentro del m茅todo de ruta se especifica la ruta y luego el get El m茅todo es llamado, o encadenado, en el resultado de llamar a route. Dentro del m茅todo get encadenado hay una funci贸n de devoluci贸n de llamada que analizaremos con m谩s detalle en la secci贸n sobre controladores. Este m茅todo de definir rutas se conoce como “encadenamiento de rutas”.

    La primera ruta describe un punto final que mostrar谩 las tasas de impuestos estatales y municipales en respuesta a una solicitud GET correspondiente a :stateName y :cityName, respectivamente. En Express, especifica lo que se conoce como “par谩metros de ruta” precediendo una secci贸n de una ruta delimitada entre barras diagonales con dos puntos para indicar un marcador de posici贸n para un par谩metro de ruta significativo. La segunda ruta / calcular /: stateName /: cityName /: amount describe un punto final que calcular谩 los montos de impuestos de la ciudad y del estado, as铆 como el monto total en funci贸n del par谩metro de monto de la ruta.

    Las otras dos invocaciones del app objeto est谩n especificando middleware. El middleware Express.js es una caracter铆stica incre铆blemente 煤til que tiene muchas aplicaciones que podr铆an justificar f谩cilmente su propia serie de art铆culos, por lo que no voy a profundizar mucho aqu铆. Solo sepa que el middleware son funciones que pueden conectarse, acceder y modificar la solicitud, la respuesta, el error y los siguientes objetos de un ciclo de solicitud-respuesta Express.

    Registra una funci贸n de middleware llamando al use m茅todo en el app objeto y pasando combinaciones 煤nicas de rutas y funciones de devoluci贸n de llamada. El primer middleware declarado en el objeto de la aplicaci贸n especifica nuestras dos URL dentro de una matriz y una funci贸n de devoluci贸n de llamada que verifica si el estado que se pasa para solicitar informaci贸n fiscal est谩 disponible.

    Esta aplicaci贸n de demostraci贸n solo se desarrollar谩 para responder a solicitudes de ciudades en Nebraska, pero alguien podr铆a extenderla f谩cilmente con otros estados dado que tienen una p谩gina web est谩tica disponible p煤blicamente con informaci贸n similar. El segundo middleware sirve como captura para todas las rutas de URL solicitadas que no se especifican.

    Controladores

    Los controladores son la parte de una aplicaci贸n Express que maneja las solicitudes reales realizadas a las rutas definidas y devuelve una respuesta. Estrictamente hablando, los controladores no son un requisito para desarrollar aplicaciones Express. Se puede usar una funci贸n de devoluci贸n de llamada, an贸nima o de otro tipo, pero el uso de controladores conduce a un c贸digo mejor organizado y a una separaci贸n de preocupaciones. Dicho esto, usaremos controladores, ya que siempre es una buena idea seguir las mejores pr谩cticas.

    En su archivo controllers / index.js, copie y pegue el siguiente c贸digo.

    'use strict';
    
    const svc = require('../services');
    
    const getTaxRate = (req, res) => {
        const state = req.params.stateName;
        svc.scrapeTaxRates(state, stateUrls[state.toLowerCase()], (rates) => {
            const rate = rates.find(rate => {
                return rate.city.toLowerCase() === req.params.cityName.toLowerCase();
            });
            res.send(rate);
        });
    }
    
    const calculateTaxes = (req, res) => {
        const state = req.params.stateName;
        svc.scrapeTaxRates(state, stateUrls[state.toLowerCase()], (rates) => {
            const rate = rates.find(rate => {
                return rate.city.toLowerCase() === req.params.cityName.toLowerCase();
            });
            res.send(rate.calculateTax(parseFloat(req.params.amount)));
        });
    }
    
    
    const stateUrls = {
        nebraska: 'http://www.revenue.nebraska.gov/question/sales.html';
    };
    
    module.exports = {
        getTaxRate,
        calculateTaxes,
        stateUrls
    };
    

    Lo primero que ve que se importa y declara en el m贸dulo de controladores es una constante llamada svc que es la abreviatura de “servicio”. Este objeto de servicio sirve como una pieza reutilizable de funcionalidad para solicitar una p谩gina web y analizar el HTML resultante. Profundizar茅 m谩s en la secci贸n sobre Cheerio y servicios sobre lo que est谩 sucediendo detr谩s de escena con este objeto de servicio, pero por ahora solo sepa que analiza el HTML para los bits significativos que nos interesan (es decir, tasas de impuestos).

    Las dos funciones que m谩s nos interesan son getTaxRate y calculateTaxes. Ambas funciones se pasan en solicitud y respuesta (req y res) objetos a trav茅s del route.get(...) m茅todos en el m贸dulo de rutas. los getTaxRate funci贸n accede al stateName par谩metro de ruta del objeto params del objeto de solicitud.

    El nombre del estado y su URL de destino correspondiente (en este caso, solo Nebraska y su p谩gina web gubernamental que muestra informaci贸n imponible) se pasan al m茅todo del objeto de servicio scrapeTaxRates. Se pasa una funci贸n de devoluci贸n de llamada como tercer par谩metro para filtrar y responder con la informaci贸n de la ciudad correspondiente al cityName par谩metro encontrado en la ruta de la ruta.

    La segunda funci贸n del controlador, calculateTaxes, nuevamente usa el m茅todo de servicio scrapeTaxRates para solicitar y analizar el HTML, pero esta vez calcula los impuestos a trav茅s de un m茅todo dentro del TaxRate class, que discutiremos a continuaci贸n en la secci贸n de modelos.

    Modelos

    Al igual que los controladores, los modelos no son algo estrictamente necesario para una aplicaci贸n Express. Sin embargo, los modelos son bastante 煤tiles cuando queremos encapsular datos (estado) y comportamiento (acciones) dentro de nuestras aplicaciones de manera organizada.

    En su archivo modelos / index.js, copie y pegue el siguiente c贸digo:

    'use strict'
    
    class TaxRate {
        constructor(state, city, localRate, stateRate) {
            this.state = state;
            this.city = city;
            this.localRate = localRate;
            this.stateRate = stateRate;
        }
    
        calculateTax (subTotal) {
            const localTax = this.localRate * subTotal;
            const stateTax = this.stateRate * subTotal;
            const total = subTotal + localTax + stateTax;
            return {
                localTax,
                stateTax,
                total
            };
        }
    }
    
    module.exports = TaxRate;
    

    El 煤nico modelo (o m谩s correctamente dicho: clase) que definiremos en nuestra aplicaci贸n es TaxRate. TaxRate contiene campos de miembros para almacenar datos sobre el estado, la ciudad, la tasa impositiva local y la tasa impositiva estatal. Estos son los campos de clase que componen el estado del objeto. Solo hay un m茅todo de clase, calculateTax(...), que toma el par谩metro que representa un monto subtotal pasado a la ruta / calcular /: stateName /: cityName /: amount path y devolver谩 un objeto que representa las cantidades de impuestos calculadas y el monto total final.

    Cheerio

    Cheerio es una biblioteca de JavaScript liviana que implementa el n煤cleo de jQuery para acceder, seleccionar y consultar HTML en aplicaciones del lado del servidor. En nuestro caso, utilizaremos Cheerio para analizar el HTML en la p谩gina web est谩tica que solicitamos al sitio web del gobierno de Nebraska que muestra informaci贸n fiscal.

    Servicios

    En nuestra peque帽a aplicaci贸n, usaremos un m贸dulo de servicios personalizados para implementar la solicitud de la p谩gina HTML del sitio web del gobierno de Nebraska, as铆 como el an谩lisis del HTML resultante para extraer los datos que deseamos.

    En su archivo services / index.js, copie y pegue el siguiente c贸digo:

    'use strict';
    
    const http = require('http');
    const cheerio = require('cheerio');
    const TaxRate = require('../models');
    
    const scrapeTaxRates = (state, url, cb) => {
        http.get(url, (res) => {
            let html="";
      
            res.on('data', chunk => {
                html += chunk;
            });
      
            res.on('end', () => {
                const parser = new Parser(state);
                const rates = parser.parse(html);
                cb(rates);
            });
        });
    };
    
    class Parser {
        constructor(state) {
            this.state = state;
        }
    
        parse(html) {
            switch(this.state.toLowerCase()) {
                case 'nebraska':
                    return this.parseNebraska(html);
                default:
                    return null;
            }
        }
    
        parseNebraska(html) {
            const $ = cheerio.load(html);
            let rates = [];
            $('tr').each((idx, el) => {
                const cells = $(el).children('td');
                if (cells.length === 5 && !$(el).attr('bgcolor')) {
                    const rawData = {
                        city: $(cells[0]).first().text(),
                        cityRate: $(cells[1]).first().text(),
                        totalRate: $(cells[2]).first().text()
                    };
                    rawData.cityRate = parseFloat(rawData.cityRate.replace('%', ''))/100;
                    rawData.totalRate = parseFloat(rawData.totalRate.substr(0, rawData.totalRate.indexOf('%')))/100;
                    rawData.stateRate = rawData.totalRate - rawData.cityRate;
                    rates.push(new TaxRate('Nebraska', rawData.city, rawData.cityRate, rawData.stateRate));
                }
            });
            return rates;
        }
    }
    
    module.exports = {
        scrapeTaxRates;
    };
    

    Las primeras tres l铆neas se est谩n importando (a trav茅s de require()) algunos objetos de nivel de m贸dulo http, cheerioy TaxRate. TaxRate se describi贸 en la secci贸n anterior sobre m贸dulos para no vencer al proverbial caballo muerto y repasar su uso con demasiado detalle, por lo que basta con decir que se usa para almacenar datos de tasas impositivas y calcular impuestos.

    los http El objeto es un m贸dulo de node que se utiliza para realizar solicitudes desde el servidor a otro recurso en red, que en nuestro caso es la p谩gina web de tasa impositiva del gobierno de Nebraska. El restante es Cheerio, que se utiliza para analizar el HTML utilizando la conocida API jQuery.

    El m贸dulo de servicios solo expone una funci贸n disponible p煤blicamente llamada scrapeTaxRates, que toma una cadena de nombre de estado, una cadena de URL (para la p谩gina del estado que muestra las tasas de impuestos) y una funci贸n de devoluci贸n de llamada para procesar las tasas de impuestos de formas 煤nicas especificadas por el c贸digo del cliente que llama.

    Dentro del cuerpo del scrapeTaxRates funciona el get m茅todo para el http se llama al objeto para solicitar la p谩gina web en la URL especificada. La funci贸n de devoluci贸n de llamada pas贸 al http.get(...) El m茅todo maneja el procesamiento de la respuesta. En el procesamiento de la respuesta, se crea una cadena de HTML y se almacena en una variable llamada html. Esto se hace de forma incremental a medida que data Se activa el evento y la respuesta devuelve una parte de los datos almacenados en b煤fer.

    Tras el disparo del end evento se invoca una funci贸n de devoluci贸n de llamada final. Dentro de esta devoluci贸n de llamada, Parser se crea una instancia de la clase y se llama al m茅todo parse para analizar el HTML y extraer la informaci贸n espec铆fica de la estructura y el dise帽o de la p谩gina web de Nebraska. Los datos analizados se cargan en una serie de TaxRate objetos almacenados en una matriz y pasados 鈥嬧媋 la funci贸n de devoluci贸n de llamada para ejecutar la l贸gica especificada en el c贸digo del cliente que llama (en nuestro caso, en las funciones del controlador descritas anteriormente). Es en este 煤ltimo paso que los datos se serializan y env铆an como respuesta a la persona que llama de la API REST.

    Conclusi贸n

    En este breve art铆culo, investigamos c贸mo dise帽ar una aplicaci贸n Node.js simple y ligera que extraiga informaci贸n 煤til sobre la tasa impositiva de un sitio web del gobierno, lo que podr铆a ser 煤til para aplicaciones de comercio electr贸nico. Los dos prop贸sitos principales de la aplicaci贸n son recaudar tasas de impuestos y mostrar esa informaci贸n para una ciudad determinada o calcular los impuestos en funci贸n de un estado, una ciudad y un subtotal.

    Por ejemplo, a continuaci贸n encontrar谩 capturas de pantalla de la aplicaci贸n que muestran los impuestos de la ciudad de Omaha y calculan los impuestos para un subtotal de $ 1000. Para probar esta aplicaci贸n cd en el directorio ra铆z y escriba $ node server.js en la consola. Ver谩 un mensaje que dice “Aplicaci贸n de node ejecut谩ndose en el puerto 3500”.

    Espero que este art铆culo lo inspire a seguir investigando el mundo del raspado de datos para que pueda crear aplicaciones 煤tiles y productos de datos significativos. Como siempre, doy la bienvenida a todos y cada uno de los comentarios a continuaci贸n.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *