JavaScript >> Javascript 文檔 >  >> Tags >> API

使用 Express 和 Cheerio 構建 Web-Scraped API

在過去的幾十年裡,萬維網的發展導致大量數據被收集並粘貼到整個互聯網的網頁上。這種雙曲線的網絡內容生產和分發的一個必然結果是,如果人們能夠有效地提取和聚合這些信息,那麼這些信息可以以多種方式使用。

收集和聚合 Web 上可用數據的最常見方法是(1)使用 REST 或 SOAP 等技術從 API 請求數據,以及(2)編寫程序從鬆散結構的數據中解析或抓取數據,例如HTML。到目前為止,前者是程序員使用信息的首選方法,但由於生產者方面需要開發時間和資源,這通常是不可能的。因此,獲取珍貴數據的唯一可用方法往往是抓取它。

本文將介紹一種構建獨立 Node.js 應用程序的技術,該應用程序收集(抓取)內布拉斯加州的稅收數據,並將該信息呈現給用戶或根據城市和供應量計算稅收。

使用的技術有:

  • Node.js:基於 Chrome 的 V8 引擎構建的 JavaScript 運行時
  • Express:一個 Node.js 網絡框架
  • Cheerio:一個反映熟悉的 jQuery 庫 API 的 HTML 解析庫

源代碼可以在 GitHub 上找到。

基礎項目設置

本教程將利用 Node Package Manager (npm) 來初始化項目、安裝庫和管理依賴項。在我們開始之前,請確保您已經為您的環境配置了 npm。

初始化接受基本默認選項的項目:

安裝依賴:

基本項目結構:

快遞

我們將使用 Express 為稅收計算應用程序構建我們的 RESTful API。 Express 是一個適用於 Node 應用程序的 Web 應用程序框架,它既靈活,因為它對您的應用程序開發方式施加的限制很少,而且非常強大,因為它提供了多種有用的功能,可用於眾多 Web 應用程序。

設置 Express

在 server.js 中,我們將包含一些樣板的 Express 設置代碼,這些代碼將創建 Express 應用程序,然後註冊我們將在下一小節中創建的路由模塊。在文件末尾,我們將指示 Express 應用程序監聽提供的端口或 3500,這是一個硬編碼的端口。

在 server.js 中復制並粘貼以下代碼:

'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);

路線

我們將在我們的應用程序中設置路由以響應對特定且有意義的 URI 路徑的請求。你可能問的有意義是什麼意思?在 REST 範式中,路由路徑旨在以自我描述的方式公開應用程序中的資源。

在 routes/index.js 文件中復制並粘貼以下代碼:

'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`});
    });
}

此模塊中定義的兩條路線是 /taxrate/:stateName/:cityName/calculate/:stateName/:cityName/:amount .它們使用 app 註冊 通過調用 app 上的 route 方法從上述 server.js 腳本傳遞到模塊的對象 .在路由方法中指定路由,然後是 get 在調用路由的結果上調用或鏈接方法。鍊式 get 方法內部是一個回調函數,我們將在控制器部分進一步討論。這種定義路由的方法稱為“路由鏈接”。

第一個路由描述了一個端點,它將顯示州和城市的稅率以響應對應於 :stateName 的 GET 請求 和 :cityName , 分別。在 Express 中,您可以指定所謂的“路由參數”,方法是在由正斜杠分隔的路由部分前面加上冒號,以指示有意義的路由參數的佔位符。第二條路線/calculate/:stateName/:cityName/:amount 描述了一個端點,它將根據路線的金額參數計算城市和州稅金額以及總金額。

app 的另外兩個調用 對象正在指定中間件。 Express.js 中間件是一個非常有用的功能,它有許多應用程序,可以很容易地保證他們自己的系列文章,所以我不會在這裡深入探討。只要知道中間件是可以掛鉤、訪問和修改 Express 請求-響應週期的請求、響應、錯誤和下一個對象的函數。

你通過調用 use 註冊一個中間件函數 app 上的方法 對象並傳入路由和回調函數的獨特組合。在 app 對像上聲明的第一個中間件指定了我們在一個數組中的兩個 URL 和一個回調函數,該函數檢查傳遞給請求稅務信息的狀態是否可用。

免費電子書:Git Essentials

查看我們的 Git 學習實踐指南,其中包含最佳實踐、行業認可的標準以及隨附的備忘單。停止谷歌搜索 Git 命令並真正學習 它!

此演示應用程序將僅用於響應內布拉斯加州城市的請求,但有人可以很容易地將其擴展到其他州,因為他們有一個公開可用的類似信息的靜態網頁。第二個中間件用作所有請求但未指定的 URL 路徑。

控制器

控制器是 Express 應用程序的一部分,它處理對定義的路由發出的實際請求並返迴響應。嚴格來說,控制器不是開發 Express 應用程序的必要條件。可以使用匿名或其他方式的回調函數,但使用控制器可以更好地組織代碼和分離關注點。話雖如此,我們將使用控制器,因為遵循最佳實踐始終是一個好主意。

在您的 controllers/index.js 文件中復制並粘貼以下代碼。

'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
};

在控制器模塊中導入和聲明的第一件事是一個名為 svc 的常量 這是“服務”的縮寫。此服務對像用作請求網頁並解析生成的 HTML 的可重用功能。我將在 Cheerio 和服務部分更深入地了解這個服務對像在幕後發生的事情,但現在只知道它會解析 HTML 以獲得我們感興趣的有意義的位(即稅率)。

我們最感興趣的兩個函數是getTaxRatecalculateTaxes .這兩個函數都在請求和響應中傳遞(reqres ) 對象通過 route.get(...) 路由模塊中的方法。 getTaxRate 函數訪問 stateName 來自請求對象的params對象的路由參數。

