想做一个定时在 QQ 群发消息、每天定时提醒的机器人,但搜了一圈资料后发现,要么框架太老跑不起来,要么只给概念不给能跑的代码。这篇文章就用一套足够简单、足够快的方案,把定时任务 QQ 机器人的完整搭建过程讲清楚,包含架构原理、完整可运行代码、常见报错排查和生产环境建议。新手可以照着一步步配置,有基础的开发者可以直接复制代码改配置使用。
1. 定时任务 QQ 机器人是什么,能做什么
1.1 什么是 QQ 机器人
QQ 机器人本质上是一个能接收 QQ 消息事件、主动调用 QQ 接口发送消息的自动化程序。它通过协议层与 QQ 账号建立连接,然后由业务代码决定“收到什么消息做什么回应”或者“到什么时间点主动发什么消息”。
定时任务 QQ 机器人,就是在普通 QQ 机器人的基础上,加入定时任务调度能力。普通的机器人是事件驱动型,比如群里有人发消息才触发回复;而定时机器人既有事件处理能力,又有时间驱动能力,比如每天上午 9 点自动在群里发送打卡提醒,或者每隔 30 分钟向指定用户推送一条状态信息。
1.2 定时任务机器人能解决什么问题
定时任务机器人最常见的应用场景包括:
- 每日早安、晚安定时发送。
- 每周定期发送周报提醒或会议提醒。
- 每隔一段时间自动拉取天气、股票、赛事等数据并推送到群。
- 监控类任务定时检查服务状态,异常时主动通知群成员。
- 群活跃度维护,定时发送话题引导消息。
这类需求的共性特点是:不需要用户主动发消息触发,而是按照指定时间点或时间间隔自动执行。如果只靠人工操作,很容易遗漏,定时任务机器人可以把这些重复工作自动化。
1.3 主流实现方案对比
当前实现 QQ 机器人主要有三条技术路线:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 基于 OneBot 协议实现(如 NapCat、LLOneBot) | 协议实现负责登录 QQ,通过 WebSocket/HTTP 与业务代码通信 | 生态成熟,社区资料多,接入简单 | 账号存在风控风险,需使用小号 |
| 基于 QQ 官方机器人平台 | 使用官方开放接口 | 合规、稳定 | 功能受平台限制,个人订阅类需求支持有限 |
| 自写协议层 | 直接解析 QQ 客户端协议 | 可控性强 | 工作量大,逆向成本高,不建议个人去碰 |
本文选择的是第一种方案,也是目前个人开发者做 QQ 机器人的主流方式:使用 OneBot 协议实现负责账号接入,Python 端通过 aiocqhttp 作为协议客户端,再结合 APScheduler 完成定时任务。
2. 环境准备与整体架构
2.1 技术选型说明
整个项目涉及四个核心组件:
- OneBot v11 协议实现:负责账号登录、消息收发、与 QQ 服务器的数据交互。本文以社区常用的 NapCat 为例,类似的还有 LLOneBot、Lagrange 等。
- aiocqhttp:Python 端 OneBot v11 协议的 WebSocket 客户端库,负责接收消息事件、调用发送接口。
- APScheduler:Python 业界常用的任务调度库,支持 cron 表达式、间隔触发、日期触发等模式。
- Python 3.10/3.11:运行环境。
这套组合的思路是:OneBot 协议实现负责“能和 QQ 通信”,aiocqhttp 负责“Python 侧能收发消息”,APScheduler 负责“定时触发动作”,三个组件各司其职,代码量可以压缩到很少。
2.2 安装 Python 环境与依赖
建议使用 Python 3.10 或 3.11。版本太新时,个别依赖库可能存在 asyncio 兼容性问题,如果你想省心,直接用 3.11 比较稳妥。
创建项目目录并安装依赖:
mkdir qq_timer_bot cd qq_timer_bot pip install aiocqhttp APScheduler可以创建一个requirements.txt方便复现:
aiocqhttp>=0.9.0 APScheduler>=3.10.0安装完成后可以用一行命令验证:
python -c "import aiocqhttp, apscheduler; print('Dependencies OK')"如果打印出Dependencies OK,说明依赖安装成功。
2.3 准备 OneBot 协议登录端
这一步容易被新手卡住。简单来说,OneBot 协议实现是一个独立程序,你需要:
- 从对应项目 Release 页面下载适合你系统的版本。
- 启动程序,按照提示登录一个 QQ 号。建议使用小号,不要使用常用主号。
- 在控制台或配置文件中新增一个“反向 WebSocket 客户端”连接,地址填写后面 Python 机器人监听的地址。
以本文的配置为例,Python 机器人监听127.0.0.1:8080,那么反向 WebSocket 的地址就填:
ws://127.0.0.1:8080/ws/注意不同 OneBot 实现的配置界面差异较大,具体按钮名称可能不同,但核心概念都是“正向 WebSocket”和“反向 WebSocket”。aiocqhttp 默认是服务端模式,所以要配置的是反向 WebSocket 客户端。
启动顺序很重要:要先启动 Python 机器人,再启动 OneBot 协议端,或者两者都启动后协议端会自动重连。如果协议端先启动,连接不上会自动重试,问题也不大。
2.4 项目结构与端口规划
整个项目建议这样组织:
qq_timer_bot/ ├── requirements.txt ├── config.py # 配置项集中管理 ├── tasks.py # 定时任务定义与调度器 └── bot.py # 机器人主程序端口规划上,8080是 Python 机器人的 WebSocket 监听端口,OneBot 协议端作为客户端主动连接过来。如果本机端口被占用,可以换成其他端口,但需要同步修改 OneBot 协议端配置。
3. 核心原理拆解
3.1 OneBot v11 协议与事件模型
OneBot v11 是一套标准的 QQ 机器人通信协议,它定义了消息事件、通知事件、请求事件以及各种 API 调用格式。
在 OneBot 的模型里,QQ 客户端负责与 QQ 服务器通信,当群里有新消息时,OneBot 协议实现会把消息事件封装成 JSON,通过 WebSocket 推送给业务端。业务端也可以调用 API,让 OneBot 协议实现去发送消息。
事件主要分为三类:
| 类型 | 示例 | 说明 |
|---|---|---|
| 消息事件 | group_message、private_message | 群消息、私聊消息 |
| 通知事件 | group_increase、friend_add | 群成员变动、好友添加 |
| 请求事件 | friend_request、group_request | 好友申请、加群申请 |
aiocqhttp 会把这些事件转换成 Python 对象,你通过装饰器@bot.on_message()、@bot.on_notice()即可注册对应的处理函数。
3.2 aiocqhttp 的工作方式
aiocqhttp 是 NoneBot 生态中的轻量级 OneBot v11 SDK。它的工作模式如下:
- 启动时创建一个 HTTP/WebSocket 服务,默认路径是
/ws/。 - 等待 OneBot 协议端建立反向 WebSocket 连接。
- 收到事件后,将 JSON 数据转换为
Event对象,分发到注册的处理器。 - 业务代码调用
bot.call_action()时,通过已经建立的 WebSocket 连接发送 API 请求给协议端。
简单理解,aiocqhttp 帮你处理了 WebSocket 连接、事件解析、API 封装,你只需要关心业务逻辑。
3.3 APScheduler 调度模型
APScheduler 是 Python 里非常强大的任务调度库。它支持四种触发器:
| 触发器 | 适用场景 | 示例 |
|---|---|---|
date | 指定时间执行一次 | 明天上午 10 点执行一次 |
interval | 固定间隔循环执行 | 每 30 分钟执行一次 |
cron | 按时间表达式执行 | 每天早上 9 点执行 |
combine | 组合多个条件 | 两周一次的周一执行 |
在定时任务机器人的场景中,最常用的是cron和interval。
cron 触发器示例:
# 每天早上 9 点发消息 CronTrigger(hour=9, minute=0) # 每周一早上 9 点发消息 CronTrigger(day_of_week="mon", hour=9, minute=0) # 每隔一周的周一早上 9 点执行 CronTrigger(day_of_week="mon", week="1/2", hour=9, minute=0)interval 触发器示例:
# 每 30 分钟执行一次 IntervalTrigger(minutes=30) # 每 2 小时执行一次 IntervalTrigger(hours=2)week="1/2"表示从第一周开始,每间隔 2 周执行一次,配合day_of_week="mon"就是“每隔一周的周一执行”。
3.4 定时任务与机器人事件循环的协作机制
这里涉及一个关键点:APScheduler 的异步调度器AsyncIOScheduler必须运行在机器人同一个 asyncio 事件循环中。
Python 的 asyncio 模型下,aiocqhttp 的 WebSocket 服务在一个事件循环中运行,APScheduler 的异步任务也必须挂载到同一个事件循环里,否则会出现定时任务不触发、协程没有运行环境等诡异问题。
因此,正确做法是:
- 先创建
AsyncIOScheduler。 - 在
bot.run()之前调用scheduler.start()。 - 这样当
bot.run()启动事件循环后,调度器也能在同一循环中执行任务。
如果定时任务里执行的是 async 函数,那么 APScheduler 会自动把协程提交到当前事件循环,不需要额外处理。
4. 完整实战案例
下面我们实现一个具备如下功能的定时任务机器人:
- 每天早上 9 点向指定群发送早安提醒。
- 每 30 分钟向指定 QQ 发送私聊消息。
- 群内发送“任务列表”可以查看当前定时任务。
- 管理员发送“暂停任务”和“恢复任务”可以控制调度器。
4.1 编写 config.py
所有需要手动调整的配置集中放在这里:
# 文件:config.py # 目标群号,替换成你自己的测试群 GROUP_ID = 12345678 # 目标 QQ 号,用于定时私聊测试 TARGET_QQ = 123456789 # 管理员 QQ,用于执行暂停、恢复等控制命令 ADMIN_QQ = 123456780 # 机器人 WebSocket 服务监听地址和端口 HOST = "127.0.0.1" PORT = 8080 # 定时任务参数 MORNING_HOUR = 9 MORNING_MINUTE = 0 INTERVAL_MINUTES = 30实际使用时,把GROUP_ID、TARGET_QQ、ADMIN_QQ替换成你自己的 QQ 号或群号。
4.2 编写 tasks.py
这个文件负责创建调度器、注册定时任务、提供任务查询方法:
# 文件:tasks.py from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.cron import CronTrigger from apscheduler.triggers.interval import IntervalTrigger # 使用 AsyncIOScheduler,任务函数可以是 async 函数 scheduler = AsyncIOScheduler(timezone="Asia/Shanghai") # 发送函数由 bot.py 注入,避免循环导入 send_group = None send_private = None def init_tasks(send_group_func, send_private_func): global send_group, send_private send_group = send_group_func send_private = send_private_func # 每天早上 9 点发送群消息 scheduler.add_job( morning_notice, CronTrigger(hour=9, minute=0), id="morning_notice", replace_existing=True, misfire_grace_time=60, ) # 每 30 分钟发送一次私聊消息 scheduler.add_job( interval_notice, IntervalTrigger(minutes=30), id="interval_notice", replace_existing=True, misfire_grace_time=60, ) async def morning_notice(): if send_group: await send_group("早上好!定时任务机器人开始工作啦~") async def interval_notice(): if send_private: await send_private("这是一条每 30 分钟触发一次的定时私聊消息。") def get_all_jobs(): jobs = scheduler.get_jobs() return [ { "id": job.id, "next_run_time": job.next_run_time.strftime("%Y-%m-%d %H:%M:%S") if job.next_run_time else "None", } for job in jobs ]代码中的misfire_grace_time=60表示任务原定执行时间错过 60 秒内仍允许补执行。如果机器人因为短暂卡顿错过了执行时间,这个参数能有效降低丢任务概率。
4.3 编写 bot.py
这是机器人的主程序,负责建立 WebSocket 服务、注册消息处理器、启动调度器:
# 文件:bot.py import config from aiocqhttp import CQHttp from tasks import scheduler, init_tasks, get_all_jobs # 创建机器人实例 bot = CQHttp() async def send_group_message(message: str): try: await bot.call_action("send_group_msg", group_id=config.GROUP_ID, message=message) except Exception as e: print(f"[定时任务] 群消息发送失败: {e}") async def send_private_message(message: str): try: await bot.call_action("send_private_msg", user_id=config.TARGET_QQ, message=message) except Exception as e: print(f"[定时任务] 私聊消息发送失败: {e}") @bot.on_message() async def on_message(event): text = event.message.strip() if text == "hello": await bot.send(event, "你好!我是定时任务机器人。") elif text == "任务列表": jobs = get_all_jobs() if jobs: text_list = "\n".join( f"任务 {job['id']}:下次执行 {job['next_run_time']}" for job in jobs ) await bot.send(event, f"当前定时任务如下:\n{text_list}") else: await bot.send(event, "当前没有定时任务。") elif text == "暂停任务" and str(event.user_id) == str(config.ADMIN_QQ): scheduler.pause_all() await bot.send(event, "已暂停全部定时任务。") elif text == "恢复任务" and str(event.user_id) == str(config.ADMIN_QQ): scheduler.resume_all() await bot.send(event, "已恢复全部定时任务。") if __name__ == "__main__": # 注入发送函数 init_tasks(send_group_message, send_private_message) # 先启动调度器,再启动机器人,保证两者使用同一个事件循环 scheduler.start() print("定时任务调度器已启动。") print(f"反向 WebSocket 服务已启动:ws://{config.HOST}:{config.PORT}/ws/") # 启动机器人服务 bot.run(host=config.HOST, port=config.PORT)bot.call_action()是 aiocqhttp 提供的通用 API 调用方法,第一个参数是 OneBot 协议中的动作名,后面是参数。虽然也可以直接通过bot.send_group_msg、bot.send_private_msg这种属性式写法调用,但call_action更通用清晰。
4.4 启动与运行
整个启动流程如下:
- 启动 Python 机器人:
python bot.py看到如下输出表示机器人服务已打开:
定时任务调度器已启动。 反向 WebSocket 服务已启动:ws://127.0.0.1:8080/ws/启动 OneBot 协议端,配置反向 WebSocket 地址为
ws://127.0.0.1:8080/ws/。协议端连接成功后,你会看到输出中新增了连接日志。
用另一个 QQ 向机器人发送
hello,机器人会回复你好。
4.5 执行结果验证
- 在目标群中,每天 9 点会收到早安提醒。
- 目标 QQ 每 30 分钟会收到定时私聊消息。
- 给机器人发
任务列表,它会返回类似内容:
当前定时任务如下: 任务 morning_notice:下次执行 2025-07-10 09:00:00 任务 interval_notice:下次执行 2025-07-09 10:30:00- 管理员发送
暂停任务后,任务不再触发;发送恢复任务后恢复执行。
同时需要注意的是,定时任务机器人需要保持 Python 程序长期运行。如果是本地开发机,建议放到云服务器、路由器等可以 7x24 小时开机的环境。
5. 常见问题与排查思路
5.1 高频问题汇总表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 机器人收不到消息 | WebSocket 连接未建立 | 检查连接地址是否完整,端口是否正确 |
| 定时任务不触发 | 调度器未启动或事件循环不一致 | 确认scheduler.start()在bot.run()之前调用 |
| 发送消息报错 | 账号掉线或 API 参数错误 | 查看 OneBot 协议端日志,确认账号在线状态 |
| 定时任务执行多次 | 程序重复启动 | 使用进程锁或检测端口占用 |
| Python 3.12 启动报错 | asyncio API 变化 | 切换 Python 3.11,或使用维护更新更积极的框架 |
| 消息发不出,提示风控 | 发送频率过高 | 降低频率,使用小号,避免营销内容 |
5.2 连接不上的排查步骤
如果机器人收不到消息,按下面顺序排查:
第一步,确认 OneBot 协议端配置的地址是否为:
ws://127.0.0.1:8080/ws/注意末尾的/ws/不能少。如果端口不是 8080,同步修改两处。
第二步,确认 Python 机器人是否真的在监听:
netstat -ano | findstr 8080Linux 下用:
ss -tlnp | grep 8080第三步,看 OneBot 协议端的日志。连接成功时会有一条类似WebSocket connected的日志,如果一直连接失败,多半是地址写错或端口被防火墙拦截。
5.3 定时任务不触发的排查步骤
如果机器人能正常收发消息,但定时任务不触发,优先检查:
scheduler.start()