Featured image of post NightmareJSで画面キャプチャを自動保存する方法【PhantomJS】

NightmareJSで画面キャプチャを自動保存する方法【PhantomJS】

ヘッドレスブラウザPhantomJSのラッパー「NightmareJS」で大量の画面キャプチャを自動保存する方法。インストールからスクリーンショット保存、複数画面の一括キャプチャ、レスポンシブ、スクロール領域の全体キャプチャまで♪

1910文字

大量のURLの画面キャプチャを自動化したい

もはやキャプチャ保存は人間のやる作業ではない

サイト開発等をしている場合、開発中の各画面のキャプチャを保存したいというケースがありますよね。

しかし、人力でブラウザで全てのページを開いてキャプチャツールを使って保存、、、なんて作業をするのはとても骨が折れます

そんな時は、NightmareJSを使って自動化しちゃいましょう!

nightmareとは?

NightmareJSとは、スクレイピングなどを行う際に多く利用されるPhantomJSという有名なヘッドレスブラウザをさらに扱いやすくしたラッパーツールです。

A high-level browser automation library. Nightmare

PhantomJSを使うとこう書いていた部分が、

PhantomJSの場合
phantom.create(function (ph) {
  ph.createPage(function (page) {
    page.open('http://yahoo.com', function (status) {
      page.evaluate(function () {
        var el =
          document.querySelector('input[title="Search"]');
        el.value = 'github nightmare';
      }, function (result) {
        page.evaluate(function () {
          var el = document.querySelector('.searchsubmit');
          var event = document.createEvent('MouseEvent');
          event.initEvent('click', true, false);
          el.dispatchEvent(event);
        }, function (result) {
          ph.exit();
        });
      });
    });
  });
});

こうなる。

NightmareJSの場合
yield Nightmare()
  .goto('http://yahoo.com')
  .type('input[title="Search"]', 'github nightmare')
  .click('.searchsubmit');

神かよ!!!

って事で早速使ってみました。

手順

phantomjsのインストール

nightmarejsはPhantomejsに依存しているのでまずはそちらをインストールします。

yarn add -D phantomjs
yarn add v1.17.3
warning package.json: No license field
warning No license field
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
warning No license field
success Saved 91 new dependencies.
info Direct dependencies
└─ phantomjs@2.1.7
info All dependencies
├─ ansi-styles@2.2.1
├─ asn1@0.2.4
├─ async@2.6.3
├─ aws-sign2@0.6.0
├─ balanced-match@1.0.0
├─ bcrypt-pbkdf@1.0.2
├─ bl@1.0.3
├─ brace-expansion@1.1.11
├─ caseless@0.11.0
├─ chalk@1.1.3
├─ combined-stream@1.0.8
├─ commander@2.20.0
├─ concat-map@0.0.1
├─ concat-stream@1.5.0
├─ core-util-is@1.0.2
├─ cryptiles@2.0.5
├─ dashdash@1.14.1
├─ debug@0.7.4
├─ delayed-stream@1.0.0
├─ ecc-jsbn@0.1.2
├─ escape-string-regexp@1.0.5
├─ extend@3.0.2
├─ extract-zip@1.5.0
├─ extsprintf@1.3.0
├─ fd-slicer@1.0.1
├─ forever-agent@0.6.1
├─ form-data@1.0.1
├─ fs-extra@0.26.7
├─ fs.realpath@1.0.0
├─ generate-function@2.3.1
├─ generate-object-property@1.2.0
├─ getpass@0.1.7
├─ glob@7.1.4
├─ graceful-fs@4.2.2
├─ har-validator@2.0.6
├─ has-ansi@2.0.0
├─ hasha@2.2.0
├─ hawk@3.1.3
├─ http-signature@1.1.1
├─ inflight@1.0.6
├─ inherits@2.0.4
├─ is-my-ip-valid@1.0.0
├─ is-my-json-valid@2.20.0
├─ is-property@1.0.2
├─ is-stream@1.1.0
├─ is-typedarray@1.0.0
├─ isarray@1.0.0
├─ isexe@2.0.0
├─ isstream@0.1.2
├─ json-schema@0.2.3
├─ json-stringify-safe@5.0.1
├─ jsonfile@2.4.0
├─ jsonpointer@4.0.1
├─ jsprim@1.4.1
├─ kew@0.7.0
├─ klaw@1.3.1
├─ lodash@4.17.15
├─ mime-db@1.40.0
├─ mime-types@2.1.24
├─ minimatch@3.0.4
├─ minimist@0.0.8
├─ mkdirp@0.5.0
├─ node-uuid@1.4.8
├─ oauth-sign@0.8.2
├─ pend@1.2.0
├─ phantomjs@2.1.7
├─ pinkie@2.0.4
├─ process-nextick-args@1.0.7
├─ progress@1.1.8
├─ qs@5.2.1
├─ readable-stream@2.0.6
├─ request-progress@2.0.1
├─ request@2.67.0
├─ rimraf@2.7.1
├─ safer-buffer@2.1.2
├─ sntp@1.0.9
├─ sshpk@1.16.1
├─ string_decoder@0.10.31
├─ stringstream@0.0.6
├─ strip-ansi@3.0.1
├─ supports-color@2.0.0
├─ throttleit@1.0.0
├─ tough-cookie@2.2.2
├─ tunnel-agent@0.4.3
├─ tweetnacl@0.14.5
├─ typedarray@0.0.6
├─ util-deprecate@1.0.2
├─ verror@1.10.0
├─ which@1.2.14
├─ xtend@4.0.2
└─ yauzl@2.4.1
✨  Done in 36.27s.

