ChromeとPythonのchromedriver-binaryのバージョンを合わせたい。

## はじめに

今回は本当に備忘録な記事です。
ただ、あとあと「こうすれば解決するけど、なぜそうなるんだろう？」みたいなところは追記したいと思います。

また、晴れてChrome 75がリリースされた後でも、この方法が使えるかどうかは確認しようと思います。(20190603 追記)

### 追記：20191006

設定が最初に記載した時点でも有効かどうか確認、一部修正して、ソースコードをGitHubに上げました。

- https://github.com/akiko-pusu/qiita-article-chrome-python

### 今回やりたかったこと

- Python3のDockerイメージを利用して、サッとPython＋Seleniumの環境を作りたい
- headlessモードで動かせればいいので、できるだけシンプルにしておきたい
- ChromeはGoogleのリポジトリを追加して安定版をインストールする
- chromedriver-binary (Python用)はpipからインストールする
 - https://pypi.org/project/chromedriver-binary/ 
 - ``pip install chromedriver-binary`` で追加したい

### うまくいかなかった...

タイミングによるのかもしれませんが、Chromeのバージョン（stable)と、pythonのchromedriver-binaryのバージョンがうまく合わないと、うまく動いてくれませんでした...。**(2019/06/02時点)**

2019/06/02時点で、どちらもオプション無しで入れると、Chromeは ``Google Chrome 74.0.3729.169``、chromedriver-binary は ``75.0.3770.8.0``に。

この状態でスクリプトを実行しようとすると、エラーが出て動いてくれません...
``Message: session not created: This version of ChromeDriver only supports Chrome version 75`` と言われて実行できません。
ちなみに、コマンドラインから、google-chrome は起動できます。
Pythonのコードで操作しようとすると残念な結果になってしまいました。

**参考:** [seleniumで「Message: session not created: This version of ChromeDriver only supports Chrome version 75」のエラーが表示される場合の対処法](https://qiita.com/stoneBK7/items/9f258e8ab9ce2d4fa4f5) (@stoneBK7さま: ありがとうございます！)

少し待てばStableのChrome 75がリリースされて、大丈夫になるかと思いますが、方法としてはどちらかにバージョンを揃えるしかありません。

### Chromeのバージョン情報を pip install時に引き渡す

もちろん、chromedriver-binaryのサイトからバージョンを確認し、対応する番号を明示してインストールすればいいのですが、Chromeもchromedriver-binaryも気がついたらバージョンが上がっていることがあるので、このへんは毎回手で直さずともできるようにしたい....。

ということで、こんなことを試してみました。

- 先にChromeをインストール
- Chromeのバージョンを変数にセット
- pip install時にバージョン指定

sedでうまく抽出ができなかったので、perlのパターンマッチを利用。[^1]

また、完全に一致ではなく、できるだけ近いバージョンということで 74.0 のようにメジャーバージョン＋カンマ1つ分だけを取り出しています。

```bash
google-chrome --version | perl -pe 's/([^0-9]+)([0-9]+\.[0-9]+).+/$2/g' > chrome-version
pip install chromedriver-binary~=`cat chrome-version`
```

### 実際のファイルたち

実際のDockerfile, docker-compose.yml, サンプルのPythonスクリプトを載せてみます。
以下が一式。

```bash
tree
.
├── Dockerfile
├── docker-compose.yml
└── workspace
    └── screenshot-full-20190602074859.png

1 directory, 3 files
```

#### Dockerfile

```Dockerfile
FROM python:3
WORKDIR /workspace

# Chrome入れる
RUN sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
RUN apt-get update && apt-get install -y google-chrome-stable

# Seleniumを入れる
RUN pip install selenium

#
# インストールしたChromeとPythonのchromedriver-binaryのバージョンが合わない
# 場合があるので、google-chromeのバージョン情報から バージョンの
# 近いものを pip installする
#
RUN google-chrome --version | perl -pe 's/([^0-9]+)([0-9]+\.[0-9]+).+/$2/g' > chrome-version
RUN pip install chromedriver-binary~=`cat chrome-version` && rm chrome-version

# フォントを追加（日本語のページをスクリーンショットする場合には追加）
RUN apt-get install -y fonts-ipafont-gothic --no-install-recommends

CMD python /workspace/sample.py
```

#### docker-compose.yml

```docker-compose.yml
version: '3'
services:
    sample:
        build: .
        volumes:
            - ./workspace:/workspace
```

#### workspace/sample.py

```python:workspace/sample.py
#
# Chromeを入れてイメージを作成。その後コンテナで下記を実行。
# workspace/ ディレクトリにスクリーンショットが書き出されます。
#
# docker-compose build
# docker-compose run もしくは
# docker-compose run sample python /workspace/sample.py
#

import chromedriver_binary
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import datetime # yyyymmddHHMMSS のタイムスタンプをファイルに付ける

options = webdriver.ChromeOptions()
options.add_argument('--headless')
options.add_argument('--disable-gpu')

# 今回のDockerではrootで実行なのでこのオプションが必要
options.add_argument('--no-sandbox')

driver = webdriver.Chrome(options=options)

driver.get("https://www.google.com")
print(f'title is {driver.title}')

current_time = datetime.datetime.today()
current_time_str = current_time.strftime("%Y%m%d%H%M%S")

driver.save_screenshot(f'screenshot-full-{current_time_str}.png')

driver.quit()
```

あとは、ビルドしてスクリプトを実行してみます。(CMDの処理を呼び出し)

```bash
docker-compose build
docker-compose run sample
title is Google.  # pythonのスクリプトからの出力。PNGも書き出されます
```

### まとめとか気づきとか

前にもちょこちょことPython+Seleniumで試すことがあったのですが、久しぶりで色々と忘れていたり、気がついてなかったこともいくつかありました。
以下、書き留めておきます。

- pip installの際に、バージョン指定は =~ や 大なり小なりが使える
 - オプション無しか、完全一致くらいしか意識して使っていませんでした
- ChromeはHeadlessモードが使えるようになって、これは非常に便利！
 - 2, 3年前までもう少し大変だった気がします....
- 今回のDockerを使った例では、rootで動かすので、Chromeにオプションが必要
- 画面をスクロールさせるようなケースのスクリーンショットを撮りたい場合は、画面サイズを一度計測してからリサイズしてキャプチャを取る必要がある（今回は扱っていません）
- Chromeと各言語に対応したドライバの組み合わせで、もちろんPythonだけじゃなくほかの言語でもうまくいかない時がたまにある（あったのを忘れていた）

などなど。

なお、今回まで [Puppeteer](https://github.com/GoogleChrome/puppeteer) は知りませんでした...。（Qiita上でもすでにたくさん記事がありますね！）

> Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

今回はPythonのためこうしたメモになりましたが、目的に応じてPuppeteerも試してみたいと思います！

[^1]: Pythonの公式イメージを使っているのですが、デフォルトでperlが入っています....