AWS-EC2(Amazon Linux)にGoogleChromeをインストールしてNode+Selenium3でスクリーンショットを取る

  • 6
    いいね
  • 0
    コメント

はじめに

ブラウザでレンダリングされたスクリーンショットを取る必要があり、EC2にインストールする奮闘した備忘録を残しておきます。今後、他のエンジニアの人が苦悩をしないためにも…。

そもそも

Amazon Linuxが提供しているリポジトリは古い。更に他のリポジトリを入れようと競合する。
本当に厄介でした。ローカルの開発環境はCentOS7で、インストールはすんなりいったので、
AmazonLinuxでも普通にいけるでしょーと思っていた私が間違っていました…。
戦いはこれから始まるのです…

Google Chrome 59のインストールにはgtk3, gdk3, atk, libXssが必要

これがまた厄介。AmazonLinux上でgtk3, gdk3, atk, libXssをインストールする文献が全くもってない。
Ubuntuならともかく、Amazon Linuxの情報がインターネット上にない。
適当にrpm引っ張って入れれば行けるかと思いきや依存関係が多すぎて更にその依存関係を入れようとすると更に依存関係のライブラリが必要になる。考えただけで地獄。

Amazon Linux にはそもそもgtk2, gdk2すら入っていない

そりゃそうですよね。だってCLIで動くことだけを考えて作られてるんだもん。
もうやだこの時点で絶望。

それでも私は戦う道を選んだ

Red Hat Enterpriseに逃げる選択肢もあった。でもお金は極力かけたくない。そんな上からの<社会的フィルター>を叶えるために、私はAmazon LinuxにGoogleChromeというブラウザを入れるための戦い挑んだのでした。

インストールするまえの前置き

結論から言うとGoogleChrome 59を入れるのが間違い。GoogleChromeは58から59に切り替わった時点で、gtk2から、gtk3, gdk2からgdk3に使用するライブラリを切り替えています。
でもgtk2, gdk2ならどうだろう?ワンチャン行けそうな気がしてきた。というわけでインストールするのは現状のstable版の59ではなく58です。(そもそも論としてGoogle ChromeのバイナリのバージョンがSelenium3は58以上じゃないと動かなかったです。)

ソースは私。公開されているGoogle Chrome Version 49から59まで片っ端から入れて確認しました。

とりあえず依存関係を入れてGoogle Chromeを入れる

Amazon LinuxはRHELベースなので、それに合わせてrpmを入れまくる。
(海外のミラーサーバーなので一回失敗すると何度もダウンロードするはめになるので、先にwgetで取得しておく。)


cd /tmp

# atk
$ wget ftp://rpmfind.net/linux/centos/7.3.1611/os/x86_64/Packages/atk-2.14.0-1.el7.x86_64.rpm
$ sudo yum install atk-2.14.0-1.el7.x86_64.rpm

# gtk2 (このバージョンじゃないと後ほどGoogleChromeでライブラリに不足がでているというエラーがでます。)
$ wget ftp://rpmfind.net/linux/centos/6.9/os/x86_64/Packages/gtk2-2.24.23-9.el6.x86_64.rpm
$ sudo yum install gtk2-2.24.23-9.el6.x86_64.rpm

# gtk2-devel
$ wget ftp://rpmfind.net/linux/centos/6.9/os/x86_64/Packages/gtk2-devel-2.24.23-9.el6.x86_64.rpm
$ sudo yum install gtk2-devel-2.24.23-9.el6.x86_64.rpm

# libXss
$ wget ftp://rpmfind.net/linux/centos/7.3.1611/os/x86_64/Packages/libXScrnSaver-1.2.2-6.1.el7.x86_64.rpm
$ sudo yum install libXScrnSaver-1.2.2-6.1.el7.x86_64.rpm

# GoogleChrome 58
$ wget http://orion.lcg.ufrj.br/RPMS/myrpms/google/google-chrome-stable-58.0.3029.110-1.x86_64.rpm
$ sudo yum install google-chrome-stable-58.0.3029.110-1.x86_64.rpm

(記憶が曖昧なので上記の依存関係で解決できるかわかりません…足りないようでしたら教えていただければ情報提供致します。)

これで、Google Chromeのインストールは完了です。これを見つけるのに本当に時間がかかりました。泣きたい。

Selenium Standaloneを落としてくる。

Selenium Standaloneを落としてきます。もちろんjar形式のものです。それ以外には目を向けてはいけません。闇が潜んでいます。
http://docs.seleniumhq.org/download/

スクリーンショット_2017-07-18_5_30_23.png

nvmをインストールしてnodeとnpmを使えるようにする

nvmをインストールします。、

$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash

~/.bash_profileに下記を追加します。

[ -s "~/.nvm/nvm.sh" ] && . "~/.nvm/nvm.sh"

その後下記で反映させます。

$ source ~/.bash_profile

nvmでnodeをインストールします。

$ nvm install stable