NightmareJSのインストール

次にNightmareJSをインストールします。

yarn add -D nightmare
yarn add v1.17.3
warning package.json: No license field
warning No license field
[1/4] 🔍  Resolving packages...
warning nightmare > electron > electron-download > nugget > progress-stream > through2 > xtend > object-keys@0.4.0:
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
warning No license field
success Saved 85 new dependencies.
info Direct dependencies
└─ nightmare@3.0.2
info All dependencies
├─ @types/node@8.10.53
├─ ajv@6.10.2
├─ array-find-index@1.0.2
├─ asynckit@0.4.0
├─ aws4@1.8.0
├─ buffer-from@1.1.1
├─ camelcase-keys@2.1.0
├─ camelcase@2.1.1
├─ clone@1.0.4
├─ code-point-at@1.1.0
├─ commander@1.0.4
├─ currently-unhandled@0.4.1
├─ decamelize@1.2.0
├─ deep-defaults@1.0.5
├─ deep-extend@0.6.0
├─ defaults@1.0.3
├─ electron-download@3.3.0
├─ electron@2.0.18
├─ enqueue@1.0.2
├─ error-ex@1.3.2
├─ es6-promise@4.2.8
├─ fast-deep-equal@2.0.1
├─ fast-json-stable-stringify@2.0.0
├─ find-up@1.1.2
├─ function-source@0.1.0
├─ har-schema@2.0.0
├─ home-path@1.0.6
├─ hosted-git-info@2.8.4
├─ indent-string@2.1.0
├─ ini@1.3.5
├─ is-arrayish@0.2.1
├─ is-finite@1.0.2
├─ is-fullwidth-code-point@1.0.0
├─ is-utf8@0.2.1
├─ jsesc@0.5.0
├─ json-schema-traverse@0.4.1
├─ keypress@0.1.0
├─ load-json-file@1.1.0
├─ loud-rejection@1.6.0
├─ map-obj@1.0.1
├─ meow@3.7.0
├─ minimist@1.2.0
├─ minstache@1.2.0
├─ mkdirp@0.5.1
├─ ms@2.0.0
├─ multiline@1.0.2
├─ nightmare@3.0.2
├─ normalize-package-data@2.5.0
├─ nugget@2.0.1
├─ object-assign@4.1.1
├─ object-keys@0.4.0
├─ parse-json@2.2.0
├─ path-exists@2.1.0
├─ path-parse@1.0.6
├─ path-type@1.1.0
├─ performance-now@2.1.0
├─ pretty-bytes@1.0.4
├─ progress-stream@1.2.0
├─ psl@1.4.0
├─ punycode@1.4.1
├─ rc@1.2.8
├─ read-pkg-up@1.0.1
├─ read-pkg@1.1.0
├─ redent@1.0.0
├─ repeating@2.0.1
├─ resolve@1.12.0
├─ safe-buffer@5.1.2
├─ semver@5.7.1
├─ signal-exit@3.0.2
├─ single-line-log@1.1.2
├─ sliced@1.0.1
├─ spdx-correct@3.1.0
├─ spdx-exceptions@2.2.0
├─ speedometer@0.1.4
├─ split2@2.2.0
├─ string-width@1.0.2
├─ strip-bom@2.0.0
├─ strip-indent@1.0.1
├─ strip-json-comments@2.0.1
├─ sumchecker@1.3.1
├─ through2@2.0.5
├─ trim-newlines@1.0.0
├─ uri-js@4.2.2
├─ uuid@3.3.3
└─ validate-npm-package-license@3.0.4
✨  Done in 93.50s.

スクリーンショットを保存

まずは試しに、Googleで検索した結果をキャプチャ保存するスクリプトを書いてみます。

[adsense-responsive]

sample.js
const Nightmare = require('nightmare')
const nightmare = Nightmare({ show: true})// ブラウザの設定

