如何在基于gevent+greenlet的环境中调用Playwright的async异步方法
方案评估与优化建议
现有方案的问题
你当前的实现可以跑通,但远算不上最优,存在两个明显缺陷:
- 每个greenlet都对应启动了独立系统线程、独立asyncio事件循环,还单独初始化Playwright实例、启动浏览器进程,资源开销极高。如果是做压测场景,并发数稍微上去就会出现资源不足,完全浪费了gevent轻量协程的优势。
- 你注释了
t.join()是因为直接调用会阻塞gevent调度,哪怕你开启了gevent的thread patch,这种一对一绑定线程的模式也完全失去了gevent高并发的意义。
更优的实现方案
推荐你用全局单asyncio事件循环+gevent桥接的方案,只需要开一个后台线程跑asyncio循环,所有greenlet的Playwright调用都提交到这个循环执行,资源消耗极低,也完全兼容gevent的调度逻辑。
示例代码
import asyncio import threading import gevent from gevent.event import Event from playwright.async_api import async_playwright # 全局asyncio事件循环 _loop = None # 全局Playwright、浏览器实例,可复用 _playwright = None _browser = None def start_async_loop(): global _loop, _playwright, _browser _loop = asyncio.new_event_loop() asyncio.set_event_loop(_loop) # 初始化全局Playwright和浏览器,只执行一次 async def init(): global _playwright, _browser _playwright = await async_playwright().start() _browser = await _playwright.chromium.launch(headless=False) _loop.run_until_complete(init()) _loop.run_forever() # 启动后台asyncio线程,只启动一次 threading.Thread(target=start_async_loop, daemon=True).start() # 桥接方法:greenlet调用这个方法执行async协程 def run_async(coro): res = None exc = None done = Event() def _callback(fut): nonlocal res, exc try: res = fut.result() except Exception as e: exc = e finally: done.set() # 把协程提交到全局asyncio循环 fut = asyncio.run_coroutine_threadsafe(coro, _loop) fut.add_done_callback(_callback) # gevent等待结果,不会阻塞其他greenlet done.wait() if exc: raise exc return res # 业务逻辑 def green(i): async def do_stuff(): page = await _browser.new_page() await page.wait_for_timeout(1000) await page.goto("https://google.com") title = await page.title() await page.close() return title title = run_async(do_stuff()) print(f"任务{i},页面标题:{title}") # 启动10个greenlet也不会多开线程和浏览器 jobs = [gevent.spawn(green, i) for i in range(10)] gevent.joinall(jobs)
方案优势
- 全程只占用1个额外系统线程、1个浏览器实例,资源开销比你原来的方案低几十倍,压测场景可以轻松扛上千并发
- 没有破坏gevent的调度逻辑,所有等待都是非阻塞的,完全保留了gevent协程的轻量优势
- 浏览器实例全局复用,不用每个任务都冷启动浏览器,执行速度快很多
备选极简方案
如果你不想改太多代码,也可以直接把Playwright同步API放到gevent的ThreadPoolExecutor里执行,因为报错的本质是gevent patch了线程环境,让Playwright误以为自己跑在asyncio循环里,放到真实系统线程池跑同步API就不会触发这个报错,代码改造成本极低。
内容的提问来源于stack exchange,提问作者Cyberwiz
相关产品推荐
相关产品推荐

