1. 为什么我要给 WorkBuddy 设一个“十点半闹钟”

每天早上到工位,泡好咖啡,打开电脑,第一件事不是看邮件,而是刷一遍昨天夜里到今早的行业动态、竞品更新、社区热帖。这件事听起来简单,但真正做起来非常碎:公众号、技术社区、社交平台、内部群聊、邮件列表,信息源散落在七八个地方,手动翻一遍至少二十分钟,而且很容易漏掉关键内容。更麻烦的是,有些信息有时效性,上午十点半之前如果没看到,可能就错过了一个值得跟进的线索。

我用的工具是 WorkBuddy,一个可以挂载技能、执行自动化任务的工作台。它本身支持定时触发、脚本执行和消息推送,于是我就想:能不能让它每天上午十点半,自动把过去二十四小时的关键信息汇总成一份 AI 日报,直接推送到微信里?这样我只需要在十点半看一眼微信,就能掌握当天需要关注的重点,剩下的时间全部留给深度工作。

这个项目的核心目标很明确: 用 WorkBuddy 作为调度中枢,用 deepseek-v4-flash 作为内容生成引擎,用微信小程序或微信消息通道作为接收端,实现一份每日自动生成、自动推送的 AI 日报 。它解决的不是“信息太少”的问题,而是“信息太散、筛选太慢、容易遗漏”的问题。适合所有需要跟踪行业动态的从业者、独立开发者、产品经理、运营人员,以及任何想用自动化手段给自己减负的人。

我前后调了三个版本才跑通。第一版死在定时任务不触发,第二版死在微信侧收不到消息,第三版才把链路彻底跑顺。下面我把整个设计思路、关键细节、实操步骤和踩过的坑全部拆开讲,你照着做就能复现。

2. 整体方案设计与核心思路拆解

2.1 为什么选 WorkBuddy 做调度而不是自己写 Cron

很多人第一反应是:不就是定时任务吗,服务器上写个 crontab 不就行了?我一开始也这么想,但实际跑下来发现,纯 Cron 方案有三个硬伤。

第一, 环境依赖太重 。你要自己维护一台常驻服务器,装 Python 环境、配依赖、处理日志轮转,还要保证网络稳定。对于只是想要一份日报的人来说,这是杀鸡用牛刀。

第二, 技能扩展不灵活 。日报的内容源会变,今天抓社区,明天可能加邮件,后天可能加内部知识库。纯脚本方案每加一个源就要改代码、重新部署,维护成本很高。

第三, 消息推送要自己对接 。微信侧的消息通道有各种限制,自己对接不仅麻烦,还容易因为频率问题被限制。

WorkBuddy 的优势在于,它把 定时调度、技能挂载、脚本执行、消息推送 这几件事打包成了一个工作台。你只需要定义“什么时候触发、触发后执行什么、执行完推送到哪里”,中间的运行环境、依赖管理、日志记录它帮你处理了。这就像你本来要自己盖一间厨房才能做饭,现在直接给你一个带灶台的操作间,你只管炒菜就行。

提示:WorkBuddy 的定时任务支持 Cron 表达式,也支持自然语言描述时间。我实测下来,用 Cron 表达式更稳,因为自然语言解析偶尔会有时区歧义。

2.2 deepseek-v4-flash 在链路里扮演什么角色

日报的生成分两步: 信息采集 和 内容生成 。采集靠脚本,生成靠模型。我选 deepseek-v4-flash 的原因很直接: 快、便宜、够用 。

日报这种场景不需要模型做复杂推理,它只需要把采集到的原始信息做三件事:去重、摘要、分类。deepseek-v4-flash 的响应速度在同类模型里属于第一梯队,生成一份八百字左右的日报,通常三到五秒就能返回。而且它的上下文窗口足够放下我采集的几十条原始信息,不需要做复杂的分片处理。

这里有个关键设计: 我不让模型直接生成最终文案,而是让它输出结构化 JSON 。比如:

{
  "date": "2025-06-01",
  "sections": [
    {
      "title": "行业动态",
      "items": [
        {"summary": "某平台发布新版本", "source": "社区", "importance": "high"}
      ]
    }
  ]
}

拿到 JSON 之后,我再在脚本里做格式化,拼成适合微信阅读的 Markdown 或纯文本。这样做的好处是,模型只负责它擅长的“理解和归纳”,排版和推送逻辑由代码控制,稳定性高很多。如果让模型直接写最终文案,它偶尔会加一些奇怪的修饰词,或者把来源搞混,反而增加排查成本。

