pyppeteerで非同期でクローリングする実装をしていて、エラー時に自動でbrowserとpageを開き直してリトライ…って考えてたんですが、あまりにリトライ処理が煩雑になるので
- 毎回処理したい固定長のurlのリストを受け取る
- レスポンスはrequests-htmlのものが便利だったので流用
- ブラウザ操作周りの例外が出たらfetchの外で例外処理して全部やり直す
- HTMLから必要な情報だけをpyppeteerから取得することもできるが、一度メモリに乗せて同期的な世界に返してしまう
って感じに割り切って実装しようと思ってるんですが、どう思いますか?
↓こんな感じのコード
from pyppeteer import launch import asyncio from requests_html import HTML def fetch(urls, *, headless=True): loop = asyncio.get_event_loop() return loop.run_until_complete(_async_fetch(urls, headless=headless)) async def _async_fetch(urls, *, headless): async with Crawler(headless=headless) as c: async def _inner(url): async with await c.get(url) as page: html = page.html return html return await asyncio.gather(*[_inner(u) for u in urls]) class Crawler(object): def __init__(self, *, headless): self._headless = headless async def __aenter__(self): self._browser = await launch(headless=self._headless) return self async def __aexit__(self, exc_type, exc, tb): await self._browser.close() return False async def get(self, url): page = await self._browser.newPage() await page.goto(url) return PageResponse(self, page, url) class PageResponse(object): def __init__(self, browser, page, url): self._crawler = browser self._page = page self._url = url async def __aenter__(self): content = await self._page.evaluate('document.body.textContent', force_expr=True) self._html = HTML(html=content, url=self._url) return self async def __aexit__(self, exc_type, exc, tb): await self._page.close() return False @property def html(self): return self._html async def screenshot(self, path): await self._page.screenshot(path=path)