pyppeteer製のクローラをplaywright-pythonに移行したときのTips

SPA向けのクローラをpyppeteerで実装していたのですが、[playwright-python](https://github.com/microsoft/playwright-python)に移行してみました。

基本的な使い心地はそのままに、細かいところで便利になっている印象なので、pyppeteerを単純なクローラとして利用しているのでしたら、試してみてはいかがでしょうか。


# ■導入

普通にpip, pipenv, poetry等でplaywright-pythonを入れたあと、`playwright install`もしくは`python -m playwright install`で各ブラウザのバイナリを取得します。

```
$ poetry add playwright
$ poetry run playwright install
```

ただし、これだと`chromium`/`firefox`/`webkit`の全バイナリが入ってしまうので、クローラのように複数ブラウザで動かす必要がない場合は、利用するブラウザを明示的に指定するのがおすすめです。[^1]

```
$ poetry run playwright install webkit
```

[^1]: 各バイナリが200MBくらいあるので

# ■スクリプトの書き換え

pyppeteerとほとんど変わりませんが、asyncで書かなくても良い(もちろん書いても良い)ので、よりとっつきやすくなっています。


```pyppeteer.py
import asyncio
from pyppeteer import launch

async def main():
    browser = await launch({	
        "args": ["--no-sandbox"],	
        "slowMo": 5,	
        "headless": True,
    })
    page = await browser.newPage()
    await page.goto('https://example.com')
    await page.screenshot({'path': 'example.png'})
    await browser.close()

asyncio.get_event_loop().run_until_complete(main())
```

```playwright.py
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    iphone_11 = p.devices["iPhone 11 Pro"]
    browser = p.webkit.launch(headless=True)
    context = browser.new_context(**iphone_11)
    page = context.new_page()
    page.goto("https://example.com")
    page.screenshot(path="example.png")
    browser.close()
```

* 参考:
    * https://github.com/pyppeteer/pyppeteer#examples
    * https://github.com/microsoft/playwright-python#examples


## `page.waitFor()`や`slowMo`が不要に

[Auto-waiting](https://playwright.dev/docs/actionability)が優秀で、画面描画に合わせて`page.waitFor()`や`slowMo`で適宜waitを入れてやらなくても、いい感じに処理してくれます。

余計なwaitがないと実行時間も短くなるので、とりあえず消して試してみるのがおすすめです。


## device

viewportとかその辺の設定がdeviceとしてビルトインで提供されていて楽ちんです。対応デバイス一覧は以下を参照。

https://github.com/microsoft/playwright/blob/master/src/server/deviceDescriptors.js

任意のデバイスを定義して使う場合はこんな感じ。 

* 設定値一覧
    * https://playwright.dev/python/docs/emulation/#devices

```python
browser = playwright.webkit.launch(headless=True)
device = {
    'user_agent': 'foobar',
    'viewport': {
        'width': 1200,
        'height': 2048
    },
    'device_scale_factor': 1,
    'is_mobile': True,
    'has_touch': True,
    'default_browser_type': 'webkit'
}
context = browser.new_context(**device)
```

クローラとしてUAを明示したいだけなら、以下でOKです。

```python
browser = playwright.webkit.launch(headless=True)
context = browser.new_context(user_agent="foobar")
```


## HTML文字列取得

pyppeteerと同じなので、クローラがhtml文字列をlxmlやbs4に食わせるタイプとして実装されていれば、特に変更不要です。

```:python
page = context.new_page()
page.goto(url)

html = page.content()
```

# ■Dockernize

公式の https://github.com/microsoft/playwright-python/blob/master/Dockerfile を参考にしてください。 

ライブラリが足りない場合、実行時に丁寧に表示されるので、とりあえず流してみて、エラーがあれば追加してあげるのが良さそうです。


```:shell

playwright._impl._api_types.Error: Host system is missing dependencies!

  Install missing packages with:
      sudo apt-get install libnss3\
          libnspr4\
          libasound2
```

参考に、以下に動作確認したものを示します。(2021/02/28: webkit用)

```:Dockerfile
FROM ubuntu:focal

# 0. Browser-binaries path (site-packages/playwright)
# @see https://playwright.dev/python/docs/installation/#managing-browser-binaries
ENV PLAYWRIGHT_BROWSERS_PATH 0

# 1. Install latest Python
RUN apt-get update && apt-get install -y python3 python3-pip && \
    update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1 && \
    update-alternatives --install /usr/bin/python python /usr/bin/python3 1

# 2. Install WebKit dependencies
RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
    libwoff1 \
    libopus0 \
    libwebp6 \
    libwebpdemux2 \
    libenchant1c2a \
    libgudev-1.0-0 \
    libsecret-1-0 \
    libhyphen0 \
    libgdk-pixbuf2.0-0 \
    libegl1 \
    libnotify4 \
    libxslt1.1 \
    libevent-2.1-7 \
    libgles2 \
    libxcomposite1 \
    libatk1.0-0 \
    libatk-bridge2.0-0 \
    libepoxy0 \
    libgtk-3-0 \
    libharfbuzz-icu0

# 3. Install gstreamer and plugins to support video playback in WebKit.
RUN apt-get update && apt-get install -y --no-install-recommends \
    libgstreamer-gl1.0-0 \
    libgstreamer-plugins-bad1.0-0 \
    gstreamer1.0-plugins-good \
    gstreamer1.0-libav \
    # add
    libvpx6\
    libgstreamer-plugins-base1.0-0\
    libgstreamer1.0-0\
    libopenjp2-7

RUN apt-get update \
    && pip install --upgrade pip \
    && pip install --no-cache-dir poetry && poetry config virtualenvs.create false && poetry install --no-dev \
    && python -m playwright install webkit \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*
```

なお、GitHub Actions経由で実行した場合など、コンテナの実行環境によっては予期していないbrowser binariesパスを見に行ってしまうことがあります。

これを防ぐには、以下のように対応してください。

* 環境変数に`PLAYWRIGHT_BROWSERS_PATH=0`と指定
    * `site-packages/playwright`以下がbrowser binariesパスになる
        *  (上記サンプルはこれ、具体的には`/usr/local/lib/python3.8/dist-packages/playwright/driver/package/.local-browsers/`になりました)
* 環境変数に`PLAYWRIGHT_BROWSERS_PATH`を明示的に指定
    * `PLAYWRIGHT_BROWSERS_PATH=/root/.cache/ms-playwright`とか

参照: https://playwright.dev/python/docs/installation/#managing-browser-binaries

なお、pyppeteerでDockerコンテナ経由の実行時に必要だった`"args": ["--no-sandbox"]`は、playwrightでは不要になります。


### (備考) tz選択が出て止まってしまう場合

```
Configuring tzdata
------------------

Please select the geographic area in which you live. Subsequent configuration
questions will narrow this down by presenting a list of cities, representing
the time zones in which they are located.

  1. Africa      4. Australia  7. Atlantic  10. Pacific  13. Etc
  2. America     5. Arctic     8. Europe    11. SystemV
  3. Antarctica  6. Asia       9. Indian    12. US
Geographic area:
```

`DEBIAN_FRONTEND="noninteractive"`を突っ込んでください。環境変数に設定してあげてもよいかと思います。

* 参考
    * Docker Ubuntu18.04でtzdataをinstallするときにtimezoneの選択をしないでinstallする - Qiita https://qiita.com/yagince/items/deba267f789604643bab

# ■移行で注意すべき点

## pyppeteerで使われているChromiumのバージョンはかなり古い

pyppeteerで使われているChromiumのバージョンはかなり古い(REV:588429)です。
playwright-python(2021/02/28時点)だとREV:854489なので、そのあたりに起因する挙動の違いがないとも限りません。注意してください。


## playwright-python公式のベースイメージはUbuntu Focal Fossa(20.04)

DockerHubのpythonイメージとかconda系を使ってる場合、playwrightに必要なライブラリがなくて困ったりするかもしれません。

私は`python:3.8.6-slim`を使ってたんですが`libevent-2.1-7`が見当たらなくてうーん…ってなりました。

頑張る気力がなかったので、playwrightに寄せる形でクローラのベースイメージを`ubuntu:focal`にしてしまいましたが、クローラで使っている他のライブラリの依存関係が厳しめな場合、注意が必要です。

ちなみに`ubuntu:focal`のpython3は`3.8.5`だったので、その辺も現在お使いのライブラリによっては、調整が必要になるかもしれません。

# ■最後に

**「pyppeteerのスペルに気を使わなくて良くなる」**、この一点だけでもplaywright-python、おすすめです！