开头
先说个现象:我最近在技术群里被问得最多的一句话就是——“AI 定时任务到底怎么搭?我看别人能自动写日报、自动盯价,我也想搞一套。”
这种需求其实一点都不玄乎。我大概从去年开始琢磨这块,一开始就是个 Python 脚本,定时调大模型接口,早上 8 点把日报推到群里;后来在公司里做数据工程,又用 XXL-JOB 管理了一堆带 AI 分析的任务。折腾下来最大的感受是:AI 定时任务的核心难点,根本不在“定时”这两个字,而在怎么把数据采集、AI 处理、结果推送这条链路设计稳定。这篇文章就把我实际跑过的方案、踩过的坑、以及可以直接抄作业的配置思路都写出来,想搞个人自动化也好,想在公司落地也好,希望都能帮上忙。
1. 先把需求捋清楚:AI定时任务到底在跑什么
1.1 定时只是触发器,真正值钱的是后面的AI执行链路
很多人一看到“AI 定时任务”这个词,第一反应就是“定时调一下大模型 API 不就行了”。确实,用 Cron 或者 Spring Boot 的 @Scheduled,到点发一个 HTTP 请求,这事儿本身不难。但如果你只是定时把一段 Prompt 丢给大模型,然后把返回的内容打成日志,那基本没什么用——这等于把大模型当成一个定时闹钟,完全没有发挥出自动化的价值。
我习惯把 AI 定时任务拆成四段链路来看:
- 触发源:到点触发,也可以通过消息队列、事件驱动触发;
- 数据获取:从数据库、API、文件、RSS 等地方把原始数据拉回来;
- AI 处理:对数据做总结、分类、生成文本、提取关键信息等;
- 结果分发:推送到 IM 机器人、写入文档、发邮件,或者存库。
用一个生活化的类比:定时任务就像是你的智能闹钟,AI 则像那个被闹钟叫醒的秘书。闹钟本身只会把你叫醒,但秘书醒来后会帮你查邮件、整理日程、把今天要干的事列成清单。你真正需要的不是“闹钟响”,而是“闹钟响完之后秘书把事办妥”。所以设计一个 AI 定时任务,重点永远在“AI 执行链路”上,定时只是最外层的启动条件。
1.2 哪些场景最适合交给AI定时任务
从我实际接触到的需求来看,适合用 AI 定时任务跑的场景,大多具备三个特征:数据源稳定、输出格式固定、需要周期性重复。如果一件事你每天、每周都要做,而且做完后的结果都是“把一堆信息整理成一段话”,那它就非常适合自动化。
这里先列一个常见的场景对照表,后面我会挑四个最典型的展开讲:
| 场景 | 推荐触发频率 | 常用数据源 | AI 主要工作 | 输出形式 |
|---|---|---|---|---|
| 日报/周报 | 每天/每周定时 | Git 提交记录、任务系统 API、工时数据 | 汇总要点、生成结构化日报 | 推送到 IM / 邮件 |
| 新闻/行业信息聚合 | 每小时/每天 | RSS、新闻 API、网页爬虫 | 摘要、打标签、去重聚合 | Markdown 文档 / 推送 |
| 个人计划与日程 | 每天早上 | 日历 API、待办事项清单 | 生成今日计划、优先级排序 | 同步到日历 / 推送 |
| 商品价格监控 | 每 30 分钟/1 小时 | 电商公开 API、商品页面 | 价格波动分析、购买建议 | 提醒推送 |
| 数据指标异常检测 | 每小时 | 数据库、监控系统 API | 生成诊断说明、异常摘要 | 告警邮件 |
| 行业竞品动态分析 | 每天 | 官网更新、资讯平台 | 对比分析、变化摘要 | 日报附加栏目 |
如果你发现自己的需求也踩中了其中一种,那就可以直接往下看具体的方案设计。如果哪个场景不在表格里也别怕,后面讲的设计思路完全能套用。
2. 方案选型:从Cron到分布式调度,选型比写代码更重要
2.1 人人都会的Cron表达式,但很多人写错
不管用哪种框架,定时任务都绕不开 Cron 表达式。这东西看起来就是五个或六个字段,但实际上有非常多的细节坑。
我先给一份最基础的速查表(Linux Cron 的五位格式是:分 时 日 月 周):
| 表达式 | 含义 |
|---|---|
0 8 * * * | 每天早上 8:00 |
*/5 * * * * | 每 5 分钟 |
30 9 * * 1 | 每周一早上 9:30 |
0 0 1 * * | 每月 1 号 0:00 |
0 2 * * 7 | 每周日 2:00(部分系统 0 也表示周日) |
我见过不少人在“日”和“周”两个字段同时填值,结果任务在奇怪的时间反复触发。比如0 8 1 * 1这个表达式,在 Linux 的 Vixie Cron 里是“每月 1 号”和“每周一”两个条件取“或”关系执行的,也就是说每个月 1 号会跑一次,每个周一还会跑一次。如果你只想让它在“每月 1 号且是周一”才执行,就得单独写脚本判断,或者用更专业的调度平台。
另外大家容易忽略的是服务器时区。很多云服务器默认用的 UTC 时间,你写0 8 * * *想每天早上八点跑,结果每天下午四点才跑。真要排查这种问题特别浪费时间,所以搭建任务前我都是先date -R看一眼时区,如果不一致,要么在系统层面改时区,要么在 Cron 表达式里把 UTC 偏移量算进去。
2.2 Spring Boot定时任务与XXL-JOB怎么选
如果你是 Java 技术栈,最自然的方案是先用 Spring Boot 自带的@Scheduled注解,比如这样:
@Scheduled(cron = "0 30 8 * * ?") public void runDailyAiReport() { // 拉取数据 -> 调用大模型 -> 推送结果 }这种写法胜在简单,不用引额外依赖,项目里随便加一个方法就能跑起来。但它有几个硬伤:第一,任务跑挂了没有统一的重试和告警机制,你只能自己加 try-catch 和日志;第二,如果服务是多实例部署,同一个定时任务会在每个实例上各执行一次,造成重复推送;第三,想动态改下一次执行时间,得改代码重新发布。
当任务量上来之后,我建议直接上 XXL-JOB 这类分布式调度平台。它做对了几件事:任务在管理后台可视化配置、执行器和调度器分离、支持失败重试和告警、还可以通过 API 动态调整 Cron。你只需要在业务系统里写一个方法,加上@XxlJob("dailyAiReport")注解,剩下的生命周期管理都交给调度平台。尤其在我们团队里,有几十个 AI 分析任务共用一套调度平台,哪个任务昨天挂了、重试了几次、执行日志长什么样,后台一眼就能看清楚。这个好处在任务少的时候感觉不明显,任务一多就特别明显。
2.3 ETL场景:Spoon/Kettle的数据同步定时配置
很多 AI 定时任务不是直接面向用户数据的,而是先要把数据从业务库同步到分析库,再由下游任务去调大模型做分析。这块我目前用得最顺手的是 Kettle(也叫 Spoon)做 ETL 数据同步。它的定位很简单:把数据从一个地方抽出来,做清洗,再灌到另一个地方。你可以用图形界面画好转换流程,然后通过命令行工具kitchen.sh来跑 Job。
在实际项目中,我的做法是先把 Kettle 的转换文件(.ktr)和 Job 文件(.kjb)放到服务器固定目录,然后在 Linux 的 Crontab 里写一条定时任务:
30 0 * * * /data/soft/kettle/kitchen.sh -file=/data/jobs/sync_data_for_ai.kjb -level=Basic >> /data/logs/kettle/sync_$(date +\%F).log 2>&1这条命令的意思是每天凌晨 0:30 执行一次同步,日志按日期落盘。注意 Crontab 里如果要用date命令拼文件名,百分号要写成\%,这个细节卡过我好几次。
Kettle 也可以在 Job 的 START 组件里直接配置定时触发,但个人建议不要把调度和任务本身耦合在一起,统一由外部的 Crontab 或调度平台来管,这样可观测性强很多。你只需要让 Kettle 专注做“数据同步”这件事,等它同步完,下游的 AI 分析任务再接手。
2.4 个人项目的不错选择:Python schedule和n8n
不是所有人都用 Java 项目。我自己很多个人小项目用的是 Python,最常用的是一个叫schedule的第三方库,用起来非常直白:
import schedule import time schedule.every().day.at("08:00").do(run_daily_ai_report) schedule.every(30).minutes.do(run_price_monitor) while True: schedule.run_pending() time.sleep(10)这种方案的好处是零配置,代码里写清楚节奏就行,适合个人服务器上跑三五个任务的情况。但它要求进程常驻,如果脚本崩了,任务就停了。所以我在服务器上配合 systemd 或者 pm2 把脚本挂成守护进程,保证意外退出后能自动拉起。
如果你不想写太多代码,n8n 是个很好的低代码选项。它本身就是一个工作流引擎,可以在网页上拖拽出“触发 -> HTTP 请求 -> AI 节点 -> 推送”的流程,还内置了 Cron Trigger 节点。它的优势在于可视化,老板或者同事想看看你的任务是怎么跑的,打开界面一拖就能看懂。但劣势也明显:流程复杂了以后,节点的排错体验不如直接写代码来得顺畅。我的建议是:5 个任务以内用 n8n 或 Python schedule 都行,超过 5 个而且相互之间有依赖关系,就老老实实上正式调度平台。
3. 四个高频场景的AI定时任务实操
3.1 自动生成日报:再多系统数据也不用手动汇总
先聊聊最火的“AI 自动写日报”。这个场景我复现过很多版本,核心流程无非四步:拉取工作数据、组织 Prompt、调用大模型、推送结果。难就难在第一步,因为企业里的数据分散在 Git、任务管理平台、数据库甚至聊天记录里,不把数据源打通,后面做得再花哨都是空中楼阁。
我这里给一个最小可用的 Python 示例,数据源先用 Git 提交记录和任务系统 API 来代替,方便你理解整个链路:
import requests import subprocess import datetime import os API_KEY = os.getenv("LLM_API_KEY") API_URL = os.getenv("LLM_API_URL", "https://api.example.com/v1/chat/completions") WEBHOOK_URL = os.getenv("DINGDING_WEBHOOK_URL") def collect_data(): # 拉取最近一天 git 提交信息 since = datetime.datetime.now() - datetime.timedelta(days=1) since_str = since.strftime("%Y-%m-%d %H:%M:%S") commits = subprocess.check_output( ["git", "log", f"--since={since_str}", "--pretty=%s"], cwd="/path/to/project" ).decode("utf-8", errors="ignore").strip() # 模拟从任务系统 API 拉取数据 tasks = requests.get( "https://task.example.com/api/my-tasks", params={"date": datetime.date.today().isoformat()}, headers={"Authorization": "Bearer " + os.getenv("TASK_TOKEN")}, timeout=10 ).json() return commits, tasks def build_prompt(commits, tasks): return f""" 请根据以下工作内容帮我生成一份日报,要求: 1. 用100字以内的中文,分条列出当天完成的主要工作 2. 结尾给出明天的工作建议 git 提交: {commits} 任务系统记录: {tasks} """ def call_llm(prompt): resp = requests.post(API_URL, json={ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": prompt}], "temperature": 0.3 }, headers={"Authorization": f"Bearer {API_KEY}"}, timeout=60) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] def push_report(report): data = {"msgtype": "text", "text": {"content": f"【AI日报】\n{report}"}} requests.post(WEBHOOK_URL, json=data, timeout=10) def run_daily_ai_report(): try: commits, tasks = collect_data() prompt = build_prompt(commits, tasks) report = call_llm(prompt) push_report(report) except Exception as e: # 失败日志必须完整记录 print(f"[日报任务失败] {datetime.datetime.now()} {e}", flush=True)这个脚本看起来简单,但有几个细节是真实跑起来才体会到的。第一,模型参数里的temperature我建议调低到 0.3 左右,日报这种任务需要稳定输出,温度高容易发挥不稳定;第二,所有外部请求都要设置timeout,不要无限等下去;第三,失败信息里一定要带时间戳,不然事后查日志根本定位不到是哪个批次的问题。
跑通脚本之后,你可以把它挂到 Crontab 里:
0 9 * * 1-5 /usr/bin/python3 /data/scripts/daily_report.py >> /data/logs/daily_report.log 2>&1这样每个工作日早上 9 点,AI 就会自动帮你把日报写好并推到群里。
3.2 定时抓新闻并汇总摘要:信息聚合自动化
“AI 定时看新闻”是我个人使用频率最高的场景。做技术的人都有一种信息焦虑,怕错过重要更新,但每天刷几十个网站又太费时间。我的做法是:用一个定时任务把各个信源的更新抓回来,先做去重和过滤,再调大模型生成摘要,最终汇总成一个简报文档或一条推送。
整个流程可以拆成以下步骤:
- 订阅信源:找到目标网站的 RSS 地址,或者用第三方搜索 API。很多技术博客、开源项目 Release 页面都支持 RSS;
- 定时抓取:每一到两小时跑一次,把新增条目的标题、链接、发布时间抓下来;
- 去重过滤:用 URL 或标题哈希去重,再按你配置的关键词做一次粗筛;
- AI 摘要:把过滤后的内容拼接成 Prompt,让大模型为每篇生成一句话摘要,并归类;
- 汇总推送:把摘要生成 Markdown 或纯文本,推送到企业微信机器人、钉钉群,或者写成本地文件。
下面是一段简化版的抓取和生成逻辑:
import feedparser import hashlib import requests import os def fetch_articles(): articles = [] feeds = ["https://example.com/rss.xml", "https://news.example.org/feed"] for feed_url in feeds: feed = feedparser.parse(feed_url) for entry in feed.entries[:20]: uid = hashlib.md5(entry.link.encode()).hexdigest() articles.append({"uid": uid, "title": entry.title, "link": entry.link}) return articles def get_summary(articles): text = "\n".join([f"- {a['title']}: {a['link']}" for a in articles]) prompt = f"请将以下新闻条目用一句话概括,并按照技术、产品、市场三个维度分组:\n{text}" resp = requests.post(...) # 调用大模型 return resp.choices[0].message.content这里有个很容易被忽略的坑:文章去重不能只看标题,标题可能一样但链接不同,更稳妥的是用 URL 去重。另外,AI 摘要生成时可以考虑让模型先返回 JSON 结构,方便后续程序化处理,而不是直接返回大段文本。
这类任务我建议把调度频率控制在每 2 小时左右,不要太频繁。新闻源更新再快,也没必要每分钟去拉一次,太频繁不仅浪费 API 配额,还容易被对方网站封掉 IP。
3.3 智能计划与日程管理:每天自动生成To-Do
这个场景适合个人效率控。我目前的一个典型用法是:每天早上自动抓取日历事件和待办清单,然后让 AI 根据我的目标、时间和优先级,生成当天的计划。
数据源通常来自两个方面:一是日历 API,比如 Google Calendar、飞书日历都有开放的接口可查当天的日程;二是你自己的待办清单,可能是 Notion 数据库、飞书多维表格,甚至是一个简单的 Markdown 文件。把这些数据拼成一个 Prompt 发给大模型,让它输出“今日三件要事”“时间块安排”“需要拒绝的事项”等内容。
这里给一个 Prompt 模板,你直接套用就行:
你是我的时间规划助手。请根据以下信息为我制定今天的计划: 1. 今天日期是:{date} 2. 我的日历安排是:{calendar_events} 3. 我的待办清单是:{todo_items} 4. 我本周的核心目标是:{weekly_goal} 要求: - 先列出今天最重要的3件事 - 再把剩余时间按时间块分配 - 最后指出哪些待办可以推迟或委托给他人 - 输出总字数控制在200字以内,用中文生成的结果可以直接推送给自己,也可以写回日历或待办工具。我的建议是不要全自动写入,先推送到手机端看一眼再决定。原因很简单,AI 模型对你某一天的实际精力情况没有感知,它生成的计划有时会过于理想化。把 AI 当成一个“私人的计划参谋”,而不是“全权的计划执行者”,体验会好很多。
如果要做全自动同步,目前最方便的是 Notion API 或者飞书开放平台的文档写入接口。给每个待办项建一个 document block,再设置优先级字段,操作并不复杂。但注意接口鉴权要放到环境变量里,不要硬编码在脚本中。
3.4 价格监控与提醒:大模型不只是“闹钟”
“盯价格”是很多人想搞的定时任务。最开始我理解的价格监控特别简单:每隔半小时抓一次商品价格,低于某个阈值就提醒。但跑了一段时间发现一个局限性——这种“机械阈值”模式只对绝对数值敏感,完全不考虑历史趋势。比如一件 100 元的商品低于 90 就提醒,但如果你错过了“双 11 降到 80 元”的信息,这个提醒就没什么价值了。
所以我在价格监控任务里引入 AI 的判断,不再是单纯判断“低于阈值”,而是让大模型基于历史价格曲线、当前优惠幅度、同类竞品价格,甚至用户设定的预算,输出一个“现在值不值得入手”的分析。整个流程可以这样设计:
- 定时抓取价格数据:可以通过商品页面的结构化数据接口,也可以是网页爬虫,存到数据库;
- 计算历史价格特征:用 SQL 提取近 N 天的最低价、平均价、近 24 小时价格走势;
- 触发判断:只有满足“当前价低于近 30 天最低价”或“优惠幅度超过 15%”这些条件时才调用大模型,避免频繁请求;
- 大模型生成购买分析:输入价格历史、商品描述、用户偏好,输出一段建议文本;
- 推送提醒:通过 Server 酱、钉钉机器人或邮件推送到手机上。
举个简化版的 SQL 查询:
SELECT product_name, price, MIN(price) OVER (PARTITION BY product_id ORDER BY created_at ROWS BETWEEN 29 PRECEDING AND CURRENT ROW) AS min_price_30d, AVG(price) OVER (PARTITION BY product_id ORDER BY created_at ROWS BETWEEN 29 PRECEDING AND CURRENT ROW) AS avg_price_30d FROM price_history WHERE product_id = 123 ORDER BY created_at DESC LIMIT 30;把这条查询的结果作为上下文传给大模型,Prompt 里带上你设定的预算和期望值,它就能生成比“价格跌破 100 元”这种规则式提醒更有参考价值的建议。
这里必须提醒一句:爬取电商价格时要严格遵守对方网站的服务条款和 Robots 协议,尤其注意请求频率,不要对目标站点造成压力。我一般设置成 1 小时抓一次,并且做随机间隔请求,尽量减少对源站的干扰。
4. 常见问题与排查技巧实录
4.1 Cron表达式“不执行”或“乱执行”
我接触下来,Cron 任务出问题最频繁的原因有三个:时区、字段、环境变量。
- 时区:服务器默认 UTC,你在本地想的是早上 8 点,crontab 执行时间却是 16 点。排查方法很简单,终端执行
date -R和timedatectl,看到不是本地时区就该意识到问题; - 字段:日和周同时设置时执行条件为“或”,无数新手在这里被坑。没关系,踩过一次之后你就会记住;
- 环境变量:Crontab 执行的环境和你手动终端执行的环境不完全一样。脚本里如果是用相对路径找文件,或者依赖你在 shell 里手动
export的变量,就会莫名失败。这种问题最隐蔽,我做了一个习惯:脚本内部一律用绝对路径,所有配置统一从.env文件加载或者用source /etc/profile预加载环境。
排查时最有效的办法,就是先看日志。Crontab 本身的执行记录可以用grep CRON /var/log/syslog(Ubuntu)或者journalctl -u cron查。你自己脚本的日志一定要重定向到文件,比如>> /data/logs/task.log 2>&1,这样至少能区分“任务压根没启动”和“任务启动后代码报错”这两种情况。
4.2 AI接口超时、限流与不稳定
调用大模型 API 是整条链路里最不稳定的环节。服务端可能超时,可能因为并发过高而不返回,可能一时网络抖动导致请求中断。我建议至少做三件事:
- 设置超时和重试:请求超时设置在 30 到 60 秒,失败后最多重试 3 次,每次间隔指数退避,比如 5 秒、20 秒、80 秒。千万别做无间隔重试,很容易把对方的限流策略打爆;
- 区分失败类型:是数据获取失败,还是 AI 生成失败?两者处理方式不一样。数据获取失败往往和源站稳定性有关,可以等下次再跑;AI 生成失败则可能换个 Prompt 就能成功,建议单独告警;
- 定义降级策略:AI 服务连续失败时,至少要把原始数据以文本形式推送给用户,别让整个任务看起来像“凭空消失”一样。
遍历任务如果比较多,我还建议给调大模型的环节加一个并发控制信号量,比如用 Python 的threading.Semaphore限制同时只能跑 2 个请求。并发太高不仅容易被限流,还会把脚本进程内存顶上去,后续任务全卡住。
4.3 任务依赖与数据没准备好
AI 任务经常依赖前置数据。比如你得等 Kettle 把昨夜的数据同步完,AI 日报才能生成;你得等爬虫抓完最新的商品信息,价格分析才有意义。如果不管依赖关系,到点就跑,大概率会出现“AI 分析了一个空表”这种很奇葩的结果。
依赖控制我总结了三种方式:
- 串行脚本:在同一个 Shell 脚本里按顺序执行,第一步失败直接退出,后续不执行;
- 调度平台依赖:XXL-JOB 里可以配置任务的后置任务,前一个成功才触发下一个;
- 数据就绪检查:在 AI 任务启动时去查数据库里今天的数据是否已存在,不存在就跳过或告警。
我建议最稳的做法是“你管调度,我管数据就绪”组合。调度平台负责什么时候跑,任务内部先做一次数据完备性校验,两边都有保障,缺一不可。
4.4 幂等性与重复提醒问题
定时任务一旦涉及重试机制,就一定会遇到重复执行的问题。比如手动执行了一次日报生成,结果发现推送没成功,又触发了一次重试,结果群里出现了两条日报。再比如价格监控脚本因为网络原因在一个小时内被调度了三次,用户收到了三条一模一样的提醒。
解决办法是给每次任务设计一个天然的业务幂等键。日报任务可以用report_date + user_id作为唯一标识,启动时先查库,当日已生成就直接跳过;价格提醒则可以在 Redis 里记录“上次提醒的最低价格”,只有当新价格比记录值更低时才再次提醒。核心原则就一条:任务可以重试,但结果不能重复产生。
4.5 服务器节假日和业务日历
这个问题在个人项目里不明显,但在公司场景中就特别重要。你肯定不想在周末早上 8 点收到一封“AI 日报”,更不想在法定节假日被钉钉群里的自动播报轰炸。所以我在设计 AI 定时任务时,会在任务启动后先判断“今天是否工作日”,如果不是就直接退出。
最简单的方式是维护一个节假日表,或者用现成的第三方库,比如 Python 的chinese_calendar库就能判断一个日期是不是工作日。在任务里加一行判断,能省去很多你完全意识不到的打扰。
import chinese_calendar def is_workday(): today = datetime.date.today() return chinese_calendar.is_workday(today) def run_daily_ai_report(): if not is_workday(): print("非工作日,跳过日报任务") return # 正常执行5. 最后分享几个让我受益很多的习惯
文章写到这儿,几个核心场景和排错方案都聊完了。最后再分享几个我在实际操作中觉得特别有用的小习惯。
第一个是给所有 AI 定时任务设置“试跑模式”和“正式模式”。你可以在脚本里加一个环境变量TASK_MODE=dry_run,试跑时只把结果打到日志和本地文件,不推送、不写入数据库。等确认内容没问题了再切到prod模式。这个习惯帮我避开了至少五次“推送错误摘要到群里”的尴尬。
第二个是 Prompt 模板和代码分离。我把所有任务的 Prompt 都放在单独的.md文件里,脚本通过读取文件来拼装请求参数。这样做的好处是,领导想改日报语气,你只改文本就行,根本不用动代码逻辑。时间长了你会发现,改 Prompt 的频率远高于改代码的频率。
第三个是保留足够详细的日志。我在每个任务里都会输出:开始时间、结束时间、数据量、调用模型耗时、成功失败状态。日志不仅是为了排障,也是后期优化任务的重要依据。比如发现某个任务每次要跑十分钟,一看日志发现大部分时间花在 AI 接口上,那你就可以考虑是不是要换更快的模型或者并发处理。
AI 定时任务的生态还在快速变化,但底层的设计思路不会变:稳定地拿数据、合理地用模型、可靠地发结果。把这三件事做好,剩下的就交给时间去验证了。