Telegram Bot使用Playwright遇greenlet.error,如何全局声明Browser?
问题解决:Playwright greenlet.error 与全局Browser复用
错误原因
你遇到的greenlet.error: cannot switch to a different thread是因为:
- Playwright的同步API(
sync_playwright)与创建它的线程绑定,无法跨线程调用 - 你给
CommandHandler设置了run_async=True,导致start函数会在python-telegram-bot的线程池里执行,和全局Browser所在的主线程不是同一个,触发了跨线程调用的错误
可以全局复用Browser,两种方案任选:
方案一:改用异步API(推荐)
python-telegram-bot从v13开始支持异步处理,Playwright也有异步API,两者结合可以完美解决线程问题,同时全局复用Browser,避免每次启动的3秒延迟。
修改后的代码:
from telegram import Update from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes from playwright.async_api import async_playwright args = [ "--no-sandbox", "--disable-setuid-sandbox", "--use-gl=egl", ] # 全局维护Browser实例 browser = None async def init_playwright(): global browser playwright = await async_playwright().start() browser = await playwright.chromium.launch(headless=True, args=args) async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): global browser # 从全局Browser创建新页面 page = await browser.new_page() await page.goto("https://google.com") await page.screenshot(path="example.png") await page.close() await context.bot.send_photo(chat_id=update.effective_chat.id, photo=open("example.png", "rb")) async def main(): # 先初始化Playwright和Browser await init_playwright() # 创建Telegram应用 app = ApplicationBuilder().token('你的TOKEN').build() app.add_handler(CommandHandler('start', start)) # 启动轮询 await app.run_polling() if __name__ == '__main__': import asyncio asyncio.run(main())
方案二:线程本地存储隔离(同步API兼容)
如果坚持用同步API和旧版Updater,可以用threading.local给每个线程单独分配Browser实例,避免跨线程调用,同时每个线程复用自己的Browser。
修改后的代码:
from telegram.ext import Updater, CommandHandler from playwright.sync_api import sync_playwright import threading args = [ "--no-sandbox", "--disable-setuid-sandbox", "--use-gl=egl", ] # 线程本地存储,每个线程有独立的Playwright和Browser thread_local = threading.local() def get_thread_browser(): # 线程第一次调用时初始化Browser if not hasattr(thread_local, 'browser'): playwright = sync_playwright().start() thread_local.browser = playwright.chromium.launch(headless=True, args=args) thread_local.playwright = playwright return thread_local.browser def start(update, context): # 获取当前线程的Browser实例 browser = get_thread_browser() page = browser.new_page() page.goto("https://google.com") page.screenshot(path="example.png") page.close() context.bot.send_photo(chat_id=update.effective_chat.id, photo=open("example.png", "rb")) # 注意:workers数量不要过大,每个worker线程会创建一个Browser,占用资源会增加 updater = Updater('你的TOKEN', use_context=True, workers=4) updater.dispatcher.add_handler(CommandHandler('start', start, run_async=True)) updater.start_polling() updater.idle()
注意事项
- 方案一中,建议升级到python-telegram-bot v14+,
Application是官方推荐的新写法,比Updater更灵活 - 方案二中,
workers参数不要设得太大(比如你原来的124),每个worker对应一个Browser实例,会占用大量内存和CPU资源
内容的提问来源于stack exchange,提问作者JayK23
相关产品推荐
相关产品推荐