2.3 微信侧接收方案的选择逻辑

微信侧接收有三条路可走: 微信小程序订阅消息、企业微信机器人、个人微信文件传输助手 。我三条都试过,最后选了小程序订阅消息作为主通道,企业微信机器人作为备用通道。

小程序订阅消息的优势是 正式、稳定、可追溯 。用户主动订阅之后,每天十点半收到一条服务通知,点进去就能看日报详情。缺点是需要开发一个小程序页面来承载内容,前期投入稍大。

企业微信机器人的优势是 零开发、五分钟接入 。你只需要在群里添加一个机器人,拿到 Webhook 地址,脚本直接 POST 过去就行。缺点是消息长度有限制,超过一定字数会被截断,而且样式比较单一。

个人微信文件传输助手理论上最简单,但实际用下来最不稳定,因为微信对自动化操作有风控,频繁发送容易触发限制。我不推荐这条路。

我的最终方案是: WorkBuddy 定时触发脚本,脚本采集信息后调用 deepseek-v4-flash 生成结构化日报,然后通过小程序订阅消息推送给订阅用户,同时把完整版日报 POST 到企业微信机器人作为备份 。这样即使小程序侧出问题,企业微信里也能看到内容。

3. 核心细节解析与实操要点

3.1 WorkBuddy 定时任务的配置细节

WorkBuddy 的定时任务配置界面里,有几个参数需要特别注意。

Cron 表达式 :我设的是 30 10 * * * ,意思是每天上午十点三十分触发。这里有个坑:WorkBuddy 默认使用 UTC 时间,如果你在中国大陆,需要手动把时区调成 UTC+8,否则任务会在下午六点半才跑。我第一版就是栽在这里,等了三天才发现时间不对。

任务超时时间 :默认是六十秒,但采集加生成加推送的完整链路,实测下来平均需要四十到五十秒,遇到网络波动可能超过六十秒。我建议把超时调到一百二十秒,留足余量。

失败重试策略 :WorkBuddy 支持配置重试次数和重试间隔。我设的是重试两次,间隔三十秒。这样即使某次采集源临时不可用,也有机会在第二次或第三次成功。

日志保留 :默认保留最近七天的执行日志。如果你需要回溯更久,可以在设置里调到三十天。日志里会记录每次执行的开始时间、结束时间、输出内容和错误信息,排查问题时非常有用。

注意:WorkBuddy 的定时任务在触发时,如果上一次任务还没执行完,默认会跳过本次触发。这个行为在日报场景下是合理的,避免重复推送。但如果你希望排队执行,需要在高级设置里手动开启。

3.2 信息采集脚本的编写要点

采集脚本是整个链路里最脏最累的部分,因为每个信息源的抓取方式都不一样。我把它拆成了三个模块: 源配置、抓取器、清洗器 。

源配置 用一个 JSON 文件管理,每个源包含名称、类型、地址、抓取频率、权重。比如:

{
  "sources": [
    {
      "name": "技术社区热帖",
      "type": "rss",
      "url": "https://example.com/feed",
      "weight": 1.0
    },
    {
      "name": "内部知识库更新",
      "type": "api",
      "url": "https://internal.example.com/api/updates",
      "weight": 1.5
    }
  ]
}

抓取器 根据类型分发。RSS 源用 feedparser 解析,API 源用 requests 请求,网页源用 playwright 做无头浏览器渲染。这里重点说 playwright,因为很多社区页面是动态加载的,直接 requests 拿不到内容。playwright 的好处是它能等页面完全渲染后再提取文本,稳定性比 selenium 好很多。

from playwright.sync_api import sync_playwright

def fetch_dynamic_page(url):
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        page.goto(url, wait_until="networkidle")
        content = page.inner_text("body")
        browser.close()
        return content

清洗器 负责去重、去广告、提取正文。去重我用的是 SimHash 算法,把每条内容的文本转成一个六十四位指纹,相似度超过阈值的视为重复。去广告用正则匹配常见广告关键词,比如“点击领取”“限时优惠”这类。提取正文用 readability-lxml,它能自动识别页面里的主体内容区域,把导航栏、侧边栏、评论区过滤掉。

3.3 deepseek-v4-flash 的调用与提示词设计

调用 deepseek-v4-flash 的接口本身不复杂,关键是提示词的设计。我前后改了五版提示词,最终稳定下来的版本包含四个部分: 角色定义、任务说明、输出格式、约束条件 。