州名及其對應的目標 URL(在這種情況下只有 Nebraska 及其顯示應稅信息的政府網頁)被傳遞給服務對象的方法 scrapeTaxRates .第三個參數傳入回調函數,過濾掉cityName對應的城市信息 在路由路徑中找到參數。

第二個控制器函數,calculateTaxes ,再次使用服務方法scrapeTaxRates 請求和解析 HTML,但這次它通過 TaxRate 中的方法計算稅金 類,我們將在接下來的模型部分討論。

型號

與控制器類似,模型並不是 Express 應用程序的嚴格要求。但是,當我們希望以有組織的方式在應用程序中封裝數據(狀態)和行為(動作)時,模型會派上用場。

在您的 models/index.js 文件中,複製並粘貼以下代碼:

'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;

我們將在應用程序中定義的唯一模型(或更準確地說:類)是 TaxRate . TaxRate 包含用於保存州、城市、地方稅率和州稅率數據的成員字段。這些是構成對象狀態的類字段。只有一個類方法,calculateTax(...) ,它接受表示傳遞到路線 /calculate/:stateName/:cityName/:amount 的小計金額的參數 路徑,並將返回一個對象,表示計算出的稅額和最終總金額。

快樂

Cheerio 是一個輕量級的 JavaScript 庫,它實現了 jQuery 核心來訪問、選擇和查詢服務器端應用程序中的 HTML。在我們的例子中,我們將使用 Cheerio 來解析靜態網頁中的 HTML,我們從內布拉斯加州政府的網站上請求顯示稅收信息。

服務

在我們的小應用程序中,我們將使用自定義服務模塊來實現從內布拉斯加州政府網站請求 HTML 頁面以及解析生成的 HTML 以提取我們想要的數據。

在您的 services/index.js 文件中復制並粘貼以下代碼:

'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;
};

前三行正在導入(通過 require() ) 一些模塊級對象 http , cheerio , 和 TaxRate . TaxRate 在上一節關於模塊的部分中進行了描述,因此我們不會擊敗眾所周知的死馬並對其使用進行過多詳細介紹,因此只需說它用於存儲稅率數據和計算稅收即可。

http object 是一個 Node 模塊,用於從服務器向另一個網絡資源發出請求,在我們的例子中是內布拉斯加州政府的稅率網頁。剩下的一個是 Cheerio,用於使用熟悉的 jQuery API 解析 HTML。

services 模塊隻公開了一個名為 scrapeTaxRates 的公開可用的函數 ,它接受一個州名稱字符串、URL 字符串(用於顯示稅率的州頁面)和一個回調函數,以調用客戶端代碼指定的唯一方式處理稅率。

scrapeTaxRates 的正文中 函數get http 的方法 調用對象來請求指定 URL 的網頁。傳遞給http.get(...)的回調函數 方法處理響應的處理。在響應的處理過程中,會構建一個 HTML 字符串並將其存儲在一個名為 html 的變量中 .這是以增量方式完成的,如 data 觸發事件並從響應中返回緩衝的數據塊。

end 觸發後 事件調用最終的回調函數。在這個回調中 Parser 類被實例化並調用 parse 方法來解析 HTML 並提取特定於 Nebraska 網頁的結構和佈局的信息。將解析後的數據加載到一系列TaxRate中 對象存儲在數組中並傳遞給回調函數以執行調用客戶端代碼中指定的邏輯(在我們的例子中,在前面描述的控制器函數中)。在最後一步中,數據被序列化並作為響應發送給 REST API 的調用者。

結論

在這篇簡短的文章中,我們研究瞭如何設計一個簡單的輕量級 Node.js 應用程序,該應用程序可以從政府網站上抓取有用的稅率信息,這可能對電子商務應用程序很有用。該應用程序的兩個主要目的是收集稅率,或者顯示給定城市的信息,或者根據州、城市和小計計算稅收。

例如,您將在下面找到顯示奧馬哈市稅金的應用程序屏幕截圖,併計算小計 1000 美元的稅金。為了測試這個應用程序 cd 進入根目錄並輸入 $ node server.js 進入控制台。您將看到一條消息,上面寫著“節點應用程序在端口 3500 上運行”。

我希望這篇文章能激發您進一步研究數據抓取的世界,以便您可以創建有用的應用程序和有意義的數據產品。與往常一樣,我歡迎下面的任何和所有評論。


Tutorial JavaScript 教程
  1. 樣式化組件導入和 eslint 出錯

  2. 視頻標籤現在在 Safari 中不起作用

  3. 我犯的錯誤將文件路徑視為字符串

  4. 使用 jQuery 的 10 個驚人的圖像效果

  5. 從數組中選擇一對值並將其返回到兩個不同的輸出中。 JavaScript

  6. 破解 JS async/await 以鏈接 Monad

  7. 使用replace方法刪除特殊符號和多餘空格並替換為下劃線

  1. 被 CORS 政策阻止? CORS 代理是解決方案😎

  2. 多讀少讀

  3. 我最喜歡的資源是作為兼職的自由職業者💎

  4. 獲取觸發(觸發)的事件的名稱(類型)

  5. React JS 的簡單介紹

  6. Javascript GameDev 生態系統概述

  7. 檢查 Promise 是否掛起的優雅方法

  1. 使用 MSAL 和 Next.js (React) 的 Azure AD 身份驗證

  2. 如何在 Laravel 中創建一個簡單的事件流?

  3. 4 個 jQuery 屏幕鍵盤

  4. 在 Next.js 中處理圖像加載和錯誤狀態