拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Jev与LLM的三源新闻日报自动化系统实战

基于Jev与LLM的三源新闻日报自动化系统实战

1. 从"信息焦虑"到"日报自动化":这个项目到底在解决什么

每天早上睁眼第一件事就是刷各种信息流,科技新闻、行业动态、社区热帖翻一遍,半小时没了,真正有价值的内容可能就三五条。更麻烦的是,同一个事件在不同来源的报道角度、深度、时效性都不一样,靠人肉对比根本不现实。这个项目的出发点很朴素:让 Jev 替我完成"扫信息流"这件事,每天定时产出一份三源新闻日报,我只需要花五分钟读日报就行。

所谓"三源",指的是从三个不同渠道抓取新闻内容——可以是 RSS 订阅源、公开的新闻 API、或者社区热榜接口。三个来源各有侧重,有的偏快、有的偏深、有的偏社区讨论,组合起来信息覆盖面比单一来源强得多。而"日报"则是最终产物:一份经过 LLM 筛选、归类、摘要、去重后的结构化文档,按主题分块,每条附上来源和原文链接。

Jev 在这里扮演的角色是调度中枢 + 智能处理引擎。它负责定时触发抓取任务、调用 LLM 对原始内容做清洗和摘要、把结果按模板渲染成日报、最后推送到指定位置(邮件、飞书、本地 Markdown 文件都行)。整个链路的核心难点不在"抓",而在"筛"和"写"——怎么让 LLM 在有限的 token 预算内,把一堆杂乱信息压缩成一份人愿意读的日报。

这个项目适合几类人参考:一是每天需要跟踪特定领域动态的从业者,比如做投资、做技术选型、做竞品分析的;二是想入门 LLM 应用开发但不知道从什么项目练手的开发者,这个项目的链路完整但不复杂,涉及 API 调用、Prompt 设计、错误处理、定时任务,麻雀虽小五脏俱全;三是已经在用各种 RSS 阅读器但觉得"读不过来"的人,日报模式本质上是用 LLM 做了一次信息降噪。

我自己的使用场景是技术资讯跟踪。以前用 RSS 阅读器订阅了四十多个源,每天未读数轻松破千,后来干脆不看了。改成三源日报之后,每天固定收到一份两三千字的摘要,覆盖当天最重要的十几条技术动态,阅读压力骤降。下面把整个实现过程拆开讲,包括选型理由、踩过的坑、以及几个让日报质量明显提升的调优技巧。

2. 三源抓取层的设计:为什么不是源越多越好

2.1 三个来源的定位差异与互补逻辑

很多人第一反应是"既然要抓,那就多抓几个源,信息更全"。我一开始也是这么想的,接了八个源,结果日报里大量重复内容,LLM 的 token 消耗翻了三倍,摘要质量反而下降——因为模型要在冗余信息里做去重,注意力被分散了。后来砍到三个源,效果反而最好。

三个源的选取原则是定位互补,而不是数量堆砌。我最终确定的组合是:

来源类型定位更新频率内容特征
综合新闻 API覆盖广度每小时标题规范、正文完整、有分类标签
技术社区热榜社区讨论热度每 6 小时标题口语化、有评论数、反映真实关注度
垂直领域 RSS深度与专业性每天长文为主、有作者观点、时效稍慢

这三个源覆盖了"发生了什么""大家在讨论什么""专业人士怎么看"三个层面。综合新闻 API 保证不漏大事件,社区热榜反映真实热度(避免被公关稿带偏),垂直 RSS 提供深度分析。三者交叉验证,日报的信息质量比单一来源高一个档次。

提示:源的数量控制在 3 到 5 个比较合理。超过 5 个之后,去重和排序的逻辑复杂度会急剧上升,而信息增益递减明显。

2.2 抓取频率与去重策略的配合

抓取频率不是越高越好。我最初设的是每 15 分钟抓一次,结果同一个事件在一天内被反复抓取,日报里出现大量"旧闻"。后来改成按源设定频率:新闻 API 每小时一次,社区热榜每 6 小时一次,RSS 每天一次。这样既保证时效,又避免重复。

