DockerでPython-Seleniumスクレイピング環境を立てた

# はじめに

メイン機以外で定期実行されるクローラを作りたいと思ったわけですが、その場合ローカル環境に全く依存しない形が取れれば最高だということで、Docker環境の構築に乗り出しました。

# 全体図

![2021-05-01_11h13_41.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/936879/4c2cae74-6490-2112-5135-034a5913bd70.jpeg)

Python実行環境と[SeleniumHQ/docker-selenium](https://github.com/SeleniumHQ/docker-selenium)はDocker Composeを利用して別のコンテナとして立てます。
そしてSeleniumHQ/docker-seleniumを通じてVNC接続することで、簡単にクローラの挙動をチェックできます。

ちなみに、SeleniumHQ/docker-seleniumがあれば、ChromeやChromeDriverのセットアップは不要です。

# 環境構築

## Docker

最近はWindows10 Homeでも簡単にインストールできるようになったようです。
Dockerのインストーラの指示に従っていれば使える状態になります。

## VSCode(任意)

Remote-Containersプラグインによって、ローカル開発のような感覚でデバッグも楽々です。
`docker-compose.yml`や`Dockerfile`の他に、`devcontainer.json`なるVSCode用の設定ファイルを用意します。
`devcontainer.json`に盛り盛りの設定をすることも可能なようですが、素の状態で実行できなければあまり意味がないので、まずコマンドラインで動作確認をしてからVSCode用の設定を整えるのがいいと思われます。

## VNC

Docker環境は完全に閉じた環境なので、ホストマシンのブラウザを見ながらのデバッグができません。
そこで、Docker環境内にあるLinux上のデスクトップ画面を表示するために、VNCという技術を使います。

# ディレクトリ構成

```none:全体
.devcontainer\
    devcontainer.json
app\
    .vscode\
            launch.json
            settings.json
    src\
        test.py
    Dockerfile
    requirements.txt
build.sh
docker-compose.yml    
```

`.devcontainer\*`と`.vscode\*`はVSCode用の設定ファイルです。
`dockcker-compose.yml`で`app\Dockerfile`によって生成されるイメージと`SeleniumHQ/docker-selenium`イメージからコンテナを立てます。

## build.sh

```sh:build.sh
#!/bin/sh

cd `dirname $0`
docker-compose build --no-cache
docker-compose up -d
docker-compose exec app bash
```

dockerコマンド入力分の効率化目的です。
3行目(`cd ...`)があると、実行場所に制限がなくなって便利です。

## docker-compose.yml

```yml:docker-compose.yml
version: "3"
services:
  selenium:
    container_name: python-selenium-on-docker_selenium
    image: selenium/standalone-firefox-debug:3.141.59
    ports:
      - 4444
      - 5901:5900
    volumes:
      - /dev/shm:/dev/shm
  app:
    container_name: python-selenium-on-docker_app
    depends_on:
      - selenium
    build: ./app
    volumes:
      - ./app:/app
    environment:
      SELENIUM_URL: http://selenium:4444/wd/hub
    tty: true
```

* `test.py`内で、環境変数`Selenium_URL`を用いて`driver`を定義しています。
* `tty: true`でコンテナの起動状態を維持します。(後で`exec`するため)
* SeleniumHQ/docker-seleniumでは、Firefoxを利用しています。
    * 2021/05/01現在、Docker for Windowsのバグによって、Chromeが動作しないようです。([該当issue](https://github.com/SeleniumHQ/docker-selenium/issues/1267))
    * どうしてもChromeを利用したければ、`volumes`を`/dev/shm:/dev/shm`でなく、`shm_size: "2gb"`などとすればいいようです。

## Dockerfile

```dockerfile:Dockerfile
FROM python:3.7-slim-buster

ENV PYTHONIOENCODING utf-8
ENV TZ="Asia/Tokyo"
ENV LANG=C.UTF-8
ENV LANGUAGE=en_US:en

WORKDIR /app

COPY ./requirements.txt /tmp/requirements.txt

RUN pip install -r /tmp/requirements.txt
```

Python環境のベースイメージは軽量版を利用しています。
wgetやcurlなどで色々入れたい場合は通常版を利用するのが吉でしょう。今回はシンプルな構成なので軽量版で十分でした。

## requirements.txt

```txt:requirements.txt
autopep8
selenium
```

本来は`pip freeze > /app/requirements.txt`した方がいいのですが、少し自由度を持たせています。

## test.py

```python:test.py
import os

from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities


driver = webdriver.Remote(
    command_executor=os.environ["SELENIUM_URL"],
    desired_capabilities=DesiredCapabilities.FIREFOX.copy()
)
driver.implicitly_wait(5)

driver.get("https://www.time-j.net/worldtime/country/jp")

print(driver.find_element_by_xpath("/html/body/div/div[6]/div[1]/div/p[5]").text)
driver.quit()
```

```:出力結果(例)
PCの内蔵時計は、0.2秒遅れています。
```

Seleniumの動作確認用プログラムです。
[こちら](https://www.time-j.net/worldtime/country/jp)から適当な情報を取得しています。
重要な部分は、`driver = webdriver.Remote(...`あたりです。
`docker-compose.yml`で定義した環境変数`SELENIUM_URL`を利用しています。

## devcontainer.json

```json:devcontainer.json
{
	"dockerComposeFile": "../docker-compose.yml",
	"service": "app",
	"workspaceFolder": "/app",
	"settings": {
		"terminal.integrated.shell.linux": "/bin/bash"
	},
	"extensions": [
		"streetsidesoftware.code-spell-checker",
		"ms-python.python",
		"ms-python.vscode-pylance",
		"njpwerner.autodocstring"
	]
}
```

拡張機能は、好きなものを入れるといいでしょう。

## launch.json

```json:launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Current File",
      "type": "python",
      "request": "launch",
      "program": "${file}",
      "console": "integratedTerminal"
    }
  ]
}
```

Docker環境にVSCode拡張機能「Remote-Containers」で接続した状態でデバッガを利用できます。

## settings.json

```json:settings.json
{
  "python.pythonPath": "/usr/local/bin/python",
  "python.languageServer": "Pylance"
}
```

## 詳細

GitHubで公開しています。
こちらが最新版です。

<https://github.com/ryoheiszk/python-selenium-on-docker>

# 利用手順

## 上記リポジトリをクローン

自分が再利用しやすいように作ってあるので、便利ではあるだろうと思います。

```bash
git clone https://github.com/ryoheiszk/python-selenium-on-docker.git
```

## とりあえずビルド

```bash
./build.sh
```

基本的にLinuxで利用するためにシェルスクリプトとして用意しています。
中身は次のようになっています。

```bash
docker-compose build --no-cache
docker-compose up -d
docker-compose exec app bash
```

## VNCテスト

WindowsならVNCViewer等で接続テストをします。
Linuxの場合は、TigerVNCなんかでいいのではないかと思います。

`localhost:5901`→`secret`

接続ができたら何かロゴが表示されている状態だろうと思います。

## テストスクリプト実行

```bash
python /app/src/test.py
```

すると、VNCビューワ内でブラウザが表示されると思います。
コンソールに何かが表示されれば動作確認完了です。

## VSCodeでコーディング

あとは、Remote-Containersの機能を利用し、VSCodeでDocker環境に入りコーディングするだけです。
必要なパッケージなどあれば、`requirements.txt`に追記してリビルドしておきます。

# さいごに

この程度のことは結構な人数が発信していそうでしたが、意外と正解が見つからずに苦戦しました。
自分自身は、Pythonでスクレイピングする用事があれば上記のリポジトリをクローンするところから始めています。
便利なのでぜひご活用ください。