ヘッドレスサーバーでヘッドレスブラウザを動かす(CentOS7.2 + Nightmare v2)

わたなべです。2017年もよろしくお願い致します!

さてさて、先日スクレイピングをしなくてはいけない機会がありまして、
その際にCentOS7.2 + Nightmare v2の組み合わせで少し苦労したので、
備忘録として残しておきます。

Nightmare

ヘッドレスブラウザにはNightmareを採用しました。
electronがベースにされているので、Google Chromeとほぼ同じ表現が取得できます。
またソースコードもかなり簡略化できます!

動作にはNodeJS 4.x以降を必要とします。
基本はnpmでインストールするだけで使えます。

前提条件

真っさらなCentOS7.2です。

[root@nightmare ~]# cat /etc/redhat-release 
CentOS Linux release 7.2.1511 (Core) 
[root@nightmare ~]# node -v
-bash: node: コマンドが見つかりません

NodeJSインストール

デフォルトの状態だとNodeJsはyumリポジトリに存在しないようです。

[root@nightmare ~]# yum install nodejs
読み込んだプラグイン:fastestmirror, remove-with-leaves, show-leaves
Loading mirror speeds from cached hostfile
 * base: www.ftp.ne.jp
 * extras: www.ftp.ne.jp
 * updates: www.ftp.ne.jp
パッケージ nodejs は利用できません。
エラー: 何もしません

今回はNodeSourceを使用することにしました。
とりあえず最新版を入れてみます。

curl -sL https://rpm.nodesource.com/setup_7.x | bash -
yum install -y nodejs

今日(2017/01/24)時点では7.4.0が入りました!

[root@nightmare ~]# node -v
v7.4.0

動作確認

[root@nightmare ~]# mkdir test
[root@nightmare ~]# cd test/
[root@nightmare test]# npm init

〜中略〜

[root@nightmare test]# npm install nightmare

〜中略〜

[root@nightmare test]# vi example.js

GitHubのExamplesをとりあえず使います。

var Nightmare = require('nightmare');       
var nightmare = Nightmare({ show: true });

nightmare
  .goto('https://duckduckgo.com')
  .type('#search_form_input_homepage', 'github nightmare')
  .click('#search_button_homepage')
  .wait('#zero_click_wrapper .c-info__title a')
  .evaluate(function () {
    return document.querySelector('#zero_click_wrapper .c-info__title a').href;
  })
  .end()
  .then(function (result) {
    console.log(result);
  })
  .catch(function (error) {
    console.error('Search failed:', error);
  });

実際に叩いてみます。

[root@nightmare test]# node example.js
[root@nightmare test]# 

なにも出力されません…Debug出力をONにしてみましょう。

[root@nightmare test]# DEBUG=* node example.js 
  nightmare queuing process start +0ms
  nightmare queueing action "goto" for https://duckduckgo.com +2ms
  nightmare queueing action "type" +2ms
  nightmare queueing action "click" +0ms
  nightmare queueing action "wait" +0ms
  nightmare queueing action "evaluate" +0ms
  nightmare running +0ms
  electron:stderr /root/test/node_modules/electron/dist/electron: error while loading shared libraries: libgtk-x11-2.0.so.0: cannot open shared object file: No such file or directory +30ms
  nightmare electron child process exited with code 127: command not found - you may not have electron installed correctly +2ms
  nightmare electron child process not started yet, skipping kill. +1ms
[root@nightmare test]# 

どうやらelectronの起動に失敗しているようです。

Xvfbのインストール

そもそもヘッドレスサーバー(GUIをインストールしていない)の場合は、
仮想ディスプレイが必要になります。
参考:HeadlessなLinux環境でNightmare(v2)を動かすためにしたこと@井村さん

Xvfbをインストールします。
今回は以下のものをyumでゴリッと入れてしまいました。

  • GConf2
  • atk
  • gdk-pixbuf2
  • glib2-devel
  • gtk2
  • libXScrnSaver-devel
  • libXcomposite
  • libXcursor
  • libXrandr
  • libXtst
  • libnotify
  • pango
  • xorg-x11-server-Xvfb
  • cups-libs
  • xorg-x11-fonts*
  • yum install GConf2 atk gdk-pixbuf2 glib2-devel gtk2 libXScrnSaver-devel libXcomposite libXcursor libXrandr libXtst libnotify pango xorg-x11-server-Xvfb cups-libs xorg-x11-fonts*

    さて動くでしょうか….

    [root@nightmare test]# DEBUG=* node example.js                                                                                                                                                    
      nightmare queuing process start +0ms
      nightmare queueing action "goto" for https://duckduckgo.com +3ms
      nightmare queueing action "type" +1ms
      nightmare queueing action "click" +0ms
      nightmare queueing action "wait" +0ms
      nightmare queueing action "evaluate" +0ms
      nightmare running +1ms
      nightmare electron child process exited with code 1: general error - you may need xvfb +48ms
      nightmare electron child process not started yet, skipping kill. +1ms
    [root@nightmare test]# 

    エラーが変わりましたね。Xvfb経由で実行する必要があるようです。

    [root@nightmare test]# xvfb-run node example.js 
    https://github.com/segmentio/nightmare

    帰ってきました!無事動きました:D

    スクリーンショットを取ってみる

    テキストは取得できるようになりました!ここまででも活用できますが、
    NightmareはスクリーンショットをPNGやPDFとして保存出来る機能があります。
    こちらも正しくスクリーンショットが取れるようになったか確認します。
    参考:Nightmareでスクリーンショットのサイズを変更してみる@polidogさん

    サンプルを引っ張ってきました。(URLだけこのブログに変えてあります!)

    const Nightmare = require('nightmare');
    const nightmare = Nightmare();
    
    Promise.resolve(
      nightmare
        .goto("http://labs.oratta.net/")
        .screenshot("test.png")
    ).then(()=>{
      console.log("screenshot create.");
    })
    nightmare.end();
    [root@nightmare test]# vi screen-shot.js
    [root@nightmare test]# xvfb-run node screen-shot.js 
    screenshot create.
    [root@nightmare test]# ll
    合計 28
    -rw-r--r--   1 root root  532  1月 24 19:10 example.js
    drwxr-xr-x 167 root root 8192  1月 24 19:04 node_modules
    -rw-r--r--   1 root root  200  1月 24 19:03 package.json
    -rw-r--r--   1 root root 2553  1月 24 19:33 test.png
    -rw-r--r--   1 root root  236  1月 24 19:32 screen-shot.js
    [root@nightmare test]# 

    ファイルがキチンと出来てますね。

    sshでダウンロードするのも面倒だったので、Gyazoに送りつけます。
    (本題と離れるので詳細は書きませんが、APIを叩くだけなので便利です!)

    [root@nightmare test]# curl -i https://upload.gyazo.com/api/upload -F access_token=YOUR-TOKEN -F "imagedata=@/root/test/test.png"

    https://gyazo.com/51ea8d75067508eacd66ef85d162b361
    文字化けしていたので、日本語フォントをインストールします。

    yum install ipa-*-fonts

    https://gyazo.com/0ef381635d96519c2a4cf8de2bc3cc16
    正しくスクリーンショットが撮れました!

    まとめ

    以上、GUIなしのCentOS上でNightmareが正しく動くようになりました。

    余談なのですが、今回作ったスクレイピングの処理で、
    タイムアウトが起きた際のtry-catchが抜けており、
    Xvfbのプロセスが残り続けてサーバーが応答不能になりました👀
    まだまだ精進します!

    作者 Jun Watanabe

    2010年11月ORATTA入社。エンジニアリーダーと名刺には書いてました。