DevRel

nightmare.jsとgmを使ってnode.jsでLIGブログの更新をSlackでゲットだぜ

nightmare.jsとgmを使ってnode.jsでLIGブログの更新をSlackでゲットだぜ

こんにちは、まろCです。
年末に遊びすぎて風邪を引き、他の人より少し多いけど全然うれしくない11連休を過ごしました。

今回は、PhantomJSをラッパーしたnightmare.jsを使って、WebページのキャプチャのDiffを取り、サイト更新の監視をするものを作ってみます。

LIGブログを見張って一定時間毎にトップページのキャプチャ画像を撮り、1つ前のキャプチャ画像と差異があったとき、記事がリリースされ画面が更新されたと判定します。そうすると、Slackにligblog_watchbotが通知してくれるという仕組みです。

準備

主に使うもの

  • node.js
  • nightmare -> サイトのキャプチャに使います。
  • gm -> 画像のDiffを見るのに使います。
  • MongoDB -> キャプチャのログを記録します。

※ソースは全てCoffeeScriptで書いています。

インストール

環境は、Mac OS Xです。

$ brew install mongodb
$ brew install imagemagick
$ brew install graphicsmagick
$ cd ~
$ mkdir kansi
$ npm init
$ npm install -g phantomjs
$ npm install nightmare --save-dev
$ npm install mongoose --save-dev
$ npm install gm --save-dev
$ npm install dateformat --save-dev
$ npm install time --save-dev
$ npm install cron --save-dev
$ npm install request --save-dev
$ npm install slack-notify --save-dev</pre>

実装する

1. nightmareでスクリーンショットを保存して、DBにも画像名を保存

capture.coffeeではnightmareを実行し、captureModelでMongooseを使ってMongoDBへの保存を行います。Mongooseでモデルを設定する際、コレクション名を設定しなければ、DB名の複数形になるようです。

caputure.coffee(抜粋)

SETTING = require './setting.coffee'
Nightmare = require('nightmare')
dateformat = require('dateformat')
CaptureModel = require('./captureModel.coffee')

class Capture
    constructor: (@target) ->;
        @nightmare = new Nightmare()
    capture: (callback) ->;
        date = dateformat new Date(), 'yyyymmddHHMMss'
        imgName = "#{date}.png"
        @nightmare
            .goto @target
            .screenshot SETTING.CAPTURE_PATH+ imgName
            .run (err, nightmare) ->;
                #save
                saveData = new CaptureModel(
                    name: "#{date}.png"
                    createAt: date
                )
                saveData.save (err, newData) ->;
                    console.log err if err
                    return callback(imgName)
                # captureModel.capture.find (err, data) ->;
                #     console.log data
                console.log '====Capture capture====', 'capture saved.'
module.exports = Capture

caputureModel.coffee(抜粋)

mongoose = require('mongoose')

mongoose.connect 'mongodb://localhost/capture', (err) ->;
    console.log err
db = mongoose.connection
CaptureSchema = new mongoose.Schema({
    name: String
    createAt: String
})
CaptureModel = mongoose.model 'capture', CaptureSchema

module.exports = CaptureModel

実行するとキャプチャが保存されます。

キャプチャ画像

背景や、擬似要素のところできちんと表示できない部分がありますね。

2. gmで画像のDiffを調査

compareというメソッドで画像のDiffを調べることができます。2つの画像のパスを渡してあげると結果が返ってきます。

diff.coffee(抜粋)

class Diff
    constructor: () ->;
        date = dateformat new Date(), 'yyyymmddHHMMss'
        @options =
        file: SETTING.CAPTURE_PATH+ "diff-#{date}.png"
        highlightColor: 'yellow'
        tolerance: 0.02
    compare: (oldCpature, newCapture, callback) ->;
        console.log oldCpature, newCapture
        gm.compare oldCpature, newCapture, @options, (err, isEqual, equality, raw) ->;
        console.log 'The images were equal: %s', isEqual
        console.log 'Actual equality: %d', equality
        console.log raw
        callback isEqual

module.exports = Diff

昨日のスクショと、本日のスクショを比べて、diff.pngとして出力。コンソールにDiffの情報を出力します。

実行すると、optionで設定したパスに画像が出力されています。

キャプチャ画像

画像の差がある箇所が黄色でハイライトされています。
記事が更新されているので、一覧の箇所が黄色になっていますね。

The images were equal: false
Actual equality: 0.0268236487
Image Difference (MeanSquaredError):
           Normalized    Absolute
          ============  ==========
     Red: 0.0311492206        7.9
   Green: 0.0285191391        7.3
    Blue: 0.0293348867        7.5
 Opacity: 0.0182913485        4.7
   Total: 0.0268236487        6.8

The images were equal: false ここがfalseのときにSlack通知が行くようにします。

3. 一定間隔で呼んでDBに保存

node-cronを使って定期的に上記の処理を呼びます。
1時間くらいで設定します。
サーバーのcrontabと同じような時間設定ができるんですが、秒から始まるので注意が必要です。

app.coffee(抜粋)

SETTING = require './setting.coffee'
CronJob = require('cron').CronJob
Capture = require './capture.coffee'

job = new CronJob(
    cronTime: '* * */1 * * *'
    onTick: () ->;
        capture = new Capture SETTING.TARGET_URL
        capture.capture (_id) ->;
            #diff
    start: true
    timeZone: "Asia/Toyko"
)
job.start()

4. Slackに通知

Slackの画面からIncoming WebHooksの設定をします。

画像

/services/newsからIncomming WebHookをAddします。
Save Settingをクリックしたら準備完了です。node側からPOSTしてあげます。
slack-notifyを使えば一発でした。

app.coffee(抜粋)

slack = require('slack-notify')(SETTING.WEBHOOK_URL)

slack.send(
    channel: '#hubot'
    icon_url: 'https://s3-us-west-2.amazonaws.com/slack-files2/avatars/2014-12-28/3299377210_188baaf32b54f98b381d_36.jpg'
    text: 'ブログが更新されました。 http://liginc.co.jp',
    unfurl_links: 1,
    username: 'ligblog_watchbot'
)

画像

こんなかんじでSlackに流れます。

組み合わせて実行

$ coffee app.coffee

最終的な実行コマンドは上のようになります。

こんな感じで動きます。

まとめ

ソースをGitHubに公開しました。
https://github.com/ktkt-/web_watch

イノベーションは組み合わせ」と、誰かが言ってたような気がします。
それは、「モノxモノ」「モノxヒト」「ヒトxヒト」のすべてがそうではないでしょうか。
こういうLIGブログの楽しみ方もありかなと思います。それでは。

 

【JSでもっとたくさんのことができる!】

Node.js完全初心者が、モジュールを作成して、ディレクトリを非同期で読み取り、ファイルをコンソールに出力する方法

milkcocoaとgmaps.jsで、スマホ(と、まろ氏)の位置情報をリアルタイムに取得してみた

Web制作者でもネイティブアプリが作れる!node-webkitを使ってみよう。

チャットツールをGitとNode.jsとHerokuでDIYする方法〜相手作成編〜

チャットツールをGitとNode.jsとHerokuでDIYする方法〜画面作成編〜

この記事を書いた人

まろ
まろ フロントエンドエンジニア 2014年入社
フロントエンドエンジニアのまろCです。
コンセプト設計中心でものづくりしています。