はじめに
こんにちは、普段はPawooの開発を担当している新卒エンジニアのabcangです。
最近話題のHeadless Chromeを使って魚拓を作ってみましたので、その話をします。 結論から言うと、こういうものができました。
以下、詳しくお話していきます。
日々行われるデザイン変更をどう把握するか
pixivには毎日新機能やUIの変更がデプロイされており、どんどんページが変わっていきます。
ある日、ディレクターから「デザインの変更履歴を追うための魚拓ツールがほしい」と相談されました。魚拓ツールがあると、なにか数値の変動があったときにデザインの崩れを確認したり、過去のデザインを振り返ったりするときに便利とのことです。
ちょうどそのタイミングでHeadless Chromeが利用できるGoogle Chrome 59がリリースされていたので、試すいい機会だと思い引き受けました。
Headless Chromeとは
Headless ChromeはGoogle Chrome 59から搭載された新機能で、画面表示を含むすべてのUIを一切表示することなくChromeを立ち上げることができます。 そのため、画面を持たないサーバーでもChromeを動かすことができます。 また、ChromeはChrome DevTools Protocolを提供しており、このプロトコルを使用して様々な操作をすることができるので、ブラウザを使用したテストの自動化やスクレイピングなどの処理を効率的に行うことができます。 さらに、レンダリング結果をキャプチャ画像やPDF形式で出力できるため、自動操作を行った後に画面を保存することもできます。
仕様を固める
まずはじめに、次の項目についてヒアリングを行いました。
- 必要な機能は何か
- どんな問題を解決したいのか
- どういうニーズがあるのか
- どこまで作り込むべきなのか
一口に魚拓と言っても、なぜ求められるのか、誰がどういうシーンで使うのかを把握した上で実装しなければ無駄なプロダクトになってしまうと思ったからです。
そこで、発案者のディレクターだけではなく、社内で需要がありそうな人からもヒアリングを行った結果、仕様はこんな感じになりました。
- やること
- 指定のURLに対して毎日1枚の全画面キャプチャをとる
- PC版スマートフォン版のそれぞれをとる
- ログイン前後でUIが変わるため、ログイン後の画面も取る
- やらないこと
- 社外からアクセスできるようにする
- ビューワーのUIを綺麗にする
- チャレンジすること
- 過去のキャプチャと現在のキャプチャの差分を見られるようにする
この仕様を満たすには、定期的に画面をキャプチャするためのプログラムと、キャプチャ画像や過去のキャプチャとの差分を見るための画像ビューワーの2つを実装する必要があります。
画面キャプチャプログラム
今回はNode.jsからHeadless Chromeを操作して画面をキャプチャすることにしました。 おおまかな流れとしては次のようになります。
- Chromeを起動
- ブラウザを操作して指定したURLにアクセス
- 画面全体をキャプチャ
- Chromeを終了
そして毎日明け方にこのプログラムが実行されるようにcronを設定します。具体的な処理を次のサンプルコードを使って説明します。
Chromeの起動と操作
Node.jsからHeadless Chromeを操作するために、chrome-launcherとchrome-remote-interfaceを使用します。
chrome-launcherはその名の通りNode.jsからChromeを起動するためのライブラリです。Headlessモードで起動するために--headless
オプション指定します。また、Mesaがない場合にエラーが発生するのを防止するために、--disable-gpu
オプションを指定する必要があります。一部のLinux環境ではエラーが発生して起動できない場合があるので、その時は--no-sandbox
オプションも指定する必要があります。
chrome-remote-interfaceはChrome DevTools Protocolを使うためのライブラリで、このライブラリを使用することでChromeを操作することができます。Page
やRuntime
などの一部の機能を使う場合はenable
メソッドを実行する必要があります。
画面のキャプチャ
このプログラムの一番のポイントは画面のキャプチャ部分です。
通常通り起動されたGoogle Chrome 59では全画面をキャプチャする機能が実装されていますが、この機能ではスクロール時に遅延読み込みされる画像が表示されないうえ、現時点でChrome DevTools Protocolからは利用できません。
そのため、全画面キャプチャ機能は自前で実装する必要がありました。具体的には、Headless Chromeで表示したページの最下部まで少しずつスクロールするプログラムをRuntime.evaluate
を使って実行し、末尾までスクロールさせた後にページからscrollWidth
とscrollHeight
の値を取得して、Headless Chromeの画面サイズとして設定してやります。
Chrome DevTools ProtocolからはPage.captureScreenshot
という「画面に写っている範囲のみをキャプチャする」メソッドが提供されており、上記のようにしてページサイズとHeadless Chromeの画面サイズを同一にすることで、綺麗に全画面をキャプチャすることができました。
実際のプログラムでは
実際に動かしているプログラムは、キャプチャしたいURLのリストを読み込んでそれぞれのページを保存したり、画像を保存するときに圧縮したりしています。
またページによってはログイン処理もしています。紹介したサンプルコードにはログイン処理は載せていませんが、Runtime.evaluate
を使ってブラウザ内でJavaScriptを実行して、ログインフォームにIDとパスワードを入力して送信することで実現できます。
画像ビューワー
画面キャプチャプログラムをNode.jsで書いたので、画像ビューワーもNode.jsを使って書くことにしました。また、差分を見れるようにするためにblink-diffを使用しました。
blink-diffはNode.jsで動く画像比較ツールで、差分のある部分を赤やオレンジで目立たせた画像を出力してくれます。デフォルトの設定では色の僅かな違いが差分として検出されていましたが、オプションで閾値を設定することで無視することができました。
画像ビューワーのUIはこだわらなくていいとのことだったので、見た目はすごくシンプルなものになりました。
画像表示画面では指定した日付のキャプチャ画像を見ることができます。また、合わせて比較先の日付も指定することで、2つのキャプチャ画像の差分を見ることもできます。 このように、7/13はフォローボタンが小さいですが、7/14ではフォローボタンが大きくなっていることが簡単に確認できます。
感想・今後の展開
実際にHeadless Chromeを使ってみて、画面を持たないサーバでもChromeを動かせるのは魅力的だと感じました。 また、Headless ChromeというよりChromeの話になりますが、Chrome DevTools Protocolが非常に多くの機能を提供しており、その機能を使って自由に操作することができたので驚きました。
画面のキャプチャ処理では、うまく画面全体を保存できなかったり、スクロールしてもうまく画像が読み込まれなかったり、結構試行錯誤を繰り返しました。最終的には綺麗に全体を取得することができたので満足しています。 また、画像ビューアーには「チャレンジすること」として挙げていた差分表示機能をつけることができてよかったです。発案者のディレクターにも確認しましたが、差分の可視化に喜んでくれました。
今後は広告やイラストの部分を差分として検出しない機能や、一定数以上差分があったら通知するシステムを作りたいと考えています。
参考にした記事
エンジニアを募集中です
ピクシブ株式会社では新サービス・新技術の可能性を積極的に探るエンジニアを大募集中です!