MAGAZINE

ルーターマガジン

クローリング/スクレイピング

chrome_remoteという選択(脱Selenium大作戦)

2020.06.17
Pocket

結論だけを読みたい人は、「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は予め起動しておく必要があります。

Chrome DevTools Protocolとは

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/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222

Linux/Windows(WSL2)の例

google-chrome --remote-debugging-port=9222

chrome_remoteは、Selenumのようにブラウザ起動や終了には関知しません。プロセスの親子関係もありません。

debugging-port つきで起動したら起動しているかどうかを確認します。

http://127.0.0.1:9222/json

にブラウザでアクセス、もしくはcurlでアクセスすると、JSONが帰ってきます。各タブごとにどういうWebScoketエンドポイントでアクセスすればいいかを示してくれます。

よくあるミスが、すでにchromeがdebugging-portオプションなしで起動してるにも関わらず、debugging-portつきで起動しようとして、オプションなしのchromeでもう1タブ開いているだけというのがあります。

またサーバー上で実行する際には、すでにdebugging-portで起動していて同じポートで起動しようとして失敗するケースもあります。

ポートをリッスンしているかどうかは見えにくいため、curlで9222でアクセスするステップを一度はさみましょう。

サンプルソース

おまたせしました。ミニマムのサンプルソースです。9222のポートをlistenしているchromeを起動して以下のrubyスクリプトを起動すると動作が確認できます。


require 'chrome_remote'

# ブラウザのロードが終わってるかどうかを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

# 以下にメインロジックを書いていく(基本的には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 回視聴"}}

また、実務上ではJSからはHTMLを返してもらうだけで、詳細のロジックはRuby+Nokogiriで扱うことが多いです。どうせRDBにはデータを入れるので、ActiveRecordeを使ったロジックとNokogiriを使ったロジックを近くに置くことで、HTMLとRDBというデータ構造の対応をわかりやすくします。

以下が実務上の雛形となります。

require 'chrome_remote'
require 'nokogiri'

# ブラウザのロードが終わってるかどうかを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 = ChromeRemote.client
# ページ移動
@chrome.send_cmd 'Page.navigate', url: 'https://www.youtube.com/watch?v=4J6YxGAAido'
wait_for_complete # ロードが終わるまで待つ
# JSを送ることでHTMLを取得(他にもClickする等の操作もJSONで可能)
response = @chrome.send_cmd 'Runtime.evaluate', expression: 'document.documentElement.outerHTML'
html =  response['result']['value']
doc = Nokogiri::HTML.parse(html)
puts doc.at_css('.view-count').text
補足

2020年の本記事公開当初はWSL1前提での記述でしたが、WSL2がほぼ標準になりWSL2上でChromeも動作するようになったため、WindowsとLinuxは同じ扱いにするように修正しました(2022-09-01)。Windowsで実行する方は予めWSL2上でChromeを起動してください。

Pocket

CONTACT

お問い合わせ・ご依頼はこちらから