[2017/7最新]RubyとSeleniumでHeadless chromeを動かす on Ubuntu/Linux

  • 31
    いいね
  • 6
    コメント

[2017-06-22 追記] chromeバージョン59の正式リリースに伴い、記事の内容を全面刷新しました。
[2017-07-27 追記] selenium使用時にsend_keysが使用できない問題を記載しておりましたが、Chromedriver2.31のリリースに伴い問題が解消しました!

はじめに注意

chromeはバージョン59より正式にヘッドレスモードを搭載しました。

それを試そうと「chrome headless」などで検索すると、古いバージョンのchromeをXvfbなどの仮想ディスプレイを通してLinux上で動かす記事が沢山でてきますが、これらはheadless chromeに関しての記事ではなく、普通のGUI chromeをどうにかしてcui上で動かそうという取り組みの記事です。

chromeのヘッドレスモードでは、PhantomJSと同じようにXvfbはありません。googleの中の人も公式に名言しています。もし参考にしている記事でXvfbという単語が出てきたら、少し古い情報かもしれませんのでご注意下さい。

環境

環境構築方法は後述しますが、先に本記事の実行環境を書いておきます。

# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.1 LTS"

# google-chrome-beta --version
Google Chrome 59.0.3071.36 beta

# chromedriver -v
ChromeDriver 2.29.461571 (8a88bbe0775e2a23afda0ceaf2ef7ee74e822cc5)

# ruby -v
ruby 2.2.5p319 (2016-04-26 revision 54774) [x86_64-linux]

# cat Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
    childprocess (0.7.0)
      ffi (~> 1.0, >= 1.0.11)
    ffi (1.9.18)
    mini_portile2 (2.1.0)
    nokogiri (1.7.2)
      mini_portile2 (~> 2.1.0)
    rubyzip (1.2.1)
    selenium-webdriver (3.4.0)
      childprocess (~> 0.5)
      rubyzip (~> 1.0)
      websocket (~> 1.0)
    websocket (1.2.4)

PLATFORMS
  ruby

DEPENDENCIES
  nokogiri
  selenium-webdriver

BUNDLED WITH
   1.12.5

事前準備

必要なライブラリをインストール

$ sudo apt-get update
$ sudo apt-get install -y libappindicator1 fonts-liberation

ヘッドレスモードを使用できるchromeをインストール

$ curl -O https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
$ sudo dpkg -i google-chrome-stable_current_amd64.deb

コマンドラインからChromeをヘッドレスで実行する

遷移先ページのHTMLボディを標準出力する

$ google-chrome --headless --disable-gpu --dump-dom https://www.chromestatus.com/ > test.html

遷移先ページのスクリーンショットを撮影

$ google-chrome --headless --disable-gpu --screenshot https://www.chromestatus.com/

Seleniumを通してRubyからheadless chromeを動かす

chromedriverのインストール

chromedriverはChromeバージョン59をサポートしている2.30以降をインストールして下さい。

[2017-07-27追記]
Chromedriverは2.31以降をインストールしてください。2.30以前ですと、後述のseleniumでsend_keysが使えない問題が発生していたのですが、2.31でこの問題が解消しています!

# curl -O https://chromedriver.storage.googleapis.com/2.29/chromedriver_linux64.zip
# unzip chromedriver_linux64.zip
# chmod +x chromedriver
# mv -f chromedriver /usr/local/share/chromedriver
# ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver
# ln -s /usr/local/share/chromedriver /usr/bin/chromedriver
# chromedriver
Starting ChromeDriver 2.29.461571 (8a88bbe0775e2a23afda0ceaf2ef7ee74e822cc5) on port 9515
Only local connections are allowed.

RubyでChromeをヘッドレスモードで実行

サンプルソース

以下のサンプルソースでUbuntu上で正常にfacebookのトップページにアクセスできていることは確認できています。ただ、フォームの入力を行う際に後述のエラーが発生するという状態です。

Gemfile
source "https://rubygems.org"

# gem "rails"
gem "selenium-webdriver"
gem 'nokogiri'
test_headless_chrome.rb
require 'selenium-webdriver'
require 'nokogiri'
require 'pp'

ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36"

caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {binary: '/usr/bin/google-chrome-beta', args: ["--headless", "--disable-gpu", "--user-agent=#{ua}", "window-size=1280x800"]})
session = Selenium::WebDriver.for :chrome, desired_capabilities: caps
session.manage.timeouts.implicit_wait = 30
session.navigate.to "https://www.facebook.com/"
session.save_screenshot('page.png')

id = session.find_element(:name, 'email')
id.send_keys('test@mail.com') # <= 現在はsend_keysでエラーが発生

session.quit

フォームの入力(send_keys)でエラーが出る

Macで試したような他の記事を読むと正常に実行できているようなのですが、LinuxなどCUI上で実行した場合は、フォームの入力の際にselenium-webdriver Gemが以下のようなエラーを吐きます。

同じ問題で困っている人もたくさんいるようです。原因がChromeDriver側にあるのかSelenium側にあるのか不明ですが、いずれにしてもまだ議論段階であるようです。

https://bugs.chromium.org/p/chromedriver/issues/detail?id=1772

# bundle exec ruby test_headless_chrome.rb
/root/tmp/headless_chrome_with_selenium/.bundle/ruby/2.2.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/remote/response.rb:69:in `assert_ok': unknown error: an X display is required for keycode conversions, consider using Xvfb (Selenium::WebDriver::Error::UnknownError)
  (Session info: headless chrome=59.0.3071.36)
  (Driver info: chromedriver=2.29.461571 (8a88bbe0775e2a23afda0ceaf2ef7ee74e822cc5),platform=Linux 4.4.0-21-generic x86_64)
        from /root/tmp/headless_chrome_with_selenium/.bundle/ruby/2.2.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/remote/response.rb:32:in `initialize'
        from /root/tmp/headless_chrome_with_selenium/.bundle/ruby/2.2.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/remote/http/common.rb:83:in `new'
        from /root/tmp/headless_chrome_with_selenium/.bundle/ruby/2.2.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/remote/http/common.rb:83:in `create_response'
        from /root/tmp/headless_chrome_with_selenium/.bundle/ruby/2.2.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/remote/http/default.rb:107:in `request'
        from /root/tmp/headless_chrome_with_selenium/.bundle/ruby/2.2.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/remote/http/common.rb:61:in `call'
        from /root/tmp/headless_chrome_with_selenium/.bundle/ruby/2.2.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/remote/bridge.rb:678:in `raw_execute'
        from /root/tmp/headless_chrome_with_selenium/.bundle/ruby/2.2.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/remote/bridge.rb:656:in `execute'
        from /root/tmp/headless_chrome_with_selenium/.bundle/ruby/2.2.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/remote/bridge.rb:448:in `send_keys_to_element'
        from /root/tmp/headless_chrome_with_selenium/.bundle/ruby/2.2.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/common/element.rb:156:in `send_keys'
        from test_headless_chrome.rb:42:in `<main>'

フォーム入力のsend_keysメソッドの代替手段

send_keysを使用せずにフォームを入力するため、回避策としてselenium上で任意のjavascriptを実行するexecute_scriptメソッドを使用します。javascriptが実行できれば、テキストボックスをcssセレクタで選択してsetAttributeでフォーム入力を行うことができます。

海外でも現状この集団でフォーム入力の不具合を回避している模様です。
https://bugs.chromium.org/p/chromedriver/issues/detail?id=1772#hc15

require 'selenium-webdriver'
require 'nokogiri'
require 'pp'

ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36"

caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {binary: '/usr/bin/google-chrome', args: ["--headless", "--disable-gpu", "--user-agent=#{ua}", "window-size=1280x800"]})

session = Selenium::WebDriver.for :chrome, desired_capabilities: caps
session.manage.timeouts.implicit_wait = 30

### facebookトップページに遷移
session.navigate.to "https://www.facebook.com/"

### フォーム入力、クリックによるページ遷移
#session.find_element(:name, "email").send_keys('testtest@gmail.com') # <- send_keysメソッドが現状使用不可

# フォーム入力の代替手段
session.execute_script('document.getElementById("email").setAttribute("value", "test@mail.com");')
session.execute_script('document.getElementById("pass").setAttribute("value", "testpass");')

session.find_element(css: '#u_0_q').click

session.save_screenshot('page.png')
session.quit