ほんじゃらねっと

ダイエット中プログラマのブログ

Node.jsでWebサイトの更新チェックを自動化する

f:id:piro_suke:20160501015354j:plain

特定のWebサイトを定期的にチェックして特定のキーワードが含まれていたら

通知してくれる仕組みを1つ覚えておくと色々重宝する。

例えば

会社に社員用Webサイトがあるなら、

(そしてそのサイトがRSSもメール通知も提供してないなら)

そこに「重要」とか「人事」とかのキーワードを含む記事が投稿された時に

通知を受け取れるようにしておけば、

いちいちサイトをチェックしにいかなくても

逃さず確認できる。

また、Webスクレイピングする方法を覚えておけば、

通知を受け取る以外にも、

画面キャプチャの撮影や画像やテキストの収集を

自動化したり、作成したWebシステムの画面テストを行う、

等の応用もしやすくなる。

今回は Node.js + Grunt + cheerio-httpcli を使用して

手軽にWebサイトのデータをチェックする方法を紹介する。

今週の「世界の果てまでイッテQ!」で「QTube」を放送するかをチェックする

実はうちの長女がロッチ中岡のQTubeのコーナーを気に入っていて、

「今週は中岡は出ないのか?」と大層楽しみにしている。

しかし、残念ながら不定期で放送されるので見逃すことが多い。

パパのプログラムスキルが家族の役に立てるチャンス!ということで

毎週日本テレビの番組表をチェックして

「QTube」を放送するかどうかをチャットワークに通知する

スクリプトを作ってみた。

日本テレビの今週の番組表は下記のページで確認できる。

www.ntv.co.jp

各番組の詳細は下記のような詳細ページにリンクされているようだ。

http://www.ntv.co.jp/program/detail/21849765.html

明日(5/1)は「QTube」を放送するらしい。

HTMLのソースを確認したところ、

番組表のページで「世界の果てまでイッテQ」が

含まれるリンクから詳細ページを開き、

そこの「番組内容」の中に「中岡QTube」が含まれていたら

放送されるとみて良さそうだ。

チェックスクリプトを書く

「qtube_notifier」というプロジェクト名をつけた。

package.json

{
  "name": "qtube_notifier",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "cheerio-httpcli": "^0.6.8",
    "grunt": "^0.4.5",
    "grunt-exec": "^0.4.6",
    "grunt-http": "^2.0.1"
  }
}

Gruntfile.js

module.exports = function(grunt) {
    grunt.initConfig({
        http: {
            notify: {
                options: {
                    url: "https://api.chatwork.com/v1/rooms/<チャットワークルームID>/messages",
                    method: "POST",
                    headers: {
                         "X-ChatWorkToken": "<チャットワークAPIキー>"
                    },
                    form: {
                        body: "QTube情報はありません" 
                    }
                }
            }
        },
        exec: {
            check_qtube_schedule: {
                cmd: "node index.js",
                callback: function (err, stdout, strerr) {
                    if (stdout) {
                        if (stdout.trim() != "") {
                            grunt.config("http.notify.options.form.body", "QTube情報です:\n" + stdout);
                        }
                    }
                    if (err) {
                        grunt.config("http.notify.options.form.body", "QTubeチェックスクリプトでエラーです:\n" + err);
                    }
                }
            }
        }
    });
    
    grunt.loadNpmTasks("grunt-exec");
    grunt.loadNpmTasks("grunt-http");
    grunt.registerTask("default", ["exec:check_qtube_schedule", "http:notify"]);
};

index.js

var cheerioCli = require('cheerio-httpcli');

cheerioCli.fetch("http://www.ntv.co.jp/program/", {})
.then(function (result) {
    var linkList = result.$("h3:contains('世界の果てまでイッテQ')");
    var detailLink = linkList[0].parent.attribs.href;
    //console.log(detailLink);
    return cheerioCli.fetch("http://www.ntv.co.jp/program/" + detailLink, {});
})
.then(function (result) {
    var oaDateTime = result.$("h3.oaDate").text();
    console.log(oaDateTime);
    var programText = result.$("div#programArea .program p").text();
    if (/中岡QTube/.test(programText)) {
        console.log("QTubeあります!");
        console.log(result.$.documentInfo().url);
    } else {
        console.log("QTubeありません。");
    }
})
.catch(function (err) {
    console.log(err);
})
.finally(function () {
    //console.log("done");
});

