ノードでウェブクローラを構築する方法


執筆Jordan Irabor ✏️

導入
ウェブクローラは、しばしばクローラに、または時々クモボットと呼ばれるに短縮され、体系的に体系的にインターネットの目的のためにブラウズボットweb indexing . これらのインターネットボットは、ユーザーの検索結果の品質を向上させる検索エンジンで使用することができます.ワールドワイドウェブのインデックスに加えて、クロールもデータを収集するために使用することができます.
Webスクレーピングのプロセスは、サイトの構造と抽出されたデータの複雑さに応じてCPU上でかなりタスクニングすることができます.このプロセスを最適化しスピードアップするにはNode workers (スレッド) CPU集約操作に便利です.
この記事では、ウェブサイトをスクロールし、データベースにデータを格納するWebクローラーを構築する方法を学びます.このクローラボットは、ノードワーカーズを使用して両方の操作を実行します.


必要条件
  • 基礎知識Node.js

  • Yarn or NPM (糸を使います)
  • 実行するように構成されたシステムNode コード(好ましくは10.5.0またはそれより優れているバージョン)

  • インストール
    端末を起動し、このチュートリアルの新しいディレクトリを作成します
    $ mkdir worker-tutorial
    $ cd worker-tutorial
    
    次のコマンドを実行してディレクトリを初期化します
    $ yarn init -y
    
    クローラーを構築するには以下のパッケージが必要です.

  • Axios — ブラウザとノードのための約束ベースのHTTPクライアント.js

  • Cheerio — サーバ上のDOMへのアクセスを可能にするjQueryの軽量実装

  • Firebase database — クラウドはnoSQLデータベースをホストしました.Firebaseデータベースの設定に慣れていない場合は、documentation そして、ステップ1 - 3
  • 上記のパッケージを次のコマンドでインストールしましょう.
    $ yarn add axios cheerio firebase-admin
    

    ハローワーカー
    我々は労働者を使用してクローラを構築を開始する前に、いくつかの基本をしよう.テストファイルを作成できますhello.js プロジェクトのルートで次のスニペットを実行します.

    労働者登録
    労働者クラスをインポートすることによって、労働者を初期化することができますworker_threads モジュール
    // hello.js
    
    const { Worker } = require('worker_threads');
    
    new Worker("./worker.js");
    

    ハローワールド
    印刷Hello World 以下のスニペットを実行するのと同じくらい簡単です.
    // hello.js
    
    const { Worker, isMainThread }  = require('worker_threads');
    if(isMainThread){
        new Worker(__filename);
    } else{
        console.log("Worker says: Hello World"); // prints 'Worker says: Hello World'
    }
    
    このスニペットは、ワーカークラスとisMainThread からのオブジェクトworker_threads モジュール
  • isMainThread メインスレッドまたはワーカースレッド内で実行されているかどうかを知ることができます
  • new Worker(__filename) 新しい労働者を登録する__filename この場合、hello.js

  • 労働者とのコミュニケーション
    新しいワーカー(スレッド)が生成されると、スレッド間通信を可能にするメッセージングポートがあります.以下は、労働者(スレッド)間のメッセージを渡す方法を示すスニペットです.
    // hello.js
    
    const { Worker, isMainThread, parentPort }  = require('worker_threads');
    
    if (isMainThread) {
        const worker =  new Worker(__filename);
        worker.once('message', (message) => {
            console.log(message); // prints 'Worker thread: Hello!'
        });
        worker.postMessage('Main Thread: Hi!');
    } else {
        parentPort.once('message', (message) => {
            console.log(message) // prints 'Main Thread: Hi!'
            parentPort.postMessage("Worker thread: Hello!");
        });
    }
    
    上のスニペットで、親スレッドにメッセージを送るparentPort.postMessage() ワーカースレッドの初期化後.次に、親スレッドからのメッセージを聞きますparentPort.once() . また、作業スレッドにメッセージを送るworker.postMessage() を使って作業スレッドからのメッセージを聞きますworker.once() .
    コードを実行すると、次の出力が生成されます.
    Main Thread: Hi!
    Worker thread: Hello!
    

    クローラの建設
    ノードワーカーをクロールしてデータベースに書き込む基本的なWebクローラを構築しましょう.クローラは次の順序でタスクを完了します.
  • からHTMLをフェッチするwebsite
  • レスポンスからHTMLを展開する
  • DOMを横断し、為替レートを含む表を抽出する
  • 表形式の要素tbody , tr , and td ) 交換レート値
  • オブジェクト内の店舗為替レート値を使用して、それをworker.postMessage()
  • スレッドの親スレッドからのメッセージを受け入れるparentPort.on()
  • Firestore ( FireBaseデータベース)内のストアメッセージ
  • プロジェクトディレクトリに2つの新しいファイルを作りましょう
  • main.js – メインスレッド
  • dbWorker.js – 作業スレッド用
  • このチュートリアルのソースコードはGitHub . 自由にそれを複製したり、問題を提出したりしてください.

    メインスレッド( main . js )
    メインスレッドでは、我々はscrapeIBAN website 米国ドルに対して人気の通貨の現在の為替レート.インポートしますaxios そして、それを使用して、サイトからHTMLを取得する単純なGET リクエスト.
    また、使用しますcheerio DOMを横断し、テーブル要素からデータを抽出します.抽出する正確な要素を知るにはIBAN website 我々のブラウザと負荷dev tools :

    上のイメージから見るとtable クラスの要素table table-bordered table-hover downloads . これは大きな出発点となり、我々はそれにフィードすることができますcheerio ルート要素の選択
    // main.js
    
    const axios = require('axios');
    const cheerio = require('cheerio');
    const url = "https://www.iban.com/exchange-rates";
    
    fetchData(url).then( (res) => {
        const html = res.data;
        const $ = cheerio.load(html);
        const statsTable = $('.table.table-bordered.table-hover.downloads > tbody > tr');
        statsTable.each(function() {
            let title = $(this).find('td').text();
            console.log(title);
        });
    })
    
    async function fetchData(url){
        console.log("Crawling data...")
        // make http call to url
        let response = await axios(url).catch((err) => console.log(err));
    
        if(response.status !== 200){
            console.log("Error occurred while fetching data");
            return;
        }
        return response;
    }
    
    上記のコードをノードで実行すると、次の出力が得られます.

    前に、我々は更新されますmain.js ファイルを適切にフォーマットし、作業者スレッドに送ることができます.

    メインスレッドの更新
    出力を正しくフォーマットするためには、最終的な出力を2012年に格納するので、空白とタブを取り除く必要がありますJSON . 更新しましょうmain.js したがって、
    // main.js
    [...]
    let workDir = __dirname+"/dbWorker.js";
    
    const mainFunc = async () => {
      const url = "https://www.iban.com/exchange-rates";
      // fetch html data from iban website
      let res = await fetchData(url);
      if(!res.data){
        console.log("Invalid data Obj");
        return;
      }
      const html = res.data;
      let dataObj = new Object();
      // mount html page to the root element
      const $ = cheerio.load(html);
    
      let dataObj = new Object();
      const statsTable = $('.table.table-bordered.table-hover.downloads > tbody > tr');
      //loop through all table rows and get table data
      statsTable.each(function() {
        let title = $(this).find('td').text(); // get the text in all the td elements
        let newStr = title.split("\t"); // convert text (string) into an array
        newStr.shift(); // strip off empty array element at index 0
        formatStr(newStr, dataObj); // format array string and store in an object
      });
    
      return dataObj;
    }
    
    mainFunc().then((res) => {
        // start worker
        const worker = new Worker(workDir); 
        console.log("Sending crawled data to dbWorker...");
        // send formatted data to worker thread 
        worker.postMessage(res);
        // listen to message from worker thread
        worker.on("message", (message) => {
            console.log(message)
        });
    });
    
    [...]
    
    function formatStr(arr, dataObj){
        // regex to match all the words before the first digit
        let regExp = /[^A-Z]*(^\D+)/ 
        let newArr = arr[0].split(regExp); // split array element 0 using the regExp rule
        dataObj[newArr[1]] = newArr[2]; // store object 
    }
    
    上のスニペットでは、データフォーマットよりも多くを行いますアフターmainFunc() 解決されている、我々はフォーマットされたデータをworker ストレージ用スレッド.

    ワーカースレッド( dbworker . js )
    このワーカースレッドでは、FireBaseを初期化し、主スレッドからクロールデータをリッスンします.データが到着すると、データベースに格納し、メインスレッドにメッセージを送信し、データストレージが成功したことを確認します.
    以上の操作を行うスニペットは以下のようになります.
    // dbWorker.js
    
    const { parentPort } = require('worker_threads');
    const admin = require("firebase-admin");
    
    //firebase credentials
    let firebaseConfig = {
        apiKey: "XXXXXXXXXXXX-XXX-XXX",
        authDomain: "XXXXXXXXXXXX-XXX-XXX",
        databaseURL: "XXXXXXXXXXXX-XXX-XXX",
        projectId: "XXXXXXXXXXXX-XXX-XXX",
        storageBucket: "XXXXXXXXXXXX-XXX-XXX",
        messagingSenderId: "XXXXXXXXXXXX-XXX-XXX",
        appId: "XXXXXXXXXXXX-XXX-XXX"
    };
    
    // Initialize Firebase
    admin.initializeApp(firebaseConfig);
    let db = admin.firestore();
    // get current data in DD-MM-YYYY format
    let date = new Date();
    let currDate = `${date.getDate()}-${date.getMonth()}-${date.getFullYear()}`;
    // recieve crawled data from main thread
    parentPort.once("message", (message) => {
        console.log("Recieved data from mainWorker...");
        // store data gotten from main thread in database
        db.collection("Rates").doc(currDate).set({
            rates: JSON.stringify(message)
        }).then(() => {
            // send data back to main thread if operation was successful
            parentPort.postMessage("Data saved successfully");
        })
        .catch((err) => console.log(err))    
    });
    

    Note: To set up a database on firebase, please visit the firebase documentation and follow steps 1-3 to get started.


    走るmain.js これはdbWorker.js ) ノードを指定すると以下の出力が得られます:

    FireBaseデータベースをチェックすることで、以下のようなデータが表示されます.


    最終ノート
    ウェブクローリングは楽しいことができますが、それはまた、著作権侵害をコミットするデータを使用する場合は、法律に反することができます.それは一般的にあなたがクロールするつもりであるサイトの条件と条件を読んで、事前にデータのクロールのポリシーを知っていることをお勧めします.あなたは、これの這っている方針セクションでより多くを学ぶことができますpage .
    ワーカースレッドの使用は、アプリケーションがより高速になることを保証しませんが、メインスレッドでCPU集約タスクを面倒にすることによってメインスレッドを解放するので、効率的に使用される場合は、その蜃気楼を提示することができます.

    結論
    このチュートリアルでは、為替レートを下げてデータベースに保存するWebクローラーを構築する方法を学びました.また、これらの操作を実行するためにワーカースレッドを使用する方法も学びました.
    次のスニペットのそれぞれのソースコードはGitHub . 自由にそれを複製したり、問題を提出したりしてください.

    更なる読書
    ワーカースレッドについての学習に興味がありますか?次のリンクを確認できます.
  • Worker threads
  • Node.js multithreading: What are Worker Threads and why do they matter?
  • Going Multithread with Node.js
  • Simple bidirectional messaging in Node.js Worker Threads

  • 200年代のみ:モニタで失敗し、生産のネットワーク要求を遅くする
    ノードベースのWebアプリケーションやウェブサイトの展開は簡単です.あなたのノードのインスタンスがあなたのアプリケーションにリソースを提供し続けていることを確認することは物事がより厳しい取得です.あなたがバックエンドまたはサードパーティサービスへの要求を確実にすることに興味があるならば、成功してください.try LogRocket.

    ページのロード時間などのベースラインのパフォーマンスのタイミング、最初のバイト、低速ネットワークのリクエストにログを記録するためにあなたのアプリケーションをログ出力器、およびまた、RUDX、NGRX、およびVUEXのアクション/状態を記録します.Start monitoring for free.
    郵便How to build a web crawler with Node 最初に現れたLogRocket Blog .