如何实现Python Discord Bot异步执行Selenium预约检测并发送消息
问题解答
1. 第一种方案(独立任务+通知Bot)的最简实现
你的核心问题是check_all()是同步阻塞函数,直接用asyncio.create_task()没用——因为它还是会在Discord Bot的事件循环线程里执行,卡死整个Bot。最简解决方式是把同步的check_all()放到线程池里跑,彻底脱离事件循环线程,不阻塞Bot的正常运行。
具体改动代码
from concurrent.futures import ThreadPoolExecutor import asyncio # 全局线程池,避免重复创建 executor = ThreadPoolExecutor(max_workers=2) @tasks.loop(minutes=5) async def send_message(): channel_debug = discord.utils.get(client.get_all_channels(), name='debug') current_time = datetime.now(BerlinTz) formatted_time = current_time.strftime('%Y-%m-%d %H:%M:%S') await channel_debug.send(f'{formatted_time}: Checking all cities ...') # 用线程池执行同步的check_all,不阻塞事件循环 loop = asyncio.get_running_loop() response = await loop.run_in_executor(executor, check_all, data) # 后续逻辑完全不变 city_availability = check_availability(response) await channel_debug.send(city_availability) await channel_debug.send(response) for entry in city_availability: if entry['send_msg_flag'] == True: print(entry['city'], "is available") channel_city = discord.utils.get(client.get_all_channels(), name=entry['city'].lower()) filtered_response = [element for element in response if element['city'].lower() == entry['city'].lower()] formatted_output = format_data(filtered_response) await channel_city.send(formatted_output) else: print(entry['city'], "is NOT available")
如果非要做完全独立的定时任务(比如单独脚本),最简方式是:
- 写个独立Python脚本,用
time.sleep(300)做5分钟定时,执行check_all() - 执行完后通过Discord Webhook直接给指定频道发结果,不需要和Bot做复杂通信
- 但这种方式需要维护额外进程,不如线程池方案简洁。
2. 第二种方案(双协程+数据库)是否更优?如何维护?
是否更优?
要看你的场景:
- 如果只是简单的定时检测+通知,第一种线程池方案更轻量,改动少
- 如果后续要扩展功能(比如保存历史数据、支持多Bot实例、加前端查看状态),第二种方案更优——它完全解耦了检测逻辑和Bot逻辑,Bot只负责读库发消息,不会被任何阻塞操作影响,稳定性更高。
如何维护双协程?
核心是让检测协程和Bot协程通过数据库通信,用asyncio.gather()同时启动两个协程:
示例代码结构
import asyncio import discord from discord.ext import tasks from concurrent.futures import ThreadPoolExecutor executor = ThreadPoolExecutor(max_workers=2) # 检测协程:每5分钟跑一次check_all,更新数据库 async def check_and_update_db(): while True: loop = asyncio.get_running_loop() response = await loop.run_in_executor(executor, check_all, data) # 替换成你的数据库更新逻辑(比如sqlite/PostgreSQL) update_database_with_latest_data(response) await asyncio.sleep(300) # 5分钟间隔 async def main(): # 初始化Discord Bot intents = discord.Intents.default() client = discord.Client(intents=intents) @client.event async def on_ready(): print(f'Logged in as {client.user}') # Bot启动后,启动读库发消息的定时任务 send_message_from_db.start() @tasks.loop(minutes=5) async def send_message_from_db(): channel_debug = discord.utils.get(client.get_all_channels(), name='debug') current_time = datetime.now(BerlinTz) formatted_time = current_time.strftime('%Y-%m-%d %H:%M:%S') await channel_debug.send(f'{formatted_time}: Checking database for updates ...') # 从数据库读取最新检测结果 response = get_latest_data_from_db() city_availability = check_availability(response) # 后续发消息逻辑和之前一致 await channel_debug.send(city_availability) await channel_debug.send(response) for entry in city_availability: if entry['send_msg_flag'] == True: print(entry['city'], "is available") channel_city = discord.utils.get(client.get_all_channels(), name=entry['city'].lower()) filtered_response = [element for element in response if element['city'].lower() == entry['city'].lower()] formatted_output = format_data(filtered_response) await channel_city.send(formatted_output) else: print(entry['city'], "is NOT available") # 同时启动检测协程和Bot await asyncio.gather( check_and_update_db(), client.start('你的Bot令牌') ) if __name__ == '__main__': asyncio.run(main())
注意事项
- 数据库选择:简单场景用
sqlite3即可,高并发场景用异步库比如aiosqlite或asyncpg - 数据一致性:检测协程更新数据库时用事务,避免Bot读到半更新的数据
- 定时逻辑:检测协程用
asyncio.sleep(),不要用同步的time.sleep(),否则会阻塞协程
总结
- 快速解决阻塞问题:选第一种方案的线程池实现,改动最小
- 长期扩展需求:选第二种方案,解耦逻辑更稳定
- 之前
asyncio.create_task()无效的原因:check_all()是同步阻塞函数,必须放到线程/进程里执行,不能直接在事件循环线程跑
内容的提问来源于stack exchange,提问作者Ilyas
相关产品推荐
相关产品推荐

