M1macのdockerコンテナでseleniumを動作させる方法

## はじめに

M1 MacのDockerコンテナ内でSeleniumを動かす方法を紹介します。Seleniumはブラウザの自動操作を行うツールで、Pythonなどのプログラミング言語でも実行できます。ただし、Seleniumを実行するにはライブラリの他に、ブラウザや対応するドライバが必要です。

今回の実行環境であるM1 Macは、CPUのアーキテクチャがIntel系(x64)とは異なるため、Seleniumを動作させるには一手間かかります。それでは早速実装していきましょう。

## 実行環境

- 機種：MacBook Air　2020年モデル(M1)
- OS:Sonoma 14.5
- dockerバージョン：27.1.2
- seleniumバージョン:4.23.1
- Chromeバージョン：128.0.6613.84

## 実装

### フォルダ構成

```
.
├── Dockerfile
└── app
    ├── __init__.py
    ├── app.py
    └── requirements.txt
```

### Dockerfile

seleniumは他のPythonライブラリとは違い、ブラウザやブラウザのドライバが必要になります。コンテナイメージを作成する際に、それらのツールをライブラリと一緒にインストールします。今回はブラウザにChromeを選択しました。

```Dockerfile

FROM --platform=linux/amd64 python:3.11-slim

# wget, curl, unzipをインストール
RUN apt-get update && \
    apt-get install -y wget curl unzip && \
    apt-get -y clean && \
    rm -rf /var/lib/apt/lists/*

# Chromeをインストール
RUN wget "https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb" && \
    apt-get update && \
    dpkg -i ./google-chrome-stable_current_amd64.deb || apt-get install -f -y && \
    rm ./google-chrome-stable_current_amd64.deb && apt-get -y clean && \
    which google-chrome-stable

# ChromeDriverをインストール
# 参考 https://qiita.com/casareal_user/items/f932c32517e0d6118809

FROM --platform=linux/amd64 python:3.11-slim

# wget, curl, unzipをインストール
RUN apt-get update && \
    apt-get install -y wget curl unzip && \
    apt-get -y clean && \
    rm -rf /var/lib/apt/lists/*

# Chromeをインストール
RUN wget "https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb" && \
    apt-get update && \
    dpkg -i ./google-chrome-stable_current_amd64.deb || apt-get install -f -y && \
    rm ./google-chrome-stable_current_amd64.deb && apt-get -y clean && \
    which google-chrome-stable

# ChromeDriverをインストール
RUN apt-get update && apt-get install -y unzip graphviz && \
    CHROME_VERSION=$(google-chrome-stable --version | cut -d" " -f3) && \
    echo "Chrome_Version: ${CHROME_VERSION}" && \
    echo "$(google-chrome-stable --version)" && \
    wget "https://storage.googleapis.com/chrome-for-testing-public/128.0.6613.84/linux64/chromedriver-linux64.zip" &&\
    unzip chromedriver-linux64.zip -d /tmp/ && \
    rm chromedriver-linux64.zip && \
    chown root:root /tmp/chromedriver-linux64/chromedriver && \
    chmod 755 /tmp/chromedriver-linux64/chromedriver && \
    mv /tmp/chromedriver-linux64/chromedriver /usr/bin/chromedriver && \
    chromedriver --version || apt-get install -f -y

# 作業ディレクトリを設定
WORKDIR /app

VOLUME /app

# 依存関係をコピー
COPY ./app/requirements.txt .

# 依存関係をインストール
RUN pip install --no-cache-dir -r requirements.txt

# パスを通す
ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/chrome

# アプリケーションのソースコードをコピー
COPY ./app .

# コンテナ起動時に実行するコマンドを指定
CMD ["python", "app.py"]
```

今回重要になってくるのが以下の部分です。

```dockerfile
FROM --platform=linux/amd64 python:3.11-slim
```
Chromeはamd64系のドライバしか提供されていません。プラットフォームを選択せずに動作させると、M1 Macと同じarm系の環境でコンテナイメージが作成されてしまい、ブラウザやドライバがインストールできなくなります。明示的にプラットフォームを選択することで、コンテナイメージの環境をamd64にします。

### app.py

[Example Domain](https://example.com/)を表示してスクリーンショットを作成する処理です。

```python
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options


def main():

    try:
        # データの取得・成型
        chromedriver_bin = "/usr/bin/chromedriver"
        service = Service(executable_path=chromedriver_bin)

        # Chromeオプションを設定
        options = Options()
        options.add_argument("--headless")
        options.add_argument("--no-sandbox")
        options.add_argument("--disable-dev-shm-usage")
        options.add_argument("--disable-gpu")
        options.add_argument("--window-size=1920x1080")

        driver = webdriver.Chrome(service=service, options=options)

        driver.get("https://example.com/")
        
        page_content = driver.page_source

        print(page_content)

        driver.quit()

    except Exception as e:
        print(e)
        raise e 

    return 

if __name__ == "__main__":
    main()

```

特に重要となってくるのが、Optionで指定している部分になります。
```python
        options = Options()
        options.add_argument("--headless")
        options.add_argument("--no-sandbox")
        options.add_argument("--disable-dev-shm-usage")
        options.add_argument("--disable-gpu")
        options.add_argument("--window-size=1920x1080")
```
コンテナ内で実行するときはGUIが利用できないため、ブラウザの画面を表示しないで処理するヘッドレスモードで実行する必要があります。

### requirements.txt

Selenium以外も実行していたので、必須パッケージより若干多くなっています。

```
attrs==24.2.0
beautifulsoup4==4.12.3
blinker==1.7.0
botocore==1.34.88
bs4==0.0.2
certifi==2024.7.4
charset-normalizer==3.3.2
click==8.1.7
h11==0.14.0
idna==3.8
itsdangerous==2.2.0
Jinja2==3.1.3
jmespath==1.0.1
MarkupSafe==2.1.5
outcome==1.3.0.post0
PySocks==1.7.1
python-dateutil==2.9.0.post0
requests==2.32.3
s3transfer==0.10.1
selenium==4.23.1
six==1.16.0
sniffio==1.3.1
sortedcontainers==2.4.0
soupsieve==2.6
trio==0.26.2
trio-websocket==0.11.1
typing_extensions==4.12.2
urllib3==2.2.1
websocket-client==1.8.0
Werkzeug==3.0.2
wsproto==1.2.0
```

### 実行テスト

```bash
docker build -t selenium-sample .
docker run -it selenium-sample 
```
私の場合、実行したときに以下のような警告が出ました。
コンテナのプラットフォームと実行環境のプラットフォームが異なるときに出るのだと思います。
```
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
```

コンテナのボリュームを確認すると、以下のスクリーンショットが保存されていました。

![screenshot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1586001/02f3661c-8495-6e04-8502-4f5b613f30e3.png)

## 終わりに

M1 MacのDockerコンテナでSeleniumを動作させる方法をまとめました。Seleniumはブラウザを自動実行してくれるので便利ですが、依存関係が複雑だったり、実行環境に依存する部分が大きいです。今回、コンテナイメージにまとめてどこでも実行できるようになったので、移植性が大きく向上し、満足しています。

## 後日談

後から調べたら、公式からSeleniumのコンテナイメージが公開されていることに気づきました。

https://github.com/SeleniumHQ/docker-selenium

自分で作るときは、すでに存在しないか調べてから作るようにしましょう。車輪の再開発になってしまいます。(1敗) ただ、車輪の再開発も自分の技術力向上につながるので、悪いことではないと思っています。

## 参考文献

https://qiita.com/casareal_user/items/f932c32517e0d6118809