SeleniumからHeadless Chromeを使ってみた

**2018年5月6日: Headless ChromeがStableになった後の現状に合わせた[新しい記事](https://qiita.com/orangain/items/6a166a65f5546df72a9d)を書きました。こちらもご参照ください。**

先日PhantomJSのVitalyさんが[メンテナーを引退するという話](https://groups.google.com/forum/#!topic/phantomjs/9aI5d-LDuNE)が話題になっていました。ヘッドレスなブラウザーを気軽に使う手段としてPhantomJSにはお世話になりました。今後は[Headless Chrome](https://www.chromestatus.com/features/5678767817097216)を使って欲しいとのことなので、試してみました。

Node.jsを使うサンプルは多く見つかりますが、諸事情でPythonを使いたかったので、ここではSelenium経由でHeadless Chromeを使います。

## Headless Chromeとは

Google Chrome 59から使えるようになる予定の、画面を表示せずに動作するモードです。自動テストやWebスクレイピングなどに役立ちます。

2017年4月28日現在、Mac版とLinux版のDevチャネルまたはCanaryチャネルで使用できるようです。私はMac版のCanaryチャネルで試しました。Windows版のCanaryチャネルでも試してみましたが`--headless`を指定しても画面が表示されてしまいました。そのうち使えるようになると思います[^1]。

[^1]: 参考: https://bugs.chromium.org/p/chromium/issues/detail?id=712981

とりあえずNode.jsの[chrome-remote-interface](https://github.com/cyrus-and/chrome-remote-interface)から使うのが簡単かつ情報も多いので、ここから試してみると良いでしょう。

* Headless Chromium<br>https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md
* ChromeのヘッドレスモードをMacOSXで試してみた - Qiita<br>http://qiita.com/g_ryotaro/items/da3cf376890bf3366a62

ちなみに、昔からXvfbなどの仮想ディスプレイにChromeを起動してヘッドレスで使っていた例も多くあるようです。ググる時はどちらの意味で使われているか確認しましょう。

## SeleniumからHeadless Chromeを使う

Headlessだからと言って、通常のChromeを使うときと大きく変わるわけではありません。SeleniumからChromeDriverを通して、Chromeを操作します。ChromeのWebDriverの作成時に引数としてChromeOptionsを渡し、その中で実行するChromeのパスや引数を指定します。

### 環境

試した環境は以下のとおりです。

* OS X El Capitan
* Google Chrome Canary 60.0.3082.0
* ChromeDriver 2.29
* Python 3.6.0
* Selenium 3.4.0

### 準備

前提: Python 3.6はインストールされている。

1. [Google Chrome Canary](https://www.google.co.jp/chrome/browser/canary.html)をインストールする（Stableチャネルで`--headless`が使えるようになったら不要なはず）。CanaryはStableと共存できる。
2. [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/downloads)をダウンロードして、PATHの通ったところに置く。
3. Seleniumをインストールする（ここでは仮想環境を使う）。

```
(venv) $ pip install selenium
```

### サンプルコード

Google検索を行います。[Pythonクローリング&スクレイピング](http://orangain.hatenablog.com/entry/scraping-book)のサンプルコードを改変したもので、PhantomJSを使っていた箇所をHeadless Chromeに置き換えてみました。

```selenium_google.py
import time

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options

options = Options()
# Chromeのパス（Stableチャネルで--headlessが使えるようになったら不要なはず）
options.binary_location = '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary'
# ヘッドレスモードを有効にする（次の行をコメントアウトすると画面が表示される）。
options.add_argument('--headless')
# ChromeのWebDriverオブジェクトを作成する。
driver = webdriver.Chrome(chrome_options=options)

# Googleのトップ画面を開く。
driver.get('https://www.google.co.jp/')

# タイトルに'Google'が含まれていることを確認する。
assert 'Google' in driver.title

# 検索語を入力して送信する。
input_element = driver.find_element_by_name('q')
input_element.send_keys('Python')
input_element.send_keys(Keys.RETURN)

time.sleep(2)  # Chromeの場合はAjaxで遷移するので、とりあえず適当に2秒待つ。

# タイトルに'Python'が含まれていることを確認する。
assert 'Python' in driver.title

# スクリーンショットを撮る。
driver.save_screenshot('search_results.png')

# 検索結果を表示する。
for a in driver.find_elements_by_css_selector('h3 > a'):
    print(a.text)
    print(a.get_attribute('href'))

driver.quit()  # ブラウザーを終了する。
```

以下のように実行すると、ブラウザーの画面が表示されない状態で検索結果が出力されました。

```
(venv) $ python selenium_google.py
Python - ウィキペディア
https://ja.wikipedia.org/wiki/Python
Python チュートリアル — Python 3.6.1 ドキュメント
http://docs.python.jp/3/tutorial/
Python基礎講座(1 Pythonとは) - Qiita
http://qiita.com/Usek/items/ff4d87745dfc5d9b85a4
初心者でもほぼ無料でPythonを勉強できるコンテンツ10選 - paiza開発日誌
http://paiza.hatenablog.com/entry/2015/04/09/%E5%88%9D%E5%BF%83%E8%80%85%E3%81%A7%E3%82%82%E3%81%BB%E3%81%BC%E7%84%A1%E6%96%99%E3%81%A7Python%E3%82%92%E5%8B%89%E5%BC%B7%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%8410
【入門者必見】Pythonとは？言語の特徴やシェア、仕事市場を徹底解説 | 侍 ...
http://www.sejuku.net/blog/7720
Pythonに咬まれるな : 注意すべきセキュリティリスクのリスト | プログラミング ...
http://postd.cc/a-bite-of-python/
Pythonとは - はてなキーワード - はてなダイアリー
http://d.hatena.ne.jp/keyword/Python
Python入門から応用までの学習サイト
http://www.python-izm.com/
Pythonで学ぶ 基礎からのプログラミング入門 (1) Pythonでプログラミングを ...
http://news.mynavi.jp/series/python/001/
Download Python | Python.org
https://www.python.org/downloads/
```

### PhantomJSとの差異など

* 実行中に画面は表示されないが、DockにはChromeのアイコンが表示される。
* 明示的に`driver.quit()`としないとChromeは終了しない。
* PhantomJSだと検索語を入力して送信した時にonloadまで待ってくれていたが、ChromeだとAjaxで遷移するので別途待つ必要があった（これはGoogleの挙動の違いによるものでChromeやGoogleの問題ではない）。
* ~~ヘッドレスモードだと、`save_screenshot()`で撮影したスクリーンショットが1x1の画像になってしまった。何かオプションが必要なのかも。~~

## まとめ

少なくともOS Xでは気軽にHeadless Chromeを使用できました。Stableチャネルで使用できるようになればさらに簡単です。`--headless`オプションを取り除けば画面が表示されるので、デバッグしやすそうなのも嬉しいです。