Playwright expose_function内使用Page实例方法失效问题
问题根因
expose_function 注册的回调默认运行在Playwright绑定的页面消息循环线程中,直接在回调内执行页面操作会触发两类问题:
- 同步版本中,
page.goto()、定位器调用这类API需要等待页面主线程返回响应,但此时页面主线程正阻塞在等待expose_function回调执行完成的状态,两边互相等待形成死锁,表现为调用无限期挂起。 - 异步版本中,导航操作会直接销毁当前运行
evaluate脚本的页面执行上下文,回调还未执行完成上下文就被回收,直接抛出执行上下文销毁的错误。
解决方案
不要在expose_function的回调逻辑里直接执行页面操作,回调只负责接收页面传来的参数,把实际的页面操作逻辑抛到独立的执行线程/异步任务中运行,让回调立刻返回打断死锁。
同步版本修复示例
通过后台线程承载实际页面操作逻辑,回调触发线程后立刻返回,不阻塞消息循环:
from playwright.sync_api import sync_playwright, Page import threading def search_title(page: Page, title: str): print(title) page.goto(f"https://bing.com?q={title}") def main(): with sync_playwright() as p: browser = p.chromium.launch(headless=False) context = browser.new_context(viewport={ 'width': 1920, 'height': 1024 }) page = context.new_page() page.goto(f"https://wikipedia.org") def exposed_callback(title): # 独立线程跑页面操作,不阻塞当前回调 threading.Thread(target=search_title, args=(page, title), daemon=True).start() context.expose_function("search_title", exposed_callback) page.evaluate("window.onload = () => search_title(document.title)") while True: page.wait_for_timeout(1000) if __name__ == "__main__": main()
异步版本修复示例
通过asyncio.create_task把页面操作扔到独立异步任务中执行,回调触发任务后立刻返回,避免阻塞和上下文销毁报错:
from playwright.async_api import Page, async_playwright import asyncio async def handle_title(page: Page, title: str): print(title) print(page) await page.goto(f"https://bing.com?q={title}") async def main(): async with async_playwright() as p: browser = await p.chromium.launch(headless=False) context = await browser.new_context(viewport={ 'width': 1920, 'height': 1024 }) page = await context.new_page() await page.goto(f"https://wikipedia.org") async def exposed_callback(title): # 独立任务跑页面操作,不阻塞当前回调 asyncio.create_task(handle_title(page, title)) await context.expose_function("handle_title", exposed_callback) await page.evaluate("window.onload = () => handle_title(document.title)") while True: await asyncio.sleep(1) if __name__ == "__main__": asyncio.run(main())
注意:如果需要从页面侧获取回调执行结果,不要在页面脚本中直接await暴露函数的返回值做依赖导航的后续逻辑,导航会销毁当前页面上下文,所有运行在当前上下文的脚本都会被强制中断。
内容的提问来源于stack exchange,提问作者userQWERTY
相关产品推荐
相关产品推荐