nightmare
  .goto('https://google.com') // 表示するURL
  .type('input[type=text]', 'blogenist') //タイピング対象のセレクタと入力値
  .click('input[name=btnK]')// クリック対象のセレクタ
  .wait('#resultStats') // 表示されるまで待つセレクタ
  .screenshot("./sample.png") // キャプチャの保存設定
  .end() // 終了
  .catch(error => {
    console.error('Search failed:', error)
  })

また、yarnで簡単に起動するためにpackage.jsonscriptsに以下の起動スクリプトを追記しておきます。

package.json
{
    "scripts": {
        "capture": "node ./capture.js" // 追加
    },
    "devDependencies": {
        "nightmare": "^3.0.2",
        "phantomjs": "^2.1.7"
    },
    "dependencies": {}
}

確認

では、実際にyarn captureで起動してみましょう。

正しく検索操作とキャプチャ保存も出来ていますね♪

複数画面を自動キャプチャ保存

では、応用として複数画面のキャプチャを自動で保存してみましょう。

今回は、config.jsonというファイルにキャプチャしたいページを管理し、ループ処理で自動保存する形式にしようと思います。

config.json
{
    "pages": [
        {
            "name": "ページ1",
            "url": "/2019/09/09/9157/"
        },
        {
            "name": "ページ2",
            "url": "/2019/09/08/9128/"
        },
        {
            "name": "ページ3",
            "url": "/2019/09/06/9121/"
        }
    ]
}

sample.js
const Nightmare = require('nightmare')
const fs = require('fs');
const config = require('./config.json');


config.pages.forEach((page) => {
    const nightmare = Nightmare({ show: false,width: 1920})
    const dist = `./dist/${page.name}/`;
    const fileName = `${page.name}.png`;

    if (!fs.existsSync(dist)) {
        fs.mkdirSync(dist);
    }
    nightmare
        .goto(page.url)
        .wait(1000)
        .screenshot(`${dist}/${fileName}`)
        .end() // 終了
        .catch(error => {
            console.error('Search failed:', error)
        })
});

レスポンシブも対応

さらに以下のようにデバイス情報を追記し、

[adsense-responsive]

config.json
{
    "pages": [
        {
            "name": "ページ1",
            "url": "/2019/09/09/9157/"
        },
        {
            "name": "ページ2",
            "url": "/2019/09/08/9128/"
        },
        {
            "name": "ページ3",
            "url": "/2019/09/06/9121/"
        }
    ],
    "devices": [
        {
            "name": "PC",
            "width": 1920,
            "height": 1080
        },
        {
            "name": "Tablet",
            "width": 768,
            "height": 1024
        }
    ]
}

sample.js
const Nightmare = require('nightmare')
const fs = require('fs');
const config = require('./config.json');


config.devices.forEach((device) => {
    config.pages.forEach((page) => {
        const nightmare = Nightmare({ show: false,width: device.width, height: device.height})
        const dist = `./dist/${page.name}/`;
        const fileName = `${device.name}-${page.name}.png`;

        if (!fs.existsSync(dist)) {
            fs.mkdirSync(dist);
        }
        nightmare
            .goto(page.url)
            .wait(1000)
            .screenshot(`${dist}/${fileName}`)
            .end()
        })
    });
});

ネストループする事で一気に2端末分のレスポンシブデザインの確認をするためのキャプチャも取れてしまいます・・・!

かなり便利です!!!

スクロール領域のキャプチャ

次は縦スクロールが発生する画面のキャプチャを撮ってみましょう。

[adsense-responsive]

sample.js
const Nightmare = require('nightmare')
const fs = require('fs');
const config = require('./config.json');


    config.pages.forEach((page) => {
        const nightmare = Nightmare({ show: false,width: device.width, height: device.height})
        const dist = `./dist/${page.name}/`;
        const fileName = `${device.name}-${page.name}.png`;

        if (!fs.existsSync(dist)) {
            fs.mkdirSync(dist);
        }
        nightmare
            .goto(page.url)
            .wait(1000)
            .evaluate(() => {
                return {
                    width: document.body.scrollWidth,
                    height: document.body.scrollHeight
                };
            })
            .then(({ width, height }) => {
                nightmare.viewport(width, height);
            })
            .then(() => {
                nightmare.screenshot(`${dist}/${fileName}`)
            .end()
            .catch(error => {
                 console.error('Search failed:', error)
                 })
            })
});

確認

正常にスクロールが発生する画面でも全体のキャプチャが取れました。

終わりに

以上のように、簡単に画面キャプチャを撮る事が出来ました。

たくさんの画面キャプチャを取らないといけない場合は、あらかじめ導入しておくととても便利ですので是非試して見てください♪