最新版のnodejsがインストールされるので、それを使うように設定します。

$ nvm use 8.1.4 #インストールされたnodeのバージョン

Seleniumを動かすためのWebdriverとNode用のseleniumをインストールします。


$ cd /path/to # プロジェクトなどのディレクトリ
$ npm install selenium
$ npm install selenium-webdriver
$ npm install chromedriver

あと、Javaと仮想ディスプレイも入れないと

Amazon Linuxにはもちろんディスプレイはついていないので、仮想ディスプレイのXvfbを導入します。
ついでにJavaも入れます。Selenium3の必要要件はJDK1.8以上です。

$ sudo yum install Xvfb java-1.8.0-openjdk java-1.8.0-openjdk-devel GConf2

(注釈: 入れたあとにjava -versionでバージョンを確認してください。1.8.0になっていれば問題ありませんが、それ未満だとSeleniumが動作しませんので、$JAVA_HOMEなどでjdk1.8.0までのパスを適切に通してください。)
(注釈2: 日本語フォントがインストールされていないともしかしたら文字化けする可能性があります。無償利用可能なウェブフォントやAdobeが公開しているSource Han Code JPを入れると幸せになれるかもです。フォントのインストールは割愛します)

ここからが楽しいSeleniumでスクショを取る。

ようやく下準備は整いました。いよいよ Node+Selenium3でスクリーンショットを取ります。
まず、仮想ディスプレイを起動します。

$ Xvfb :90 -ac -screen 0 1024x768x24 > /dev/null 2>&1  &

Selenium3を起動します。(ポートは30000にしています。)

$ DISPLAY=:90 java -Dwebdriver.chrome.driver="node_modules/chromedriver/bin/chromedriver" -jar selenium-server-standalone.jar -port 30000 > /dev/null 2>&1 

スクリーンショットを取るためのNodejsを書きます。以下はサンプルです。

example.js

const webdriver = require('selenium-webdriver');
const chrome = require("selenium-webdriver/chrome");
const fs = require('fs');

const driver = new webdriver.Builder()
        .withCapabilities(
            new chrome.Options()
                .addArguments('--no-sandbox')
                .toCapabilities()
        )
        .usingServer('http://127.0.0.1:30000/wd/hub')
        .build();

driver.get('https://google.co.jp');
driver.takeScreenshot().then((base64Image) => {
    // write file
    fs.writeFile('screenshot.png', Buffer.from(base64Image, 'base64'), 'base64', (error) => {});

}, (reject) => {
});

driver.quit();

example.jsができたところで実際にスクリーンショットを取ってみましょう。

$ node example.js

スクリーンショットを取るとこんな感じに撮れます。
screenshot.png

すごくいい感じ!

youtubeとかだと…
screenshot.png

いい感じにHTML5プレイヤーを認識してくれています。(ちなみに、phantomjsだとだめだった…)

おまけ

おまけとして、スマホ版レイアウトをSeleniumでスクリーンショットを撮りたい場面に遭遇します。
スマホ版レイアウトを採用としているサイトには下記の2種類があります。

  • 1) CSS3のmedia queryを用いて画面サイズによって自動的にレイアウトを変えてくれるサイト
  • 2) User-Agentを見てテンプレートファイルの吐き出しなどを分けているサイト

この両方を満たしてスクリーンショットを撮りたいその願い叶えます。

仮想ディスプレイのサイズをスマートフォンに合わせる

今回はiPhone7 Plusでいきます。

# 先ほどのXvfbを終了させます。
$ sudo pkill -f Xvfb

# 新しく設定
$ Xvfb :90 -ac -screen 0 414x736x24 > /dev/null 2>&1  &

さて、ここまでは 1)を満たすことができました。 次に2)を満たす方法が実は、Chrome自体の起動オプションではっ変更できず、拡張機能でUser-Agentを変更するしかないのです。
ですので、User-Agentを変更するChrome拡張を作成します。
これで完了です。次にこの拡張機能を読み込ませるため、example.jsを修正します。

2017/07/18 19:40追記

下記を追加すれば行けることが判明しました。

.addArguments('--user-agent="Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3"')
example.js

const webdriver = require('selenium-webdriver');
const chrome = require("selenium-webdriver/chrome");
const fs = require('fs');

const driver = new webdriver.Builder()
        .withCapabilities(
            new chrome.Options()
                .addArguments('--no-sandbox')
                                 .addArguments('--user-agent="Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3"')
                .toCapabilities()
        )
        .usingServer('http://127.0.0.1:30000/wd/hub')
        .build();

driver.get('https://google.co.jp');
driver.takeScreenshot().then((base64Image) => {
    // write file
    fs.writeFile('screenshot.png', Buffer.from(base64Image, 'base64'), 'base64', (error) => {});

}, (reject) => {
});

driver.quit();

これでスクリーンショットを撮ってみます。
screenshot.png

Youtubeは…
screenshot.png

なんときれいに撮影できました!めでたしめでたし。