如何为Telepot开发的Telegram医疗调研Bot实现定时消息推送?
修复Telepot + APScheduler重复提醒问题
看起来你踩了一个很典型的调度器使用坑——每次用户发消息就新建调度器,导致多任务重复触发。我帮你梳理下问题根源,然后直接给修复后的代码:
问题核心
你的on_chat_message方法会在**每一条用户消息(包括调研过程中的回复)**触发时执行,而你在这个方法里每次都创建了新的BackgroundScheduler并添加重复任务。这就意味着用户每回复一次调研问题,就多一个调度器在后台跑,时间一长自然会收到N条重复提醒。
修复方案
- 把调度器初始化移到
ChatHandler的构造函数:每个用户会话只创建一次调度器,避免重复实例化。 - 使用唯一任务ID + 检查任务是否存在:防止重复添加同一个任务。
- 可选:改用Cron表达式实现每日定时:如果需要固定时间(比如每天早8点),比interval更精准。
- 会话超时关闭调度器:避免无用的调度器占用资源。
修复后的完整代码
import time import telepot import json from telepot.loop import MessageLoop from telepot.namedtuple import ReplyKeyboardMarkup from telepot.delegate import pave_event_space, per_chat_id, create_open from apscheduler.schedulers.background import BackgroundScheduler # Load token TOKEN = 'klaghklsfhglka' class main(telepot.helper.ChatHandler): def __init__(self, *args, **kwargs): super(main, self).__init__(*args, **kwargs) self.indicator = '0' self.data = {} # 每个用户会话仅初始化一次调度器 self.schedule = BackgroundScheduler() # 设置任务唯一ID,避免重复添加 self.reminder_job_id = f"reminder_{self.chat_id}" # 初始化时添加一次定时任务(这里用interval,也可以换成cron) self._setup_reminder() def _setup_reminder(self): """内部方法:设置提醒任务,仅执行一次""" def repeat_message(): self.sender.sendMessage('Type /survey to start today\'s survey.') # 检查任务是否已存在,不存在才添加 if not self.schedule.get_job(self.reminder_job_id): # 这里用interval每24小时发送,如果你要固定时间,换成cron:比如每天8点 # self.schedule.add_job(repeat_message, 'cron', hour=8, id=self.reminder_job_id) self.schedule.add_job(repeat_message, 'interval', hours=24, id=self.reminder_job_id) self.schedule.start() def on_chat_message(self, msg): content_type, chat_type, chat_id = telepot.glance(msg) print(content_type, chat_type, chat_id) print(msg['text']) # Survey function def survey(): if self.indicator == 'choose_symptom': mark_up = ReplyKeyboardMarkup( keyboard=[['Pain'], ['Trouble with Breathing'], ['Tiredness'], ['Appetite'], ['Feeling sick'], ['Vomitting'], ['Bowel upset'], ['Hair loss']], one_time_keyboard=True) self.sender.sendMessage('Was there any discomfort today?', reply_markup=mark_up) self.indicator = 'rate_symptom' elif self.indicator == 'rate_symptom': self.data['symptoms'] = msg['text'] mark_up = ReplyKeyboardMarkup( keyboard=[['Not at all'],['A little'],['Somewhat'],['Very much']], one_time_keyboard=True) self.sender.sendMessage('Please rate the feeling.', reply_markup=mark_up) self.indicator = 'set_data' elif self.indicator == 'set_data': self.data['symptoms_score'] = msg['text'] self.data['date'] = time.ctime(msg['date']) with open('data.json', 'a') as handle: json.dump(self.data, handle) handle.write("\n") self.sender.sendMessage('Thank you for feedback.') # 调研完成后可以选择暂停提醒,或者保持每日发送 # self.schedule.pause_job(self.reminder_job_id) return # Detect start messages if msg['text'] == '/start': self.sender.sendMessage('Hello.', parse_mode='Markdown') elif msg['text'] == '/survey': self.indicator = 'choose_symptom' print("Survey started...") survey() def on_close(self, exception): """会话关闭时清理调度器""" if self.schedule.running: self.schedule.shutdown() super().on_close(exception) bot = telepot.DelegatorBot(TOKEN, [ pave_event_space()( per_chat_id(), create_open, main, timeout=30 ), ]) MessageLoop(bot).run_as_thread() print('Listening...') while 1: time.sleep(10)
关键修改点说明
- 调度器移到
__init__:每个用户会话只有一个调度器,不会重复创建。 - 唯一任务ID:用
chat_id拼接任务ID,确保每个用户的任务唯一,避免冲突。 - 任务存在性检查:添加任务前先判断是否已存在,防止重复添加。
- 使用
self.sender代替全局bot:Telepot的ChatHandler内置了sender属性,比直接用全局bot更安全规范。 - 会话关闭清理:
on_close方法里关闭调度器,避免内存泄漏。 - 可选Cron定时:注释里提供了固定时间发送的写法,比如每天早8点,比interval更适合每日固定时间的需求。
这样修改后,每个用户只会收到每日一次的提醒,不会再出现重复消息的问题啦!
内容的提问来源于stack exchange,提问作者Nicopii
相关产品推荐
相关产品推荐