去重分两层做。第一层是URL 去重,用 URL 的规范化形式(去掉 utm 参数、统一协议头)做哈希,存到本地 SQLite 里,抓过的直接跳过。第二层是内容相似度去重,用标题的 SimHash 做近似匹配,阈值设在 0.85 左右——这个值是我试出来的,太低会误杀不同事件的相似标题,太高则漏掉改头换面的重复报道。

import hashlib from simhash import Simhash def url_fingerprint(url): # 去掉常见追踪参数 parsed = urlparse(url) clean = f"{parsed.scheme}://{parsed.netloc}{parsed.path}" return hashlib.md5(clean.encode()).hexdigest() def title_similar(a, b, threshold=0.85): ha, hb = Simhash(a), Simhash(b) distance = ha.distance(hb) # Simhash 是 64 位,距离越小越相似 return (1 - distance / 64) >= threshold

这里有个细节:SimHash 对中文标题的效果不如英文,因为中文分词后特征词更少。我的做法是先用 jieba 分词,去掉停用词,再把关键词拼成字符串做 SimHash。实测下来中文标题的误判率从 15% 降到了 5% 左右。

2.3 抓取失败与限流的处理经验

公开 API 和 RSS 都有速率限制,硬抓会被封。我的处理策略是指数退避 + 本地缓存。第一次失败等 2 秒重试,第二次等 4 秒,第三次等 8 秒,最多重试三次。如果三次都失败,就把这个源标记为"临时不可用",跳过本轮,下一轮再试。

本地缓存的作用是兜底。如果某个源连续失败,日报里对应板块就用上一次成功抓取的内容填充,并标注"数据可能滞后"。这样至少保证日报不会开天窗。

注意:抓取时一定要设置合理的 User-Agent 和请求间隔。我见过有人用默认的 python-requests UA 高频抓取,结果 IP 被临时封禁,排查了半天才发现是 UA 的问题。

3. LLM 处理链路:token 预算、Prompt 设计与摘要质量

3.1 为什么 token 预算是这个项目的第一约束

三源抓取下来,原始内容轻松超过十万字。如果直接丢给 LLM 做摘要,先不说成本,光是上下文长度就可能超限——很多模型的单次请求上限是 128K token,十万字中文大概对应 15 万 token 左右,直接爆掉。我踩过的第一个坑就是没算 token,把整天的抓取结果一次性塞进去,结果 API 直接返回 400 错误,提示超出最大上下文长度。

所以整个 LLM 处理链路必须分层做减法。我的方案是三级压缩:

  1. 规则预筛:按关键词、来源权重、发布时间做第一轮过滤,砍掉明显不相关的内容,通常能去掉 60% 到 70%。
  2. 单条摘要:对剩下的每条内容单独调用 LLM 做一句话摘要,输出控制在 50 字以内。
  3. 聚合生成:把所有单条摘要按主题聚类,再调用一次 LLM 生成最终的日报正文。

这样每一级的输入都在可控范围内,最终聚合时的输入通常只有几千 token,稳定不超限。

3.2 单条摘要的 Prompt 怎么写才不废话

单条摘要的 Prompt 我改了七八版,最终稳定下来的版本是这样的:

你是一名新闻编辑。请用一句话概括以下内容的核心事实,不超过50字。 要求: 1. 只陈述事实,不加评价 2. 保留关键数字和专有名词 3. 如果内容没有实质信息,输出"无价值内容" 内容:{content}

关键在最后那条"无价值内容"的兜底。没有这条的时候,模型会对每条内容都硬憋出一句摘要,包括那些纯广告、纯水文,导致日报里混入垃圾。加上这条之后,模型有了"拒绝"的选项,日报的纯净度明显提升。

另一个技巧是在 Prompt 里给示例。我放了两条正例和一条反例,模型对"什么算核心事实"的理解会准确很多。示例不用多,两三条就够,多了反而占 token。

3.3 聚合阶段的主题聚类与排序逻辑

聚合阶段的核心是先聚类,再排序,最后生成。聚类我用的是简单的关键词匹配加 LLM 辅助判断——先按预定义的主题标签(如"AI""开源""硬件""政策")做粗分,再让 LLM 判断边界模糊的条目该归到哪一类。

排序逻辑是三个维度的加权:来源权重 × 0.4 + 时效性 × 0.3 + 社区热度 × 0.3。来源权重是我手动设的,垂直 RSS 给 1.0,新闻 API 给 0.8,社区热榜给 0.6。时效性按发布时间做指数衰减,24 小时前的权重降到 0.3。社区热度用评论数或点赞数归一化。

