歩いたら休め

If the implementation is easy to explain, it may be a good idea.

【Python】pyppeteerを非同期コンテクストマネージャー用のクラスでラップして遊ぶ

Pythonから簡単にHeadless Chromeを利用できるpyppeteerというライブラリがあります。Headless Chromeの操作をラップしてくれてかなり便利なのですが、ほとんどの関数やメソッドが非同期(async)nあので、しばらく遊んでasync/awaitを使った実装に慣れる必要がありそうです。

ただ、公式リポジトリのサンプルコードを見ても分かる通り、browser.newPage()close()などの前処理・後処理のコードがあります。

async def main():
    browser = await launch()
    page = await browser.newPage()
    await page.goto('http://example.com')
    await page.screenshot({'path': 'example.png'})

    dimensions = await page.evaluate('''() => {
        return {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight,
            deviceScaleFactor: window.devicePixelRatio,
        }
    }''')

    print(dimensions)
    # >>> {'width': 800, 'height': 600, 'deviceScaleFactor': 1}
    await browser.close()

同期的な関数であれば、これをクラスでラップしてコンテキストマネージャ(with句)で実装したくなりますよね?私もそう思って調べたところ、async withという非同期用のコンテキストマネージャがあるそうです。

qiita.com

これを使えば、以下のようにasync withで定形コードをまとめて実装できました。

import asyncio
from pyppeteer import launch


class Session(object):
    async def __aenter__(self):
        self._browser = await launch()
        self._page = await self._browser.newPage()
        return self

    async def get(self, url):
        await self._page.goto(url)
        return self._page # Pageを返す

    async def __aexit__(self, exc_type, exc, tb):
        await self._browser.close()


async def main():
    async with Session() as s:
        page = await s.get("https://google.com")
        await page.screenshot(path="./example.png")
        result = await page.mainFrame.content()
        print(result)

if __name__ == "__main__":
    asyncio.get_event_loop().run_until_complete(main())

async/awaitはHaskellのIO型のdo記法に近いものだと思えばなんとかなりそうです。詳しい人なら、ここでモナドの話を始めるんだと思います。

async with構文はこちらのPEP 492で提案されたもののようなので、後で読んでみます。

www.python.org