Node.jsとGrunt-cliがインストールされた環境で、

上記3ファイルをqtube_notifierというフォルダに入れて

チャットワーク情報を編集し、

npm install

を実行後

grunt

を実行すると今週「QTube」を放送するかどうかが

チャットワークに通知される。

あとは毎週実行されるように

タスクスケジューラなりcrondなりに

スケジュールを登録しておく。

blog.honjala.net

詳細ページの内容は更新されるようなので、

放送曜日(日曜)前の金曜日くらいに

このスクリプトが流れるようにするのが良さそうだ。

内容説明

今回のスクリプトの構成は、

「Webサイトにアクセスして内容をチェックする」部分と

「チャットワークに通知する」部分に分けている。

チャットワークに通知する部分については、

過去の記事と同様にGruntのgrunt-httpパッケージを

使っている。

Gruntfile.js

       http: {
            notify: {
                options: {
                    url: "https://api.chatwork.com/v1/rooms/<チャットワークルームID>/messages",
                    method: "POST",
                    headers: {
                         "X-ChatWorkToken": "<チャットワークAPIキー>"
                    },
                    form: {
                        body: "QTube情報はありません" 
                    }
                }
            }
        },
...

    grunt.loadNpmTasks("grunt-http");

これだけの記述で使いまわせるのは楽で良い。

Webサイトにアクセスして内容をチェックする部分については、

Gruntからindex.jsというNode.jsスクリプトを呼び出している。

ここでは「grunt-exec」という外部コマンドを呼び出せるパッケージを

使用している。

www.npmjs.com

以前は「grunt-run」というパッケージを使っていたのだが、

「grunt-exec」はコマンドの出力をGrunt側で受け取れるので、

チェック結果をチャットワークに流すためにこちらを使用した。

       exec: {
            check_qtube_schedule: {
                cmd: "node index.js",
                callback: function (err, stdout, strerr) {
                    if (stdout) {
                        if (stdout.trim() != "") {
                            grunt.config("http.notify.options.form.body", "QTube情報です:\n" + stdout);
                        }
                    }
                    if (err) {
                        grunt.config("http.notify.options.form.body", "QTubeチェックスクリプトでエラーです:\n" + err);
                    }
                }
            }
        }
    });
    
    grunt.loadNpmTasks("grunt-exec");

コマンドとして「node index.js」を実行するように指定し、

出力内容を「http.notify.options.form.body」、

つまりチャットワークのメッセージ内容に設定している。

grunt-execで何かしらのスクリプトを実行し、

grunt-configで実行結果をチャットワークメッセージに設定し、

grunt-httpでチャットワークにその内容を通知する、

という組み合わせは今後も色々応用できそうだ。

index.js内では、実際に番組表のページにアクセスして

内容をチェックしている。

Webスクレイピング用のライブラリやツールは

Javascriptで使えるものでも色々あるようだが、

今回はcheerio-httpcliを使ってみた。

取得したページの内容にjQuery風にアクセスできるのが良い。

www.npmjs.com

処理内容としては:

  • fetchで番組表ページの内容を取得する
  • 取得内容から「世界の果てまでイッテQ」を含むh3タグを取り出す
  • 見つかったh3タグの親aタグのリンク先(詳細ページ)を取得する
  • 詳細ページの内容を取得する
  • 「oaDate」というclass名を持つh3タグから放送日時を取得し、出力する
  • 「programArea」というid名を持つdivタグのテキストを取得する
  • テキストに「中岡QTube」が含まれるかチェックする
  • 含まれていたら「QTubeあります!」というメッセージと詳細ページURLを出力する
  • 含まれていなければ「QTubeありません」と出力する

という内容になっている。

おわり

今回のスクリプトを応用するには、

cheerio-httpcliの機能を学んでindex.jsの内容を書き換える必要があるが、

フロントエンドでjQueryに触れたことのある方ならば

それほどとっつきにくいものではないと思う。

今回はスクレイピングというほどの内容ではなかったが、

またいずれ一括取得系スクリプトも作成してみたい。

JS+Node.jsによるWebクローラー/ネットエージェント開発テクニック

JS+Node.jsによるWebクローラー/ネットエージェント開発テクニック