1. 我为什么决定把刷信息流这件事交给智能体
每天早上睁开眼,我的标准动作是:先扫一遍朋友圈,再刷几个资讯 App,然后点开收藏夹里昨晚存的一堆链接。屯了大概一周之后我发现一个很尴尬的事实——我每天平均收藏几十篇东西,真正从头到尾读完的可能不超过两篇。信息流刷得越勤,焦虑感越重,而且大量时间都浪费在"标题党筛选"这个毫无技术含量的环节上。
后来我接触到了 Jev,一个可以本地部署的编程智能体模型,能把自然语言描述拆解成多步执行任务,并且自己写代码去完成。斯坦福那边已经有人拿它构建数据采集系统,社区里也陆续出现了在 Codex 里配合使用的教程,GitHub 上有它的聊天助手仓库。我就在想:既然 Jev 能听懂任务、能写 Python、能自己调试脚本,那我为什么不干脆把"刷信息流"这件事外包给它?
这个想法最终落地成了一个小项目:三源新闻日报。简单来说,Jev 每天早上从三个不同维度的信息源里抓取内容,自己做去重、打分、筛选,再按照固定模板生成一份日报,推送到我的邮箱和微信。我只需要花三分钟把日报读完,挑两篇真正有价值的细看,就够了。
这篇博文我会把整个实现过程完整拆开:顶层设计、Jev 本地部署、采集脚本、汇总排版、定时调度和推送,以及运行两周之后踩过的坑。如果你手里已经有一个能本地跑的智能体模型,不管是 Jev 还是同类 Agent,这套思路都可以直接照搬。
1.1 信息过载的真实痛点
先聊一个更本质的问题:人为什么需要"刷"信息流?因为新闻的价值密度太低了。我做过一个粗略统计,一条信息流里大概只有 5%~10% 的内容跟我当前的工作直接相关,真正值得点开细看的可能不到 2%。但为了找出这 2%,我要把剩下 98% 全部过一遍眼睛。
这其实是典型的"人工过滤成本远高于信息获取成本"的场景。普通爬虫能解决"抓取",但解决不了"判断什么值得看";RSS 阅读器能解决"聚合",但依然需要我逐条浏览标题。真正缺的是一个具备判断力的过滤层,而判断力恰恰是智能体模型比脚本强的地方。
1.2 Jev 是什么,我拿它做了什么
Jev 这类智能体模型,和普通的对话助手有个本质区别:它不只是"回答你",而是"替你执行"。你把一个目标丢给它,它会自己规划步骤,自己写代码,自己跑,跑挂了还会读报错信息改代码重试。在 Codex 里用 Jev 做编码任务,在本地部署后用命令行和它交互,体验上和"雇了一个不用睡觉的初级工程师"很像。
我的做法是:把"生成三源新闻日报"这个完整任务交出去,但每一个环节——数据源选型、抓取脚本、去重规则、输出模板——都由我先定义清楚,再让 Jev 去落地。项目本身的形态是一个 Python 工程,包含采集模块、处理模块、调度脚本三部分,全程在 Windows 下完成。Jev 负责写代码和改代码,我负责定规则和验收结果。
2. 三源日报的顶层设计:三个信息源与一种固定格式
动手之前,我先想清楚一个问题:日报给谁看?答案是我自己,一个做软件开发的从业者。我关注三类东西:行业社区在聊什么、开源生态里冒出了什么新项目、中文技术圈里有什么值得读的文章。三类信息对应三个不同的信源,这就是"三源"的来历。
2.1 数据源选型的三个标准
选信息源的时候,我给自己定了三个硬性标准:
- 必须有稳定、合法的公开接口,不能靠破解或者抓取需要登录的页面。
- 发布时间要实时,最好当天就能拿到当天的新内容。
- 内容质量和领域互补性要强,三个源不要高度重叠。
按这三个标准,我最终选定了三个源:
| 数据源 | 获取方式 | 提供的信息维度 |
|---|---|---|
| Hacker News | 官方 Firebase API | 全球开发者社区当前最关注的话题 |
| GitHub | Search API(按时间+星标排序) | 近期冒头的高质量开源项目 |
| 中文技术博客 RSS | feedparser 解析 | 中文技术圈的长文和深度文章 |
选 Hacker News 是因为它代表了"全球开发者此刻在聊什么";选 GitHub 是因为它能直接反映"代码世界正在发生什么",不是观点而是事实;选中文 RSS 是因为我每天实际要用的还是中文内容,而且 RSS 源可以随时替换,今天挂少数派,明天换 InfoQ,甚至加一个阮一峰的网络日志,成本都很低。
2.2 日报模板与"主编"视角
有了原材料还不够,还要确定日报长什么样。我的日报格式非常固定,固定到 Jev 几乎不用思考就能套模板:
- 今日头条,全日报最重要的一条,给 200 字以内的点评。
- 三个信息源各自的重点摘要,每条包含标题、原始链接、一句话说明它为什么值得看。
- 综合热度分,用分数告诉读者"这条到底有多重要"。
我特意把格式定死,原因很简单:智能体模型在开放式任务里容易发挥不稳定,今天输出 Markdown,明天输出 JSON,后天给你来一段散文。越固定的输出模板,越容易让后续流程稳定运行。这就像给一个实习编辑定好了版面样式,他的工作就是把内容填进去,而不是自由创作。
3. Jev 的本地部署:Windows 上跑通智能体的关键细节
部署环境这块,网上资料说得多的是在 macOS 或者 Linux 上跑。我身边这台主力机是 Windows,所以整个过程是在 Windows 下完成的。说实话,Windows 部署没有想象中那么曲折,但还是有几个地方值得单独拿出来讲。
3.1 环境准备与部署流程
我的部署步骤大概是这样的:
- 确认 Python 版本。Jev 对 Python 版本有要求,我这边用的 3.11,安装依赖的时候基本没碰到兼容性问题。
- 拉取 Jev 的模型文件和运行框架。社区里有现成的项目模板,GitHub 仓库里会写清楚目录结构。
- 安装依赖,跑一次自带的 smoke test,确认模型能正常加载、能对话。
- 配置本地的模型访问密钥,让 Jev 可以以代码执行模式运行。
这里有一个很多人忽略的点:部署 Jev 不是只部署模型,还要部署它的"工具链"。因为 Jev 要自己写代码、跑代码,它还依赖本地的 Python 解释器、pip、以及网络访问能力。如果环境里缺了这些,Jev 会表现得"脑子很好但手脚不便"。
3.2 部署中最容易翻车的三个细节
第一是路径问题。Windows 的路径分隔符是反斜杠,而 Jev 生成的代码默认习惯用正斜杠。我第一版脚本就是在拼接文件路径的时候踩了这个坑,解决方案是让 Jev 统一用Path对象处理路径,而不是字符串拼接。
第二是默认编码。Windows 下默认编码是 GBK,而 Python 3 在读取 UTF-8 文件时如果没有显式声明编码,很容易在中文内容上爆UnicodeDecodeError。后面抓取 RSS 的时候这个问题反复出现,解决方案是写一个统一的请求头,明确指定Accept-Encoding和字符集。
第三是模型加载方式。Jev 支持交互式对话和批量执行两种模式,批量执行模式更适合无人值守的定时任务。我刚开始一直用交互模式跑,每次都要手动发指令,后来改成批量执行模式之后,配合调度工具才能真正实现"全自动"。
3.3 和 Codex 配合使用的一个补充
如果你平时主力开发环境是 Codex,也可以把 Jev 接进去,让它在 Codex 里作为编码 Agent 工作。我实测下来,这个组合更适合做"探索性开发"——比如让 Jev 在 Codex 的沙箱里先跑通一个抓取脚本,再把验证过的脚本搬到本地正式环境。好处是模型跑挂了不会弄脏本机环境,坏处是沙箱和本地环境往往有细微差异,部署到本地时还得再调一遍。
4. 让 Jev 写代码:第一版三源采集脚本的完整实现
部署完 Jev 之后,我的第一个指令是:写一个 Python 脚本,分别从 Hacker News、GitHub、指定 RSS 源抓取当天的内容,输出成统一的 JSON 格式。
4.1 Hacker News 与 GitHub 的抓取实现
Hacker News 的官方 API 很干净,不需要 API Key。Jev 第一版就写出了类似这样的代码:
import requests def fetch_hn_top(n=30): top_ids = requests.get( "https://hacker-news.firebaseio.com/v0/topstories.json", timeout=10 ).json()[:n] items = [] for item_id in top_ids: item = requests.get( f"https://hacker-news.firebaseio.com/v0/item/{item_id}.json", timeout=10 ).json() items.append({ "title": item.get("title", ""), "url": item.get("url", f"https://news.ycombinator.com/item?id={item_id}"), "score": item.get("score", 0), "comments": item.get("descendants", 0), }) return itemsHacker News 这个 API 的延迟有点高,逐条拉取 30 条新闻要等好几秒。后来我让 Jev 加了一个简单的并发池,用concurrent.futures.ThreadPoolExecutor并发请求,30 条的耗时从 8 秒降到了 2 秒左右。
GitHub 那边是个经典陷阱。GitHub 并没有官方"趋势"接口,网上很多教程是去爬网页版/trending页面,但那个页面没有稳定的 JSON 接口,结构说改就改,几天就崩一次。我让 Jev 换成了 Search API,按"最近 7 天创建、星标超过 50"的条件去搜仓库,按 star 数倒序排列:
import requests from datetime import datetime, timedelta def fetch_github_trending(days=7): since = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d") params = { "q": f"created:>{since} stars:>50", "sort": "stars", "order": "desc", "per_page": 20, } headers = {"Accept": "application/vnd.github+json"} resp = requests.get( "https://api.github.com/search/repositories", params=params, headers=headers, timeout=10 ).json() return [ { "name": repo["full_name"], "url": repo["html_url"], "stars": repo["stargazers_count"], "desc": repo.get("description", ""), } for repo in resp.get("items", []) ]这里有个必须提醒的坑:GitHub Search API 的速率限制是每分钟 10 次,未认证的匿名请求更少。日报脚本一天只跑一次,理论上不会碰到,但如果你像我一样在调试阶段反复触发,就会被 403 卡住。给请求头加上自己的 GitHub Token 可以把这个额度提上去,本地配置成一个环境变量就行。
4.2 RSS 源抓取与编码坑
RSS 这块,Jev 直接用feedparser库,几行就能跑通:
import feedparser def fetch_rss(feed_url, limit=10): feed = feedparser.parse(feed_url) return [ { "title": entry.get("title", ""), "link": entry.get("link", ""), "published": entry.get("published", ""), } for entry in feed.entries[:limit] ]真正的问题出在编码上。中文 RSS 源五花八门,有的输出 UTF-8,有的输出 GB2312,还有的干脆不声明编码。feedparser在遇到声明和实际编码不一致时,会出现中文乱码。解决方案是在解析前先把内容抓下来,用requests拿到字节流,根据响应头里的charset或者用chardet探测,再手动转成 UTF-8 喂给feedparser。这个坑在 Windows 下尤其明显,因为终端默认编码不是 UTF-8,打印出来全是乱码,排查了快一个小时才定位到根因。
4.3 迭代开发:让 Jev 自己修 bug
我特别想说一下这个过程里最有意思的部分:脚本跑挂的时候,我不需要自己逐行看报错。直接把报错信息原样丢给 Jev,说"这里崩了,看一下怎么修",它会把整个调用栈读一遍,定位到具体的函数,然后给出修复代码甚至直接改好文件。
实测下来,Jev 对标准库和成熟的第三方库(requests、feedparser、concurrent.futures)非常熟悉,但对一些冷门的、文档不全的库会表现出"幻觉"——比如自己编造一个根本不存在的参数。所以我的原则是:让它尽量用主流方案,一旦发现它用了奇怪的库,立刻要求它改成标准库或者最常用的那个库。
5. 汇总、去重与打分排版:从原料到日报
三个源的采集脚本跑通之后,下一个问题是:怎么把这些原材料变成一份能直接读的日报?这一步如果做不好,日报就会变成一堆杂乱链接的堆砌,和原始信息流没有任何区别。
5.1 去重与打分的具体逻辑
三个信息源之间有重合。举一个很常见的例子:某个开源项目今天在 GitHub 上火了,第二天 Hacker News 上就会有人发帖讨论它。如果不去重,读者会在一份日报里看到两条几乎一样的内容。
去重的方案是这样的:先把标题统一做归一化处理,去掉标点、把英文转小写,然后用difflib.SequenceMatcher计算两两相似度,相似度超过 0.8 就认为是同一条新闻,保留热度更高的那一条。这个阈值我调了几轮,0.8 以下容易漏掉真正重复的内容,0.9 以上又会把"同主题不同写法的两篇文章"误判成重复,0.8 到 0.85 之间表现最稳。
打分公式方面,我给每条新闻算一个综合分:
综合分 = 源权重 * 0.4 + 热度指标 * 0.4 + 时效性 * 0.2三个源各自有不同的源权重,Hacker News 和 GitHub 主要看分数和 star 数,RSS 主要看发布时间。时效性指标用当前时间减去发布时间,按小时衰减。这个公式不用多科学,关键是稳定可解释——我知道为什么 A 排在 B 前面,而不是看着一堆黑盒分数干瞪眼。
5.2 主编提示词与输出模板
去重和打分搞定之后,最后一步是让 Jev 扮演"主编",把排序后的素材生成日报正文。我给 Jev 的提示词大概长这样:
你是一份技术日报的主编。下面是今天从三个信息源采集的新闻列表,每条包含标题、链接、来源、热度分。请按以下要求生成日报:
- 选出一条今日头条,并给出 200 字以内的点评,说明它为什么重要。
- 其余新闻按来源分组,每组列出标题和链接,每条用一句话说明推荐理由。
- 输出固定 Markdown 格式,标题层级不得改变,不输出与日报无关的内容。
这里有个关键技巧:不要让 Jev 自由发挥标题。我要求它使用固定的## 今日头条、### Hacker News、### GitHub趋势、### 中文RSS这些小节名,这样后续即使我要做二次处理,也可以用正则直接定位内容。哪怕模型偶尔抽风改了格式,我的校验程序也能立刻发现并报错。
5.3 实测输出样例
最终一份日报的核心部分大概长这样:
## 今日头条 **某开源项目发布 2.0 版本,彻底移除旧 API** 点评:这个项目在过去一年星标增长超过 3 万,2.0 版本的动作影响面很广, 值得关注其迁移文档和生态变化。 ## GitHub趋势 - [某项目/仓库名](链接) - 7 天涨星 2000+,解决的是云原生场景下的配置管理问题。整个生成过程从采集到出稿,在两分钟内完成。我一开始以为模型跑这种长任务会超时,实际表现比预期稳,只要环境里网络正常、依赖齐全,基本不需要人工介入。
6. 定时调度与消息推送:日报如何每天准时出现
脚本写好了,日报格式也定了,最后一步是把整个流程串成定时任务。如果这一步不做,这个项目就只是一个"手动触发的玩具"。
6.1 调度方案:cron 与 Windows 任务计划
我的主力机是 Windows,所以首选是 Windows 任务计划程序。新建任务,触发器设为每天早上 8 点,操作指向:
cd C:\projects\news-daily && python run_daily.py >> logs\daily.log 2>&1如果你在 macOS 或者 Linux 上跑,用 cron 更简单,一行配置搞定:
0 8 * * * cd /home/you/news-daily && /usr/bin/python3 run_daily.py >> logs/daily.log 2>&1两个方案都踩过同一个坑:任务计划里运行 Python 时,工作目录常常不对。脚本内用相对路径读配置文件、写日志,结果就是"找不到文件"疯狂报错。解决方案有两个,要么在脚本入口处先用os.chdir(项目路径)切到固定目录,要么直接把所有读写操作都改成绝对路径。我选了前一种,码量最少。
6.2 推送渠道与失败恢复
日报生成完只是一个 Markdown 文件,得有个渠道送到我手上。我先做了邮件,用标准库smtplib发给自己。后来又接了一个推送服务,把日报摘要直接推到微信上,用的是 Server酱的 SendKey,一个requests.post就能搞定:
requests.post(f"https://sctapi.ftqq.com/{SEND_KEY}.send", data={ "title": "今日技术日报", "desp": markdown_content, })推送这个环节我有一个执念:宁可推送失败,也不要推送半成品。所以我的run_daily.py是整个流程的"总导演",它会检查每一步的结果:采集到的新闻条数如果少于预期阈值,就判定本次采集失败,终止后续步骤并推送一条告警;生成日报后还会校验 Markdown 的标题结构,不符合模板就重试一次,重试仍失败则放弃推送,避免给用户发一份排版乱掉的日报。
6.3 运行两周后的问题清单
项目已经稳定跑了两个多星期,把期间实际遇到、并且已经解决的问题和排查思路整理成一张表,方便你做的时候少走弯路:
| 问题 | 现象 | 根因 | 解决方案 |
|---|---|---|---|
| daily.log 为空 | 任务显示已运行,但没有任何输出 | 任务计划程序工作目录错误 | 脚本入口os.chdir固定项目目录 |
| 日报内容全是英文 | 中文 RSS 标题乱码或缺失 | 部分源返回 GBK 编码 | 抓取时按 charset 探测转码 |
| GitHub 报 403 | 调试阶段频繁触发限流 | 未带 Token 调用 Search API | 配置环境变量GITHUB_TOKEN |
| 日报重复内容 | 同一项目出现在两个源 | 标题相似度算法阈值过低 | 相似度阈值从 0.7 调到 0.82 |
| 推送未收到 | 周末日报被判定为"抓取数量不足" | 周末 Hacker News 活跃度低 | 降低周末的阈值,改为按星期几动态判断 |
最后再分享一个细节:我在调度任务里加了 15 分钟的重试机制。如果早上 8 点那次运行因为网络抖动失败了,任务计划会在 8 点 15 分自动补跑一次,用两个触发器错开时间实现。这个小设计看起来不起眼,但实际省了我不少事——有两次 RSS 源超时导致采集失败,全靠重试兜底,我完全没有感知。整条链路跑顺之后,我每天早上打开手机,日报已经安安静静躺在那里,而我不需要再和上千条原始信息流搏斗了。信息还是那些信息,只是过滤它的变成了 Jev。