You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 11:24:04