这个加权公式不是拍脑袋定的,是我用两周的日报做了 A/B 对比调出来的。最初只用时效性排序,结果日报里全是刚发的短讯,深度内容被淹没;加上来源权重和热度之后,日报的信息密度明显提升。

4. 日报渲染与推送:让输出真正可读

4.1 日报模板的结构设计

日报最终是一份 Markdown 文档,结构固定为四块:今日要闻(3 到 5 条)、分类速览(按主题分组的摘要列表)、深度推荐(1 到 2 篇长文)、数据统计(抓取条数、去重后条数、各源占比)。

这个结构是迭代出来的。最初的版本只有"分类速览",读起来像流水账,没有重点。加上"今日要闻"之后,读者可以先看最重要的几条,有兴趣再往下翻。"深度推荐"是后来加的,因为发现有些长文被摘要压缩后失去了价值,单独拎出来推荐原文更好。

模板用 Jinja2 渲染,好处是逻辑和展示分离,改版式不用动代码。下面是一个简化版的模板片段:

## 今日要闻 {% for item in highlights %} - **{{ item.title }}** — {{ item.summary }}(来源:{{ item.source }}) {% endfor %} ## 分类速览 {% for category, items in grouped.items() %} ### {{ category }} {% for item in items %} - {{ item.summary }} [原文]({{ item.url }}) {% endfor %} {% endfor %}

4.2 推送渠道的选择与踩坑

推送渠道我试过三种:邮件、飞书机器人、本地文件。邮件最通用但排版容易乱,飞书机器人实时性好但消息长度有限制,本地文件最灵活但需要自己同步。

最终我选的是本地 Markdown 文件 + 飞书机器人摘要的组合。完整日报写成本地文件,方便归档和搜索;飞书只推"今日要闻"部分,控制在 500 字以内,避免消息被截断。

飞书机器人的坑主要在消息格式上。它支持富文本卡片,但字段结构比较绕,我第一次调的时候消息发出去是空的,排查发现是 card 的 schema 写错了。建议先用官方提供的调试工具把卡片结构调通,再接入代码。

提示:推送失败一定要有重试和告警。我有一次飞书 token 过期,日报连续三天没推出去,直到手动检查才发现。后来加了个简单的告警:推送失败时写一条日志到本地文件,并在下一次成功推送时附带"上次推送失败"的提示。

4.3 日报质量的自我评估机制

日报发出去之后,怎么知道质量好不好?我加了一个简单的反馈闭环:每天日报末尾附一个"有用/没用"的投票链接,读者点完之后数据回写到本地。连续一周"没用"占比超过 30%,就触发一次 Prompt 调优。

另外还有一个自动指标:摘要压缩比。原始内容总字数除以日报总字数,正常在 50:1 到 100:1 之间。如果低于 30:1,说明摘要不够精炼;如果高于 150:1,说明可能漏掉了重要信息。这个指标帮我发现过好几次 Prompt 退化的问题。

5. 部署与运维:让日报每天准时出现

5.1 定时任务的选型与容错

定时任务我用的是系统的 cron,简单可靠。但 cron 有个问题:如果上一次任务还没跑完,下一次又触发了,会出现并发冲突。我的处理是加文件锁——任务开始时创建一个 lock 文件,结束时删除,如果 lock 文件存在就跳过本次执行。

#!/bin/bash LOCK=/tmp/news_daily.lock if [ -f "$LOCK" ]; then echo "上一次任务还在运行,跳过" exit 0 fi touch "$LOCK" trap "rm -f $LOCK" EXIT python /path/to/daily.py

trap那行很关键,保证即使脚本异常退出,lock 文件也会被清理,不会导致任务永久卡死。这个坑我踩过,有一次脚本因为 API 超时崩了,lock 文件没删,结果后面几天的任务全部跳过,日报断更了三天才发现。

5.2 API Key 管理与错误处理

项目里涉及多个 API Key:新闻 API、LLM API、推送渠道的 token。这些绝对不能硬编码在代码里。我的做法是用.env文件加python-dotenv加载,.env加入.gitignore,代码里只引用环境变量名。

