解决Aiogram Bot调用TradingView截图函数时阻塞无响应问题
解决Aiogram机器人因TradingView截图阻塞的问题
问题描述
基于Aiogram框架开发的Telegram机器人,用于获取TradingView图表截图,但调用sellbot.send_chart()函数时耗时约12秒,导致机器人进入阻塞状态,无法响应其他用户请求。
机器人核心代码
from aiogram.dispatcher import FSMContext from aiogram.dispatcher.filters.state import State, StatesGroup import sellbot from aiogram.contrib.fsm_storage.memory import MemoryStorage from aiogram import Bot, Dispatcher, executor, types from datetime import datetime bot = Bot(token='54443480yi5vzrjMRivQ') storage = MemoryStorage() dp = Dispatcher(bot, storage=storage) class Form(StatesGroup): req = State() @dp.message_handler(commands=['get']) async def get(message): await Form.req.set() await message.answer(f'\U0001F916 request format:\nrialto\nticker\ntimeframe (minutes)') @dp.message_handler(state=Form.req) async def process_name(message: types.Message, state: FSMContext): await state.finish() splitmsg = message.text.split('\n') rialto, symbol, timeframe = splitmsg ticker = f'{rialto}:{symbol}' url = sellbot.send_chart(ticker, timeframe) # 阻塞点 await message.answer_photo(url) executor.start_polling(dp)
sellbot模块阻塞代码
def screenshot(driver, ticker, timeframe): chart_url = config.urls["tvchart"] + ('?symbol=' + ticker) if ticker != 'NONE' else '' driver.get(chart_url) time.sleep(1) ActionChains(driver, duration=1).send_keys(timeframe).send_keys(Keys.ENTER).perform() ActionChains(driver).key_down(Keys.ALT).key_down('s').key_up( Keys.ALT).perform() time.sleep(2.5) clipboard = Tk().clipboard_get() return clipboard def send_chart(ticker, timeframe): start_time = datetime.now() driver = setup() driver.get("https://www.tradingview.com") driver.add_cookie('...') screenshot_preurl = screenshot(driver, ticker, timeframe) return screenshot_url
解决方案
问题根源是同步阻塞函数sellbot.send_chart()占用了asyncio事件循环,导致机器人无法处理其他请求。核心解决思路是将同步代码放到线程池/进程池中异步执行,不阻塞主事件循环。
1. 用loop.run_in_executor异步执行同步函数
修改机器人中调用send_chart的逻辑,利用asyncio的线程池执行同步代码:
import asyncio # 新增导入 @dp.message_handler(state=Form.req) async def process_name(message: types.Message, state: FSMContext): await state.finish() splitmsg = message.text.split('\n') rialto, symbol, timeframe = splitmsg ticker = f'{rialto}:{symbol}' # 获取当前事件循环,在线程池中执行同步函数 loop = asyncio.get_event_loop() url = await loop.run_in_executor(None, sellbot.send_chart, ticker, timeframe) await message.answer_photo(url)
2. 优化sellbot中的硬等待(可选)
screenshot函数中的time.sleep()是固定时长等待,可替换为Selenium显式等待,根据页面实际状态触发后续操作,减少不必要的耗时:
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By def screenshot(driver, ticker, timeframe): chart_url = config.urls["tvchart"] + ('?symbol=' + ticker) if ticker != 'NONE' else '' driver.get(chart_url) # 等待图表画布加载完成,替代time.sleep(1) WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.TAG_NAME, "canvas"))) ActionChains(driver, duration=1).send_keys(timeframe).send_keys(Keys.ENTER).perform() ActionChains(driver).key_down(Keys.ALT).key_down('s').key_up(Keys.ALT).perform() # 等待剪贴板获取到截图URL,替代time.sleep(2.5) WebDriverWait(driver, 5).until(lambda d: Tk().clipboard_get() != '') clipboard = Tk().clipboard_get() return clipboard
3. 复用Selenium Driver(可选)
当前send_chart每次调用都重新初始化浏览器driver,这是主要耗时点之一。可以全局复用单个driver,避免重复启动浏览器:
# 在sellbot模块全局初始化driver driver = None def init_driver(): global driver if not driver: driver = setup() driver.get("https://www.tradingview.com") driver.add_cookie('...') def send_chart(ticker, timeframe): init_driver() screenshot_preurl = screenshot(driver, ticker, timeframe) return screenshot_url
注意需添加异常处理逻辑,当driver崩溃时自动重新初始化。
内容的提问来源于stack exchange,提问作者Artik gnom
相关产品推荐
相关产品推荐

