Alpine LinuxでPlaywright for Pythonを動かす

## PlaywrightはAlpineをサポートしていない

[ドキュメント](https://playwright.dev/docs/intro#system-requirements)にある通り対象外の環境。実際にやってみるとPyPIからはインストールできない。

```sh
pip install playwright --break-system-packages
ERROR: Could not find a version that satisfies the requirement playwright (from versions: none)
ERROR: No matching distribution found for playwright
```

### 詳細確認

とりあえずuvで仮想環境を用意し(PoetryやPipenvな方は読み替えてください)

```sh
pip install uv --break-system-packages
mkdir work
cd work
uv init
```

Playwrightを追加するとPyPIにはサポートしているバイナリーパッケージが存在しないことやソースも置かれていな事がわかる。

```sh
uv add playwright
Resolved 5 packages in 526ms
error: Distribution `playwright==1.51.0 @ registry+https://pypi.org/simple` can't be installed because it doesn't have a source distribution or wheel for the current platform

hint: You're on Linux (`linux_x86_64`), but `playwright` (v1.51.0) only has wheels for the following platforms: `manylinux_2_17_aarch64`, `manylinux1_x86_64`, `manylinux2014_aarch64`, `macosx_10_13_x86_64`, `macosx_11_0_arm64`, `macosx_11_0_universal2`, `win32`, `win_amd64`
```

## 暫定対策方法

### Gitからソースインストール

とりあえずインストールできるが…

```sh
pip install git+https://github.com/microsoft/playwright-python.git --break-system-packages
```

### Cライブラリーの互換性

実際に動かしてみようとすると`/lib64/ld-linux-x86-64.so.2`がないとエラーなる。なぜならAlpineはGNU libcではなくmusl libcであるから。

```sh
playwright --version
rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2
```

ここで、rosetta errorは私がmacOS上でx86-64のコンテナーを動かしているから。

### 互換ライブラリの適用

そこで、GNU C Library compatibility layer for musl をインストールする。

```sh
apk add gcompat
```

しかし、今度はNode.jsの実行時にfcnt64でエラーとなる。

```sh
playwright --version
Error relocating /usr/lib/python3.12/site-packages/playwright/driver/node: fcntl64: symbol not found
```

UbuntuでビルドされたNode.jsでは色々ダメそう。他に何かライブラリーを追加すれば良いかもしれないが、そこまで追ってない。とにかくNode.jsを読んでるところで失敗していることはわかった。

```sh
ldd /usr/lib/python3.12/site-packages/playwright/driver/node
        /lib64/ld-linux-x86-64.so.2 (0x7ffffff5a000)
        libdl.so.2 => /lib64/ld-linux-x86-64.so.2 (0x7ffffff5a000)
        libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x7fffff4b0000)
        libm.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7ffffff5a000)
        libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x7fffff484000)
        libpthread.so.0 => /lib64/ld-linux-x86-64.so.2 (0x7ffffff5a000)
        libc.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7ffffff5a000)
        ld-linux-x86-64.so.2 => /lib/ld-linux-x86-64.so.2 (0x7fffff47d000)
Error relocating /usr/lib/python3.12/site-packages/playwright/driver/node: fcntl64: symbol not found
Error relocating /usr/lib/python3.12/site-packages/playwright/driver/node: gnu_get_libc_version: symbol not found
Error relocating /usr/lib/python3.12/site-packages/playwright/driver/node: setcontext: symbol not found
Error relocating /usr/lib/python3.12/site-packages/playwright/driver/node: backtrace: symbol not found
Error relocating /usr/lib/python3.12/site-packages/playwright/driver/node: backtrace_symbols: symbol not found
Error relocating /usr/lib/python3.12/site-packages/playwright/driver/node: __register_atfork: symbol not found
Error relocating /usr/lib/python3.12/site-packages/playwright/driver/node: makecontext: symbol not found
Error relocating /usr/lib/python3.12/site-packages/playwright/driver/node: getcontext: symbol not found
Error relocating /usr/lib/python3.12/site-packages/playwright/driver/node: __libc_stack_end: symbol not found
```

### Node.jsをAplineのものに差し替える

```sh
apk add nodejs
node -v
v22.13.1
```

```sh
cp /usr/bin/node /usr/lib/python3.12/site-packages/playwright/driver/node
```

とりあえずバージョン表示はできた。

```sh
playwright --version
Version 1.51.1.dev3+g4628609
```

## 動作確認

`playwright install`してもUbuntu用にビルドされたブラウザーがインストールされるだけでNode.js同様に動かないのでインストールせずAlpineのChromiumをインストールする。

```sh
apk add chromium
```

`chromium.launch()`の引数`executable_path`でAlpineのパッケージとしてインストールしたChromiumのパスを指定する。下の例はGoogleのトップページにアクセスしてPDFとして出力。

```python
from playwright.sync_api import sync_playwright


def generate_pdf(url, output_path):
    with sync_playwright() as p:
        browser = p.chromium.launch(executable_path="/usr/bin/chromium-browser")
        page = browser.new_page()
        page.goto(url)
        page.pdf(path=output_path)
        browser.close()

generate_pdf("https://google.com", "/tmp/debug.pdf")
```

## おまけ(Dockerfile)

```Dockerfile
FROM alpine

RUN apk add --no-cache \
    python3 \
    py3-pip \
    git \
    curl \
    nodejs \
    chromium

RUN pip install --no-cache-dir --break-system-packages \
    git+https://github.com/microsoft/playwright-python.git

RUN PYTHON_VERSION=$(python -c "import platform; version = platform.python_version().split('.'); print(f'{version[0]}.{version[1]}')") \
    && cp /usr/bin/node /usr/lib/python${PYTHON_VERSION}/site-packages/playwright/driver/node
```

## まとめ

Node.jsとChromiumをAlpineのものに差し替えて`chromium.launch()`するときに`executable_path`でパスを指定すれば動くっぽい。