角色定义让模型知道自己是一个“信息筛选助手”,任务说明告诉它要把原始信息归纳成几个固定板块,输出格式强制它返回 JSON,约束条件要求它不编造信息、不添加主观评价、每条摘要不超过八十字。

prompt = """
你是一个信息筛选助手。请把下面的原始信息归纳成日报,分为三个板块:
1. 行业动态:与行业趋势、产品更新相关的内容
2. 技术干货:与技术实践、工具使用相关的内容
3. 值得关注:其他有价值但不易归类的内容

输出格式为 JSON,结构如下:
{
  "sections": [
    {"title": "板块名", "items": [{"summary": "摘要", "source": "来源"}]}
  ]
}

约束:
- 每条摘要不超过 80 字
- 不编造原始信息中没有的内容
- 不添加主观评价
- 如果某个板块没有内容,items 为空数组

原始信息:
{raw_content}
"""

这里有个经验: 温度参数设成 0.3 。温度太高,模型会自由发挥,摘要里出现原文没有的表述;温度太低,模型会过于保守,把不同来源的相似内容重复输出。0.3 是我实测下来平衡点最好的值。

3.4 微信小程序订阅消息的接入流程

小程序订阅消息的接入分四步: 注册小程序、配置模板、获取订阅授权、发送消息 。

注册小程序需要主体资质,个人开发者可以注册,但部分类目有限制。日报推送属于“服务通知”类目,个人主体可以申请。

配置模板在小程序后台的“订阅消息”页面操作。你需要选一个合适的模板,或者自定义模板。模板里可以定义多个字段,比如“日报日期”“内容摘要”“详情链接”。字段类型支持文本、数字、日期等。

获取订阅授权需要在小程序前端调用 wx.requestSubscribeMessage ,让用户主动点击授权。这里有个关键限制: 一次授权只能发送一条消息 。也就是说,如果用户只授权了一次,你只能给他发一条日报,第二天就发不出去了。解决办法是引导用户勾选“总是保持以上选择”,这样后续可以持续发送。

发送消息在服务端调用微信的接口,需要用到 access_token。access_token 有有效期,需要缓存并定时刷新。我用的方案是存在 Redis 里,设置七千秒过期,每次发送前检查是否过期,过期就重新获取。

def send_subscribe_message(openid, template_id, data):
    access_token = get_access_token()
    url = f"https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={access_token}"
    payload = {
        "touser": openid,
        "template_id": template_id,
        "data": data
    }
    response = requests.post(url, json=payload)
    return response.json()

提示:小程序订阅消息的发送频率有限制,单个用户每天最多接收一定数量的服务通知。日报场景下每天一条,完全在限制范围内。

4. 实操过程与核心环节实现

4.1 从零搭建 WorkBuddy 任务的完整步骤

第一步,登录 WorkBuddy 工作台,进入“技能”页面,创建一个新的技能。技能类型选“脚本执行”,运行环境选 Python 3.10。为什么选 3.10?因为 playwright 和 feedparser 在这个版本下兼容性最好,3.11 和 3.12 偶尔会有依赖冲突。

第二步,在技能里上传或在线编辑脚本。脚本的入口函数需要按照 WorkBuddy 的规范来写,通常是定义一个 main 函数,接收一个 context 参数,返回执行结果。context 里包含触发时间、任务 ID、环境变量等信息。

def main(context):
    raw_content = collect_all_sources()
    daily_report = generate_report(raw_content)
    push_to_wechat(daily_report)
    return {"status": "success", "report_length": len(daily_report)}

第三步,配置定时触发器。在技能详情页找到“触发器”选项卡,添加一个 Cron 触发器,表达式填 30 10 * * * ,时区选 UTC+8。同时把超时时间改成一百二十秒,重试次数设为两次。

第四步,配置环境变量。把 deepseek 的 API Key、微信小程序的 AppID 和 AppSecret、企业微信机器人的 Webhook 地址都存到环境变量里,不要硬编码在脚本中。WorkBuddy 的环境变量是加密存储的,安全性有保障。

第五步,手动触发一次测试。在技能页面点击“立即执行”,观察日志输出。如果一切正常,你会看到采集条数、生成耗时、推送结果等信息。我第一次测试时采集到了四十三条原始信息,生成日报用了四点二秒,推送成功。

4.2 日报内容的格式化与推送实现

模型返回的 JSON 需要经过格式化才能推送。我写了一个 format_report 函数,把 JSON 转成适合微信阅读的纯文本。