错误处理要区分可重试和不可重试两类。网络超时、限流属于可重试,指数退避后重试;API Key 无效、请求格式错误属于不可重试,直接记录日志并跳过。我见过有人把所有错误都无脑重试,结果 Key 失效时疯狂重试,把配额耗光了。

def call_llm(prompt, max_retries=3): for i in range(max_retries): try: resp = client.chat(prompt) return resp except RateLimitError: time.sleep(2 ** i) except AuthenticationError: logger.error("API Key 无效,跳过本次调用") return None except Exception as e: logger.warning(f"未知错误:{e}") time.sleep(2 ** i) return None

5.3 日志与监控:出问题时怎么快速定位

日志我分了三层:INFO记录每步的正常执行(抓取了几条、摘要了几条、推送成功),WARNING记录可恢复的异常(某个源失败、重试成功),ERROR记录需要人工介入的问题(Key 失效、连续失败)。

日志按天切分,保留 30 天。排查问题时,先看 ERROR,再看 WARNING,基本能定位到是哪一环出的问题。我遇到过一次日报内容为空的情况,查日志发现是抓取层全部返回 0 条,再往上查是新闻 API 改了返回格式,字段名从articles变成了data。如果没有详细日志,这种问题很难快速定位。

6. 几个让日报质量明显提升的调优技巧

6.1 用"负面示例"约束 LLM 的输出风格

LLM 默认的摘要风格偏"新闻联播",喜欢用"据悉""标志着""进一步"这类词。我在 Prompt 里加了一段负面示例:

不要使用以下表达: - "据悉"、"据了解" - "标志着"、"意味着" - "进一步"、"持续" - 任何没有信息量的形容词

加上之后,摘要的废话率明显下降。这个技巧对任何 LLM 摘要任务都适用——与其告诉模型"要简洁",不如直接列出"不要用什么词",后者更可操作。

6.2 按主题动态调整摘要长度

不同类型的新闻,合适的摘要长度不一样。政策类新闻需要保留更多细节,社区讨论类只需要一句话概括。我的做法是在 Prompt 里根据主题标签动态调整字数上限:政策类 80 字,技术类 60 字,社区类 40 字。

这个调整让日报的可读性提升了不少。之前统一 50 字的时候,政策类新闻经常被压缩得看不懂,社区类又显得啰嗦。

6.3 定期回顾与 Prompt 版本管理

Prompt 是要迭代的,但改了什么、为什么改,必须记下来。我用一个简单的 Markdown 文件做 Prompt 版本管理,每次修改记录日期、改动内容、改动原因、效果对比。这样当日报质量下降时,可以快速回滚到上一个稳定版本。

我自己的 Prompt 从 v1 到 v7,中间有三次是回滚后重新改的。没有版本管理的话,根本记不清哪一版效果最好。

6.4 人工抽检与自动化指标结合

完全依赖自动指标不够,我每周会人工抽检两天的日报,看摘要是否准确、分类是否合理、有没有漏掉重要新闻。人工抽检发现的问题,往往是自动指标覆盖不到的——比如某条摘要事实性错误,压缩比指标完全正常,但内容就是错的。

抽检的另一个作用是发现新的信息源。有时候日报里某个主题的内容明显偏少,说明对应的源覆盖不够,需要补充新的源。这个判断只能靠人,自动化做不了。

7. 这套方案还能怎么扩展

三源日报跑顺之后,我陆续加了一些扩展。一个是个性化权重,根据我点击"有用"的历史记录,动态调整各主题的排序权重,让日报越来越贴合我的关注点。另一个是周报聚合,把一周的日报再做一次 LLM 聚合,生成一份周度回顾,适合周末花十分钟快速补课。

还有一个方向是多模态。现在日报只处理文本,但很多新闻的价值在图表和视频里。下一步想试试把关键图表用 OCR 提取数据,让 LLM 结合数据做摘要。这个还在试验阶段,效果好的话再单独写一篇。

如果你也在做类似的信息聚合项目,我的建议是先把单源跑通,再加源。我见过太多人一上来就接十个源,结果去重和排序逻辑没做好,日报里全是重复内容,最后项目烂尾。三源是个比较舒服的起点,跑顺了再按需扩展,比一开始就贪多要靠谱得多。

返回列表