Pythonでスクレイピングをする場合、requests-htmlが便利ですが、 JSで構築された複雑なページなどをスクレイピングしたい場合、物足りなくなるケースが出てきます。
今回は、requests-htmlが内部で使用しているpyppeteerと組み合わせて、 複雑なWebページをスクレイピングする方法をご紹介します。
requests-htmlとは
requests、pipenvの作者の方が開発したhtmlの取得・parse用のライブラリです。
HTML Parsing for Humans™
と銘打っている通り、非常にhuman readableなコードを書くことができます。
基本的な使い方は以下の通りです。
from requests_html import HTMLSession session = HTMLSession() r = session.get('https://python.org/') about = r.html.find('#about', first=True)
requests-htmlは大変便利なライブラリなのですが、JSで構築された複雑なページをスクレイピングする際には力不足になるケースがあります。 その場合は、headless browserを直接使用したスクレイピングを行うことで、対応することができます。
pyppeteerとは
pyppeteerとは、Chromeをheadlessで操作するライブラリであるPuppeteerのPythonラッパーです。 Seleniumと比べて文法がシンプルであり、async/awaitを用いてオシャレに非同期処理が書けることがメリットです。
基本的な使い方は以下の通りです。
import asyncio from pyppeteer import launch async def main(): browser = await launch() page = await browser.newPage() await page.goto('http://example.com') await page.screenshot({'path': 'example.png'}) await browser.close() asyncio.get_event_loop().run_until_complete(main())
スクレイピングに用いる場合の注意点として、20秒以上の通信をする場合、Chromiumとpyppeteerのコネクションが切れてしまうバグが存在するようです。 そのため、以下のコードでpatchを適用しましょう。
# workaround: https://github.com/miyakogi/pyppeteer/pull/160 def patch_pyppeteer(): import pyppeteer.connection original_method = pyppeteer.connection.websockets.client.connect def new_method(*args, **kwargs): kwargs['ping_interval'] = None kwargs['ping_timeout'] = None return original_method(*args, **kwargs) pyppeteer.connection.websockets.client.connect = new_method patch_pyppeteer()
また、Jupyter Notebook上で動かす場合、環境によってはasyncioのevent loopがnestできずにエラーが出るケースがあるので、その場合は以下のworkaround用ライブラリを適用してください。
import nest_asyncio
nest_asyncio.apply()
pyppeteer + requests-html
pyppeteerはあくまでHeadless Chromeの操作ライブラリであるため、単体ではhtmlのparse処理には不向きです。 そのため、requests-htmlと組み合わせ、fetchをpyppeteer、parseをrequests-htmlを用いることで、 複雑なHPのスクレイピングも短いコードで記述することができるようになります。
例は以下の通りです。
from pyppeteer import launch import asyncio from requests_html import HTML async def fetch(): browser = await launch(headless=True) page = await browser.newPage() # 対象ページに移動 url = 'http://example.com' # ページ遷移して読み込みが終わるまで待つ await asyncio.wait([ page.goto(url), page.waitForNavigation(), ]) # contentを取得し、request-htmlのparserで読み込み content = await page.content() html = HTML(html=content) # 取得したいデータのparse target = html.find('#target_id', first=True).text await browser.close() return target target = asyncio.get_event_loop().run_until_complete(fetch())
このように、シンプルなコードで複雑なページもスクレイピングすることができます。
まとめ
requests-htmlとpyppeteerを組み合わせることで、複雑なページもシンプルなコードでスクレイピングを行うことができます。