🐍

PlaywrightでSPAが呼び出すAPIのレスポンスを取得する

2024/07/07に公開

背景

最近、E2Eテストやスクレイピングをやりたいことがあって、Playwrightを使ってみています。スクレイピングすることを考えたときに、HTMLの構造を解析し、ロケーターなどで指定して、要素を取得するのが典型的な方法だと思います。ただ、よくよく考えると、例えば、SPAの画面での何かしらの検索結果を取得したいといった場合、クライアントサイドレンダリングしていることが多いと思いますので、レンダリング後のHTMLの構造を解析するよりも、APIのレスポンスのJSONを直接取得してしまった方がきれいな構造のデータが取得できて、楽なのではないかと思いました。実際、それでうまくいきそうでしたので、その方法を紹介します。

なお、Playwrightの言語については、Node.jsが主流だとは思いますが、この記事ではPython版でやってみています。Node.js版が一番機能が多いと思いますので、Node.jsでも可能だと思います。

実装

参考: https://playwright.dev/python/docs/network#network-events

from playwright.sync_api import sync_playwright, Playwright

def run(playwright: Playwright):
    chromium = playwright.chromium
    browser = chromium.launch()
    page = browser.new_page()
    # Subscribe to "request" and "response" events.
    page.on("request", lambda request: print(">>", request.method, request.url))
    page.on("response", lambda response: print("<<", response.status, response.url))
    page.goto("https://example.com")
    browser.close()

with sync_playwright() as playwright:
    run(playwright)

上記のようにすることで、ページを表示した後で発火されるリクエストやレスポンスの内容を取得することができます。

例えば、https://example.com を開いた際に呼ばれる https://example.com/api/users というAPIのレスポンスを取得したい場合、以下のようにします。

from playwright.sync_api import sync_playwright, Playwright

def run(playwright: Playwright):
    chromium = playwright.chromium
    browser = chromium.launch()
    page = browser.new_page()
    page.on("response", __handle_response)
    page.goto("https://example.com")
    browser.close()

with sync_playwright() as playwright:
    run(playwright)

def __handle_response(res: Response):
    # 所望のAPI以外のレスポンスは無視
    if not res.url.startswith("https://example.com/api/users"):
        return

    json_res = res.json()
    # 所望のAPIのjsonの中身を取得して何かする

2025/01/05 追記

https://playwright.dev/python/docs/api/class-page#page-wait-for-response

Playwrightの Page#page.expect_response() を使った方がすっきり書けました。expect_responseの第一引数にはstrを渡した場合は正規表現として解釈され、URLとのマッチングを行います。自分でlambda関数を渡してより複雑な条件を指定することも可能です。

from playwright.sync_api import sync_playwright, Playwright

def run(playwright: Playwright):
    chromium = playwright.chromium
    browser = chromium.launch()
    page = browser.new_page()
    with page.expect_response(lambda res: res.url.startswith("https://example.com/api/users")) as res:
        page.goto("https://example.com")
    json_res = res.value.json()
    # 所望のAPIのjsonの中身を取得して何かする

with sync_playwright() as playwright:
    run(playwright)

まとめ

これまでもちょっとしたスクレイピングのようなことをしたい場面はたまにあったのですが、HTMLの構造の解析が面倒だなと思っていたので、この手法は汎用性高く使えそうです。Playwrightはぜひ使いこなしていきたいツールだなと思いました。

Discussion