def format_report(report_json):
    lines = []
    lines.append(f"AI 日报 | {report_json['date']}")
    lines.append("")
    for section in report_json["sections"]:
        lines.append(f"【{section['title']}】")
        for item in section["items"]:
            lines.append(f"- {item['summary']}(来源:{item['source']})")
        lines.append("")
    return "\n".join(lines)

格式化之后,先推送到企业微信机器人。企业微信机器人的消息体支持 Markdown 格式,但长度限制是四千零九十六字节。如果日报超过这个长度,需要截断或者分多条发送。我的做法是:如果超过三千五百字节,就只推送摘要版,完整版存到数据库,通过小程序详情页查看。

def push_to_wecom(content):
    webhook = os.environ["WECOM_WEBHOOK"]
    if len(content.encode("utf-8")) > 3500:
        content = content[:3000] + "\n\n... 完整内容请在小程序查看"
    payload = {
        "msgtype": "markdown",
        "markdown": {"content": content}
    }
    requests.post(webhook, json=payload)

小程序侧的推送需要先查询订阅用户列表,然后逐个发送。订阅用户列表存在数据库里,每次用户授权时插入一条记录。发送时遍历列表,调用 send_subscribe_message 。如果某个用户发送失败,记录失败原因,下次重试。

4.3 参数计算与性能优化记录

整个链路的耗时分布大致是:采集三十秒、生成五秒、推送三秒、其他开销五秒,总计约四十三秒。采集是大头,因为要等 playwright 渲染页面。我做了两个优化。

优化一:并发采集 。把独立的源放到线程池里并行抓取,采集时间从三十秒降到了十二秒。注意,playwright 的浏览器实例不要并发太多,我实测下来同时开三个页面比较稳,开五个以上容易内存溢出。

from concurrent.futures import ThreadPoolExecutor

def collect_all_sources():
    sources = load_sources()
    with ThreadPoolExecutor(max_workers=3) as executor:
        results = list(executor.map(fetch_source, sources))
    return merge_results(results)

优化二:缓存静态内容 。有些源的内容变化不频繁,比如内部知识库的文档列表,没必要每次都全量抓取。我加了一个简单的缓存层,用文件的修改时间做判断,如果距离上次抓取不到一小时,直接读缓存。

优化三:模型调用批处理 。如果原始信息超过模型单次处理的上下文限制,需要分批调用。我的做法是按来源分组,每组单独生成摘要,最后合并。这样虽然多调用几次模型,但每次的输入更聚焦,生成质量反而更好。

4.4 完整链路的联调与验证

联调阶段我重点验证了四个场景: 正常执行、源不可用、模型超时、推送失败 。

正常执行场景下,日志显示采集四十三条、生成四点二秒、推送成功,微信侧十点半准时收到消息。

源不可用场景下,我把其中一个源的地址改成无效地址,脚本没有崩溃,而是跳过了该源并在日志里记录了警告。最终日报里少了那个源的内容,但整体流程正常。

模型超时场景下,我把 deepseek 的接口地址改成一个不存在的地址,脚本在等待十秒后抛出超时异常。WorkBuddy 的重试机制在三十秒后重新触发,第二次因为缓存了采集结果,直接跳过采集进入生成,但模型依然不可用,最终任务标记为失败,并发送了告警通知到企业微信。

推送失败场景下,我故意把企业微信的 Webhook 地址改错,脚本捕获了异常并记录日志,但没有影响小程序侧的推送。这说明两个推送通道是解耦的,一个失败不影响另一个。

注意:联调阶段一定要用测试数据,不要直接跑真实数据。我一开始图省事直接跑真实源,结果测试过程中给订阅用户发了好几条重复日报,非常尴尬。

5. 常见问题与排查技巧实录

5.1 定时任务不触发或触发时间不对

这是最常见的问题,我遇到过三次。第一次是时区没设对,WorkBuddy 默认 UTC,我以为是本地时间,结果任务在下午六点半才跑。第二次是 Cron 表达式写错了,把 30 10 * * * 写成了 30 10 * * 1 ,变成了每周一才触发。第三次是任务被禁用了,我在调试时手动关掉了触发器,后来忘了重新开启。

排查顺序建议是:先看触发器状态是否启用,再看 Cron 表达式是否正确,最后检查时区设置。WorkBuddy 的触发器页面会显示“下次触发时间”,你可以对照这个时间来判断是否配置正确。

5.2 微信侧收不到推送消息

这个问题分两种情况:小程序订阅消息收不到,和企业微信机器人收不到。

