LoginSignup
GOU_KUN
@GOU_KUN

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

【Playwright for Python】スレッドセーフ(?)を実現したい

発生している問題・エラー

page.onで登録したイベントハンドラー上の処理において、処理が終わる前に新しいイベントが入ると処理が競合してしまいます。

該当するソースコード

from playwright.sync_api import sync_playwright, Playwright, Response

def response_handler(response: Response):
    #...競合する処理

def main(playwright: Playwright):
    browser = playwright.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto("")
    page.on("response", response_handler)

with sync_playwright() as playwright:
    main(playwright)

自分で試したこと

1. whileでtime.sleep

おそらく同期APIでもasyncioを使用しているため全体がsleepしてしまう。

from playwright.sync_api import sync_playwright, Playwright, Response
import time

lock = False

def response_handler(response: Response):
    global lock
    while lock:
        time.sleep(1)
    lock = True
    #...競合する処理
    lock = False

def main(playwright: Playwright):
    browser = playwright.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto("")
    page.on("response", response_handler)

with sync_playwright() as playwright:
    main(playwright)

2. threadingのLock

随分的はずれなことをやっているというのは薄々わかっております。

from playwright.sync_api import sync_playwright, Playwright, Response
import threading

lock = threading.Lock()

async def response_handler(response: Response):
    global lock
    lock.acquire()
    #...競合する処理
    lock.release()

def main(playwright: Playwright):
    browser = playwright.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto("")
    page.on("response", response_handler)

with sync_playwright() as playwright:
    main(playwright)

3. whileでasyncio.sleep

こちらの記事を読んでasyncioについてはざっと理解しております。
最終的に行き着いたのが下のコードなのですが、そもそもresponse_handlerが呼ばれなくなってしまいました。

from playwright.sync_api import sync_playwright, Playwright, Response
import asyncio

lock = False

async def response_handler(response: Response):
    global lock
    while lock:
        asyncio.sleep(1)
    lock = True
    #...競合する処理
    lock = False

def main(playwright: Playwright):
    browser = playwright.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto("")
    page.on("response", response_handler)

with sync_playwright() as playwright:
    main(playwright)

非同期APIを使えばよかったのかもしれませんが、今更コードを全部書き直すのも不可能に近いのでなんとか同期APIで実現したいです。
どなたかわかる方、ご教授いただけましたら幸いです。

0

1Answer

自己解決しました

Playwrightの関数を使うことで無事に解決することができました。
page.wait_for_functionを使うのが一番簡単なのですが、なぜか

playwright._impl._api_types.Error: EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".

というエラーが発生してしまったので自分でPromiseを返す関数を作成して待機させました。

from playwright.sync_api import sync_playwright, Playwright, Response

code = """
    async () => {
        return await new Promise((resolve) => {
            setInterval(() => {
                if (!window.popupLock) {
                    resolve();
                }
            }, 1000);
        });
    }
    """

def main(playwright: Playwright):
    def response_handler(response: Response):
        page.evaluate(code)
        page.evaluate("window.popupLock = true;")
        #...競合する処理
        page.evaluate("window.popupLock = false;")

    browser = playwright.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto("")
    page.on("response", response_handler)

with sync_playwright() as playwright:
    main(playwright)

お騒がせしました。誰かの役に立てることを願います。

0

Your answer might help someone💌