学生アルバイトのMoriです。
今回は Node.js を使って静的なWebページのスクレイピングをしたいと思います。

目次

1. Node.jsとは

ご存知の方も多いかと思いますが、念のため簡単な解説をします。
Node.jsとは非同期処理をサーバーサイドで扱うためのJavascript環境です。
JavaScriptの特徴であるノンブロッキングな動作をするので、I/O待ちなどの処理をなくすことにより大量の処理を効率的に捌くことができます。(他にもありますが、知りたい方はご自分でお願いします)

2. なぜNode.jsでスクレイピングするのか

弊社ではスクレイピングにRubyを用いることがとても多いため、他の言語ではどのように実装するのか知りたかったからです。Ruby以外のスクレイピングでよく用いられる言語にPythonがありますが、そちらはネット上の日本語記事も多かったため、比較的少ないNode.jsで実装してみることにしました。

3.今回の目標

今回は弊社のホームページのニュース一覧から、タイトルと公開日時を取得したいと思います。取得した情報は後述のような構造のデータベースに入れます。

スクレイピング対象

4. 今回使うデータベースの構造

以下のような構造のnewsテーブルに情報を保存したいと思います テーブル構造

5. 今回のスクレイピングに使うモジュール

今回スクレイピングに使いたいモジュールは以下の3つです。

  • request-promise
  • fast-html-parser
  • knex

それぞれのモジュールの役割は

  • リクエストの送信・受信
  • HTML解析
  • データベースへの保存

です。

以上のモジュールは次のようにして、インストールしてください

npm install --save request-promise
npm install --save fast-html-parser
npm install --save knex 
npm install --save mysql //ご自身がお使いのDBにあったモジュールを入れてください

6.ソース

//モジュールの読み込み
const HTMLParser = require('fast-html-parser');
const request = require("request-promise");
const knex = require('knex')({
  client: 'mysql',
  version: '5.7',
  connection: {
    host : 'localhost',
    user : 'root',
    password : '',
    database : 'nodejs-crawl'
  }
});

//GETリクエストを投げる
request.get("https://rooter.jp/news/")
.then(function (html) {
  //取得したHTMLをパース
  var root = HTMLParser.parse(html);
  var promises = [];
  for (var news of root.querySelectorAll('article.news.tile')){
      title        = news.querySelector('.article-title').text.trim()
      published_at = news.querySelector('.time').attributes['datetime']
      //DBに保存する処理をプロミスの配列に格納
      promises.push(knex.insert({'title':title, 'published_at':published_at}).into('news'))
  }
  //DBへの保存処理を実行
  Promise.all(promises).then(function (results){
    console.log(results);
    //DBの保存処理がすべて終了したら、knexを解放
    knex.destroy()
  }).catch(function (errors){
    console.log(errors);
  })
}).catch(function (err){
  console.log(err)
});

7. ソースの解説

ソースコードのポイントは4つあります。

7.1 データベースに接続

以下のような書き方でデータベースと接続することができます。
他にも設定できる項目がありますが、今回は単純化のため紹介しません。
注意点として、clientに指定するDBクライアントのモジュールはお使いの環境に合わせてインストールしてください。

const knex = require('knex')({
  client: 'your_database_client',
  version: 'your_client_version',
  connection: {
    host : 'your_host',
    user : 'your_user_name',
    password : 'your_password',
    database : 'your_database_name'
  }
});

注意点ですが、knexモジュールを使い終わったら

knex.destroy()

をしてDBとの接続を解除してあげましょう。この処理を行わないとスクリプトが終了しません。

7.2 GETリクエストを投げる

単純なGETリクエストを投げるだけなら、以下のように書けます。 requestモジュールと比べてPromiseが使えるため、request-promiseモジュールは非常にスッキリと書くことができます。

request.get(url)
.then(function(body){
  //レスポンスのBodyに対して何かの処理
}).catch(function(err){
  //エラー処理
})

7.3 HTMLのパース

var root = HTMLParser.parse(html);

でDOMツリーを作成して、

querySelectorAllメソッドや.querySelectorメソッドでCSSを使った要素ノードの指定ができます。(もちろん、getElementByIdなどのメソッドも使えます)

7.4 データベースに保存

以下のような方法で任意のテーブルにレコードを挿入できます。
knex.insert({'column1':'data1', 'column2':'data2'...}).into('table_name')
.then({
  //データベースに保存に成功した後の何らかの処理
}).catch({
  //データベースに保存に失敗した後の何らかの処理
})

8. 結果 

上のソースを実行した結果として、データベースの中身は以下のようになりました。 記事のタイトルと公開日が設定できてるようです。

結果

9. 最後に

無事実装できました。
慣れないせいもありますが、Ruby・Pythonより頭を使って書かなきゃいけないので、あまり書きたくはないかもです(笑)
しかし、非同期の性質やエンジンの高速さを考えるとパフォーマンスが求められるときは選択肢に入るのかなと思いました。
今回は静的なWebページのスクレイピングを例にあげましたが、Node.jsには動的なWebページなどを取得するためのchromeヘッドレスのラッパーであるpuppeteerというモジュールがあります。弊社のブログではpuppeteerについての記事も載せているため、是非そちらも合わせてご覧ください。

10. 参考にしたWebページ(2018/11/01時点)

コード作成に当たって、非常に助かりました。