結論だけを読みたい人は、「chrome_remoteサンプルソース」を先に見てください。
Webアプリケーションの操作自動化の検討順
Webアプリケーションの操作自動化は以下の順に検討します。
- HTTP GET(POST)だけで操作できるか
- rubyならopen-uri、phpならfile_get_contents などのファイルを開く機能だけでできるか
- ログインCookieなどがあればアクセスできるか
- Mechanizeなどのライブラリを使ってアクセスするという方法
- JSまで実行しないと再現できないか
- 要は普通のブラウザと同様の動きをするなら、ブラウザ自動実行をする必要があり、一番有名なライブラリがSelenium
簡単にできることは簡単に済ませたいので、ファイルオープンする機能の延長で定期的にHTMLを監視できればそれで実装します。また、Seleniumはプログラミング言語で完結するのではなく、裏でブラウザを起動するためメモリも多く消費しますし、ブラウザのバージョンとの組合わせでも動作が変わってくるため、システムが複雑になります。
Seleniumの良さとツラミ
Seleniumにはライブラリの他に「selenium driver」という単体のアプリケーションが必要です。各プログラミング言語は、driverに対して通信をし、driverがブラウザを操作します。
chromeを操作するdriverやFirefoxを操作するdriverなど個別に提供されており、プログラミング言語は、ブラウザ個別の仕様を気にすることなく、同じソースで、複数のブラウザのテストなどが可能になります。
しかしながら「ChromeやFirefoxの違いを吸収してくれる!」ってのは、driverが頑張ってるからであって、driverの頑張りにも限界があります。
Chromeのバージョンがあがるごとに、driverのバージョンもあげないとダメです。普段使いの開発マシンのChromeなんて自動的にあがっちゃいますね。これ辛いです。
また、「クロスブラウザのテストをしたい」という時以外はchromeとFirefoxが同じソースで自走操縦可能という価値は過剰です。単にWebアプリケーションを自動化したいなら、Chrome専用ソースでも十分です。
chrome_remote とは
この記事で紹介するのが、 chrome_remote というgemライブラリです。 chrome_remote は chromeをdebbuging-port経由で操作します。 Pythonなど他の言語でもchrome-remote-interface-pythonなどのライブラリ名で存在します。
gem install chrome_remote
Gemfileでインストールしてもいいのですが、普段遣いのライブラリとして、私は標準のrubyにインストールしています。依存関係も少なくてシンプルです。
selenium driverなどの中間層がないため、chromeは予め起動しておく必要があります。
devtools-protocolとは
外部からchromeをdebbuging-port経由で操作するためのプロトコルです。 https://chromedevtools.github.io/devtools-protocol/ に、そのレファレンスがあります。逆に言えばここに書かれてる機能しかありません。
Seleniumのようなdom操作の機能もありません。
殆どの操作は、ブラウザ上で動くJSを送ることで実現します。ちょうどDebeloperToolのコンソールでJSを実行するような感覚です。
まずはChromeをdebugging-portつきで起動します
chromeのremote-debbugging-portオプションは、Linux、Mac、Windowsのいずれにも備わっています。
Macの例
/Applications/Google\ Chrome.app --remote-debugging-port=9222
Windowsの例
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
Linuxの例
google-chrome --remote-debugging-port=9222
chrome_remoteは、Selenumのようにブラウザ起動の概念がないので、Seleniumのようにブラウザ終了をしなくてもいいです。プロセスの親子関係もありません。
debugging-port つきで起動したら起動しているかどうかを確認します。
http://127.0.0.1:9222/json
にブラウザでアクセス、もしくはcurlでアクセスすると、JSONが帰ってきます。各タブごとにどういうWebScoketエンドポイントでアクセスすればいいかを示してくれます。
よくあるミスが、すでにchromeがdebugging-portオプションなしで起動してるにも関わらず、debugging-portつきで起動しようとして、オプションなしのchromeでもう1タブ開いているだけというのがあります。
またサーバー上で実行する際には、すでにdebugging-portで起動していて同じポートで起動しようとして失敗するケースもあります。
ポートをリッスンしているかどうかは見えにくいため、curlで9222でアクセスするステップを一度はさみましょう。
サンプルソース
おまたせしました。サンプルソースです。ほぼ「雛形」なのでこれをベースに機能を拡張すれば、手元のchromeを自動化可能です。ログインクッキーも普段使いのものをそのまま使うので、完全に「私のChromeアシスタント」として動いてくれます。
require 'open-uri'
require 'json'
require 'chrome_remote'
# debugging-port が開いているかどうかのチェック
def chrome_running?
json_string = open('http://127.0.0.1:9222/json').read
obj = JSON.parse(json_string)
return obj.length > 0
rescue => exception
return false
end
# ブラウザのロードが終わってるかどうかをJSを投げることで確認
def wait_for_complete
loop do
sleep(1)
response = @chrome.send_cmd 'Runtime.evaluate', expression: 'document.readyState;'
break if response['result']['value'] == 'complete'
end
end
# chromeがdebugging-portを開いて起動しているかどうかをチェックしているかどうかを確認
unless chrome_running?
puts "chromeを --remote-debugging-port=9222 オプションつきで立ち上げてね"
puts "Windowsの例"
puts '"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222'
exit
end
# 以下にメインロジックを書いていく(基本的にはJSを投げまくればだいたいの操作はできる)
@chrome = ChromeRemote.client
js = "window.location = 'https://www.youtube.com/watch?v=4J6YxGAAido'" # ページを移動
@chrome.send_cmd 'Runtime.evaluate', expression: js
wait_for_complete # ロードが終わるまで待つ
js ="document.querySelector('.view-count').innerText" # 再生回数のCSSセレクタ
response = @chrome.send_cmd 'Runtime.evaluate', expression: js
p response #=> {"result"=>{"type"=>"string", "value"=>"2,637,941 回視聴"}}