Seleniumで好きな画像を生成できるプログラムを作る

## はじめに

最近、個人的な趣味で[地震の震度分布図を作画してツイートするプログラム](https://github.com/earthquake-alert/earthquake-alert)を作成したのですが、そこで使った画像生成が他のものでも使えるのではないかということで備忘録として書こうかと思います。

## 原理

Pythonの**Selenium**と呼ばれるフレームワークは、Webアプリなどのテストをするものなんですが動的サイトのスクレイピングなどにも用いられています。
レンダリングは、Google ChromeのヘッドレスプラウザというCUIベースで動くChromeでレンダリングしています。

その、Seleniumにはスクリーンショットの機能があるためローカルサーバーを立てそこに必要な情報をGETしてあげることで好きな画像をHTML/CSSで作成することが可能です。

## 必要なもの

- Python


- Selenium

一応、SeleniumはRubyやJSにもあるみたいです。
Webページをキャプチャするためのフレームワーク。
pipでのインストール方法は以下

```bash
pip install selenium
```

- Docker

WindowsやMacなどにすでにChromeが入っている場合はなくてもいいのですが、VPS上で実行する際などではChromeをDocker内に入れることで実行します。

- ローカルサーバー

Node.js、DenoなどでもOK。今回はPythonのフレームワークのFlsakを使用します。

## 作る

今回作ったソースコードは、[ここ](https://github.com/yuto51942/image-generate)においてあります。

### Webサーバーを作る

今回はFlaskで作成し、Docker composeでコンテナとしてまとめます。そのため、Pythonのモジュールはpipenvで管理しようかと思います。

以下を入力してPipenｖをインストールし、Flaskをインストールします。

```bash
pip install pipenv
pipenv --python 3.8
pipenv install flask
```

更に、たりないファイルなどを追加して以下のようにします。
ディレクトリ構成は以下です。

```text
server
   ├── Dockerfile
   ├── Pipfile
   ├── Pipfile.lock
   ├── run.py
   ├── static
   │  └── css
   │     └── style.css
   └── templates
      └── index.html
```

`run.py`、`index.html`、`style.css`は自分の好きなように作ってください。
今回は、URLパラメータでGETした文字列を画面に表示するだけにします。

Dockerfileは以下のように記述します。出力ポートは5000になっていますが任意で大丈夫だと思います。

```dockerfile:Dockerfile
FROM python:3.8

COPY run.py /run.py
COPY templates /templates
COPY static /static
COPY Pipfile /Pipfile
COPY Pipfile.lock /Pipfile.lock

RUN pip install pipenv
RUN pipenv install --system --deploy

EXPOSE 5000
```

`pipenv shell && python run.py`を試してみて、ブラウザなどで正常に表示できたらOKです。

### キャプチャ部分を作る

先程つくった`server`ディレクトリの上部に生成用のプログラムファイルなどを作成します。
ディレクトリはこのようにします。

```text
.
├── docker-compose.yml
├── Dockerfile
├── images
├── main.py
├── Pipfile
├── Pipfile.lock
├── README.md
└── server
```

pipenvでインストールするモジュールはSeleniumです。

```bash
pipenv install selenium
```

画像生成のDockerfileは以下のようにします。こちらは、Google Chromeも一緒にインストールする必要があります。

```dockerfile:Dockerfile
FROM python:3.8

COPY main.py /main.py
COPY Pipfile /Pipfile
COPY Pipfile.lock /Pipfile.lock

RUN apt-get update && apt-get install -y unzip

#install google-chrome, vim
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add && \
    echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list && \
    apt-get update && \
    apt-get install -y google-chrome-stable
RUN apt-get install -y vim

#install ChromeDriver
ADD https://chromedriver.storage.googleapis.com/84.0.4147.30/chromedriver_linux64.zip /opt/chrome/
RUN cd /opt/chrome/ && \
    unzip chromedriver_linux64.zip

ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/chrome

RUN pip install pipenv
RUN pipenv install --system --deploy
```

`ADD https://chromedriver.storage.googleapis.com/84.0.4147.30/chromedriver_linux64.zip /opt/chrome/`は現在のChromeのバージョンのリンクが必要です

https://chromedriver.storage.googleapis.com/index.html

から、現在のバージョンのリンクを貼り付けます。

さらに、サーバーのコンテナとこのコンテナをつなげるため`docker-compose.yml`を作成します。

```yml:docker-compose.yml
version:  '3'
services:
    server:
        container_name: server
        build:
            context: server
            dockerfile: Dockerfile
        ports:
            - '5000:5000'
        tty: true
        # restart: always
        command: python3 run.py
    main:
        container_name: main
        build: .
        tty: true
        # restart: always
        links:
            - server
        volumes:
            - ./images/:/images/
        command: python3 main.py
```

ここで重要なのは生成した画像をDocker外で使用する場合はその保存するディレクトリを`volumes`に指定してあげる必要があります。

`main.py`はSeleniumのキャプチャ方法として調べるとたくさん出てきますが、今回はこのように作成します。

```py:main.py
import os

from selenium import webdriver


def main():
    image_save_path = os.path.join('images', 'image.png')

    text = 'Hello'

    captcha(image_save_path, text)


def captcha(image_save_path: str, text: str) -> None:
    url = f'http://server:5000/?text={text}'

    options = webdriver.ChromeOptions()

    options.add_argument('--headless')
    options.add_argument('--no-sandbox')

    driver = webdriver.Chrome(options=options)

    driver.set_window_size(1920, 1080)
    driver.execute_script("document.body.style.zoom='100%'")
    driver.get(url)
    driver.implicitly_wait(3)
    driver.save_screenshot(image_save_path)
    driver.quit()


if __name__ == "__main__":
    main()
```

### 動かしてみる

```bash
docker-compose up
```

を入力してみて実際に`images`ディレクトリに画像が生成されていたら成功です。

ソースコード: https://github.com/yuto51942/image-generate



## 最後に

私が最近趣味で作った[Earthquake-alert](https://github.com/earthquake-alert/earthquake-alert)にも同じようにして画像生成しているのでぜひ見てみてください。なんならStarも欲しいです。

他の方法でも画像を生成できるかもしれませんが、自分の中ではこの方法が一番自由に色々な画像を生成することができるのではと思います。ぜひ試してみてください。