
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全球开发者社区当前最关注的话题GitHubSearch API按时间星标排序近期冒头的高质量开源项目中文技术博客 RSSfeedparser 解析中文技术圈的长文和深度文章选 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(n30): top_ids requests.get( https://hacker-news.firebaseio.com/v0/topstories.json, timeout10 ).json()[:n] items [] for item_id in top_ids: item requests.get( fhttps://hacker-news.firebaseio.com/v0/item/{item_id}.json, timeout10 ).json() items.append({ title: item.get(title, ), url: item.get(url, fhttps://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(days7): since (datetime.now() - timedelta(daysdays)).strftime(%Y-%m-%d) params { q: fcreated:{since} stars:50, sort: stars, order: desc, per_page: 20, } headers {Accept: application/vnd.githubjson} resp requests.get( https://api.github.com/search/repositories, paramsparams, headersheaders, timeout10 ).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, limit10): 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 21如果你在 macOS 或者 Linux 上跑用 cron 更简单一行配置搞定0 8 * * * cd /home/you/news-daily /usr/bin/python3 run_daily.py logs/daily.log 21两个方案都踩过同一个坑任务计划里运行 Python 时工作目录常常不对。脚本内用相对路径读配置文件、写日志结果就是找不到文件疯狂报错。解决方案有两个要么在脚本入口处先用os.chdir(项目路径)切到固定目录要么直接把所有读写操作都改成绝对路径。我选了前一种码量最少。6.2 推送渠道与失败恢复日报生成完只是一个 Markdown 文件得有个渠道送到我手上。我先做了邮件用标准库smtplib发给自己。后来又接了一个推送服务把日报摘要直接推到微信上用的是 Server酱的 SendKey一个requests.post就能搞定requests.post(fhttps://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。