小程序订阅消息收不到,首先检查用户是否真的完成了订阅授权。很多人以为点了“允许”就是订阅了,但实际上微信的订阅消息需要用户主动勾选“总是保持以上选择”才能持续接收。如果用户只授权了一次,你只能发一条。其次检查 access_token 是否有效,过期的 token 会导致接口返回四千零一错误。最后检查模板 ID 是否正确,模板 ID 填错会返回四千零三错误。

企业微信机器人收不到,首先检查 Webhook 地址是否完整,有没有多余的空格。其次检查消息体格式,企业微信对 Markdown 的语法支持有限,某些特殊字符会导致解析失败。最后检查机器人是否被移出群聊,或者群聊是否被解散。

5.3 模型生成内容质量不稳定

模型生成质量不稳定的表现有三种:摘要太长、摘要太短、摘要偏离原文。

摘要太长通常是提示词里没有限制字数,或者温度设得太高。解决办法是在提示词里明确写“每条摘要不超过八十字”,并把温度降到 0.3。

摘要太短通常是原始信息本身就很短,模型没有足够的内容可以归纳。这种情况可以在采集阶段做过滤,把字数少于五十字的原始信息直接丢弃。

摘要偏离原文通常是模型在“自由发挥”。解决办法是在提示词里加一条约束:“摘要必须基于原始信息,不得添加原文没有的内容”。如果还是不行,可以在生成后加一个校验步骤,用另一个模型调用判断摘要是否忠实于原文。

5.4 常见问题速查表

问题现象 可能原因 排查方法 解决方案
任务不触发 触发器禁用 查看触发器状态 重新启用触发器
触发时间不对 时区设置错误 检查时区配置 改为 UTC+8
采集为空 源地址失效 手动请求源地址 更新源配置
模型超时 接口不可用 检查 API Key 和网络 重试或更换接口
小程序收不到 未授权或 token 过期 检查授权记录和 token 引导重新授权,刷新 token
企业微信收不到 Webhook 错误 手动 POST 测试 更新 Webhook 地址
日报内容重复 去重阈值太低 检查 SimHash 阈值 提高相似度阈值
推送内容截断 超过长度限制 检查内容字节数 截断或分条发送

5.5 我踩过的三个坑和对应的避坑技巧

坑一:playwright 浏览器实例没有正确关闭 。我在早期版本里每次抓取都新建一个浏览器实例,但忘记关闭,跑了两天之后服务器内存被占满,任务开始随机失败。解决办法是用 with 语句管理浏览器生命周期,确保每次抓取后自动关闭。

坑二:deepseek 接口的并发限制 。我一开始为了提速,把模型调用也放到了线程池里并发执行,结果触发了接口的速率限制,返回四百二十九错误。后来改成串行调用,虽然慢了几秒,但稳定性大幅提升。如果你的场景确实需要并发,建议加一个令牌桶限流器,控制每秒请求数。

坑三:微信 access_token 被其他服务刷新 。如果你有多个服务共用一个小程序,它们可能会同时刷新 access_token,导致先刷新的那个 token 失效。解决办法是统一由一个服务负责刷新,其他服务从缓存读取。我用的方案是把 token 存在 Redis 里,所有服务都从 Redis 读,只有一个定时任务负责刷新。

提示:WorkBuddy 的日志功能非常有用,每次执行都会记录完整的标准输出和标准错误。排查问题时,先看日志里的错误堆栈,大部分问题都能直接定位。

6. 后续可以继续扩展的方向

这套链路跑通之后,我发现它的扩展性比想象中好。最直接的扩展是 增加信息源 ,比如把邮件列表、内部 Wiki 更新、竞品博客都接进来。每加一个源,只需要在源配置里加一条记录,写对应的抓取器就行。

第二个扩展方向是 个性化日报 。不同的人关注的内容不一样,可以在订阅时让用户选择感兴趣的板块,生成日报时根据用户偏好过滤内容。这个功能需要在小程序侧加一个偏好设置页面,后端根据偏好生成不同的日报版本。

第三个扩展方向是 日报归档与检索 。把每天的日报存到数据库里,提供一个搜索页面,支持按关键词、日期、来源检索。这样日报就不只是“当天看完就扔”的东西,而是变成了一个可积累的知识库。

我目前已经把前两个方向跑通了,第三个方向还在做。如果你也在用 WorkBuddy 做类似的事情,建议先从最简单的单源单通道跑通,再逐步加源加通道。一上来就搞大而全,很容易在联调阶段卡住。先把主链路跑顺,后面加东西就是复制粘贴的事。

更多推荐