使用MiniCPM5-2B搭建本地新闻简报系统:从RSS抓取到AI摘要全流程实践

发布时间:2026/9/24 22:14:17
使用MiniCPM5-2B搭建本地新闻简报系统:从RSS抓取到AI摘要全流程实践 每天早上被十个新闻 App 的红点轰炸却依然不知道自己错过了什么想做个信息聚合工具又担心API 费用一路走高还要把内容送去第三方服务器。这是我最初想搞本地新闻简报系统的原因。所谓的本地新闻简报系统就是把新闻源抓下来交给一台本机跑的模型去提炼摘要最终输出一份可以直接读的简报。我选的是 MiniCPM5-2B核心理由就一个参数只有 2B资源占用可控但日常摘要任务完全够用关键是所有数据都不离开这台电脑。这篇文章会从选型思路、环境部署、脚本实现到问题排查完整过一遍适合想折腾本地 AI 应用但不想把机器搞成炼丹炉的朋友。我的目标是让你照着这套流程周末花两个晚上就能跑起来属于自己的本地新闻简报。不需要 GPU 集群不需要云端 API Key一台 8GB 内存的笔记本就能扛。中间你会在各种我踩过的坑之间穿梭比如模型一本正经地乱编新闻、长期不写 cron 导致任务悄悄消失、输出格式不好好按模板来这些我都会展开讲。1. 项目整体设计与思路拆解1.1 为什么是 MiniCPM5-2B从选型逻辑说起这里要先交代一下 MiniCPM5-2B 是什么。它属于面壁智能开源 MiniCPM 系列中的新一代端侧模型名字里的 5 是系列代号2B 指的是参数量级为 2B。这类模型的共同特征是“小身材、大能量”参数规模小意味着它对内存和算力的要求低普通笔记本也能跑但训练阶段又做了大量调优使它在摘要、问答、分类这类中短文本任务上的表现远远超过同体积的早期模型。在 MiniCPM5-2B 之前我试过两种方案。第一种是直接用云端大模型 API效果确实好摘要写出来流畅又准确但一个月跑下来发现成本并不低。新闻简报不是一次性任务我每天要跑两次每条新闻都要过一次模型再加上偶尔调 prompt 时的重复调用一个月账单轻松突破几十块。第二种是本地跑一个 13B 甚至更大的开源模型效果是好但我那台 16GB 内存的 Windows 本几乎被吃满跑一次要等两分钟CPU 风扇直接起飞根本不像一个应该老老实实待在后台的工具。MiniCPM5-2B 正好卡在中间位置。量化后模型文件约 1.2GB运行内存占用 2GB 左右8GB 内存的机器完全带得动。我在我的笔记本上实测单条新闻摘要耗时大约 3 到 6 秒每天抓 20 条新闻跑完一轮也不过 1 到 2 分钟。这个速度虽然跟云端 API 没法比但对“每天早上起来看一份简报”这个场景来说完全够用。1.2 本地部署 vs 云端 API 的真实取舍网上不少文章把“本地部署”吹成万能解药真用下来其实不是那么回事。先说优点本地部署最直接的好处是隐私。新闻内容虽然绝大多数不敏感但你的阅读偏好、订阅范围这些信息如果每天往第三方服务器送多少有点心里不舒服。本地部署后整个链路全部在本机完成RSS 抓取、模型推理、简报生成没有任何一次请求出网除非你额外接了邮件推送。第二个好处是稳定和可控。云端 API 偶尔会遇到服务波动、限流prompt 调得好好的第二天模型版本被服务商悄悄更新输出格式就变了。本地模型没有这个问题你的环境自己说了算量化版本、上下文长度、采样参数全部固定今天跑出来的结果和明天跑出来的结果高度一致排查问题非常舒服。缺点同样明显。本地模型的能力上限摆在那里2B 模型对长文本、复杂推理和细节判断的能力跟云端大模型确实有差距。具体到新闻摘要场景它能把事件主体和关键动作概括得八九不离十但如果你要求它做深入分析、跨新闻关联比对就会开始露怯。另外模型下载、依赖安装、定时任务配置都是自己维护没点命令行基础会有点吃力。我的态度是工具要看场景选。每日简报这种“重复执行的轻量任务”非常适合本地小模型如果你要写深度研报、预测性分析那就老老实实把 API 调起来。1.3 系统整体架构与数据流整套系统的架构非常简单我用一条单向数据流来概括新闻源抓取 → 内容清洗去重 → 模型摘要 → 结构化整理 → 简报输出。每个环节互相独立任何一个环节挂了都不影响其他模块排查问题的时候非常方便。新闻源我选择的是 RSS。原因其实很简单RSS 是公开标准大多数新闻网站、博客、官方媒体都提供不用维护复杂的爬虫逻辑和登录状态。抓取用feedparser一个 Python 库几十行代码就能把订阅源里的标题、链接、摘要、发布时间全部解析出来。内容清洗阶段我会把 HTML 标签、多余空白、无意义的推广段落都去掉同时用 URL 去重确保同一条新闻不会因为订阅源重复而出现两次。接着进入摘要生成阶段这是整个系统的心脏。我把每一条新闻的标题、正文首段和关键句拼成一段 prompt发给本地跑的 MiniCPM5-2B让它按固定格式输出“摘要、关键信息点、分类”。生成结果会用正则解析保证格式稳定。最后把所有条目拼装成 Markdown 格式的简报文件按日期存到本地目录同时通过 SMTP 邮件推送到我的个人邮箱用手机就能看。整个链路跑完后模型进程退出内存释放等下一个定时任务再唤起。2. 核心细节解析与实操要点2.1 硬件要求与模型加载方案先解决一个最直接的问题我的机器配置不高能跑吗根据我自己的实测CPU 是 i5-1240P、内存 16GB、没有独立显卡跑 MiniCPM5-2B 的 Q4 量化版毫无压力。如果你的机器是 8GB 内存也基本够用因为模型推理时峰值内存占用大概在 2GB 到 2.5GB 之间剩下的内存给系统正常跑没问题。当然如果你有苹果 M 系列芯片或者一张入门级 NVIDIA 显卡体验会更好推理速度能明显提升功耗也更低。模型加载方案我推荐用 Ollama。Ollama 相当于一个本地模型管家负责模型下载、进程管理、请求转发支持 OpenAI 兼容的 REST API。它最大的优势是开箱即用你不需要手动处理依赖、编译、量化这些繁琐的事拉下来一个 tag 就能跑。如果你更偏好极客路线用 llama.cpp 自己编译也不是不行但对大多数想快速看到效果的人来说Ollama 明显更合适。在 Ollama 里拉取模型就是两条命令的事ollama pull minicpm5-2b ollama psollama ps用来确认模型是否已经加载。注意模型首次运行时需要加载到内存在你请求一次后会保持常驻一段时间如果长时间没有请求它会被自动释放。2.2 新闻源选择与内容清洗新闻源的质量直接决定简报的质量。我建议一开始先订阅 5 到 10 个你真正会看的源而不是贪多凑 50 个。原因很简单本地模型每天的输入量是有限的如果 50 个源每样取 5 条光输入清洗后的正文就会花掉大量时间最后生成的简报反而变成了垃圾信息堆。我目前实际保留的源大概是 8 个覆盖科技、财经、数码、行业资讯每个源取前 10 到 15 条最新条目合成日报刚好读 5 分钟。抓取之后的清洗比想象中重要。RSS 的 description 字段往往带着 HTML 标签、站内推荐、推广链接直接喂给模型会严重干扰摘要质量。我的清洗逻辑分四步用正则去掉所有...标签保留纯文本把连续的空白字符压缩成单个空格删除包含“广告”“点击查看”“阅读原文”这类词的句子通常是模板化内容截取前 500 字符作为模型输入避免单条新闻太长。去重则是另一个关键环节。同一内容可能会被多个源转载标题可能不同但链接往往一致。所以我以 URL 为唯一键用 MD5 生成链接哈希在每一天的任务运行前判断是否已处理过。标题去重不适合做大范围判断因为不同媒体喜欢改标题很容易误判。2.3 Prompt 设计让 2B 模型稳定输出摘要小模型的指令遵循能力比大模型弱这是客观事实但可以通过设计者的经验来补。Prompt 有三个原则任务单一、输出格式固定、给出强约束。我用的模板如下你是一个本地新闻摘要助手。请把用户提供的新闻内容压缩成简报条目包含三个字段 摘要用不超过40个字概括事件核心 关键点提取一个最关键的信息点可以是金额、人名、时间、型号等 分类从【科技、财经、体育、文娱、生活、其他】中选择一个 输出必须严格按下面的格式不要添加任何解释性文字 摘要... 关键点... 分类...读起来很简单但里面有几个容易踩的细节。第一必须明确“不要添加任何解释性文字”否则本地模型会自作多情地回复一句“好的下面我来为您总结”把格式完全打乱。第二分类必须给封闭集合不给模型发挥空间否则它今天输出“科技”明天输出“关于科技的内容”后天输出“科技类”数据会变得没法看。第三摘要长度限制一定要明确否则小模型偶尔会把原文大段复制过来输出长度完全失控。在实际调用时我建议把 temperature 调低到 0.2 到 0.4 之间同时限制最大输出长度在 200 个 token 左右。这样做的目的是让模型尽量保守地按原文信息分配注意力而不是发散出原文没有的内容。2.4 简报输出形式与分发方式摘要生成完之后简报的组装是最后一步但同样有很多细节。我选择 Markdown 作为输出格式因为它结构清晰、不同平台兼容性好而且在手机上阅读时排版不会乱。每天的简报文件命名带日期指向明确例如brief-2025-05-20.md。组装逻辑是按新闻发布时间从新到旧排列每条新闻包含序号、摘要、关键点、分类和原文链接。分类相同的新闻我会尽量排在一起看起来更有层次。文件生成后存放在本地output/目录同时通过 SMTP 发送一份到邮箱。邮件正文直接用 Markdown 渲染成 HTML简单又好看。关于邮件推送仍然站在本地部署的原则上多说一句SMTP 与你自己是同一套逻辑其实也算第三方服务但邮件本身是你自己的个人信箱内容只发给你自己隐私风险可控。如果你不想用邮件也可以选择只生成文件后用坚果云/Dropbox 同步到手机或推送到自己的微信/TG 机器人这个就完全看个人习惯了。3. 实操过程与核心环节实现3.1 环境准备与模型部署我用了 Python 3.10 作为主环境这一步没有太多幺蛾子Python 3.9 以上基本都能跑。有一个经验和大家分享不要用系统自带 Python在项目里建虚拟环境更干净。我用的是conda create -n news python3.10以后想删掉重来也方便。依赖安装用了两个包一个是feedparser一个是ollama。前者解析 RSS后者调本地模型推理。如果你准备用 OpenAI 兼容接口也可以只装openai包base_url 指到http://127.0.0.1:11434/v1原理一样。我这里用一条命令安装pip install feedparser ollama模型部署同样简单我直接把 Ollama 服务跑起来然后拉取模型。首次拉取会根据网速等待一段时间国内网络环境下载会比较慢建议给 Ollama 配好镜像源环境变量比如使用HF_ENDPOINT指向国内社区镜像能快不少。拉取完成后用ollama run minicpm5-2b先手动测试一句确认模型能正常回复再往下走。3.2 新闻抓取与去重模块抓取模块的核心代码如下我略去了无关日志保留主干逻辑。这里有两个容易被忽略的细节RSS 条目里的summary字段可能为空发布时间published_parsed可能不存在。所以我的代码做了完整的兼容处理取不到时间就直接用抓取时刻避免后来排序时莫名其妙报错。import hashlib import re import feedparser from datetime import datetime RSS_FEEDS [ https://example-news.com/rss, https://example-tech.com/news/rss.xml, ] def fetch_news(feed_url, limit15): parsed feedparser.parse(feed_url) items [] now datetime.now() for entry in parsed.entries[:limit]: title entry.get(title, ).strip() link entry.get(link, ).strip() summary entry.get(summary, ) or summary re.sub(r[^], , summary) summary re.sub(r\s, , summary).strip() if not title or not link: continue published now if hasattr(entry, published_parsed) and entry.published_parsed: try: published datetime(*entry.published_parsed[:6]) except Exception: published now dedup_key hashlib.md5(link.encode(utf-8)).hexdigest() items.append({ title: title[:100], link: link, summary: summary[:500], published: published, dedup_key: dedup_key, }) return items去重部分我维护了一个seen.txt文件每处理一条新闻就把这条任务的 dedup_key 写入文件。下次跑任务前先读一次内存表和磁盘表合并去重。这个办法比较土但非常适合单机小系统没有额外依赖也不会出现重复推送。def load_seen(): try: with open(seen.txt, r, encodingutf-8) as f: return set(line.strip() for line in f) except FileNotFoundError: return set() def mark_seen(dedup_keys): with open(seen.txt, a, encodingutf-8) as f: for k in dedup_keys: f.write(k \n)3.3 摘要生成与简报组装摘要生成我封装了一个函数输入是清洗后的新闻对象输出是解析后的结构化字典。这里我额外做了一件事把新闻标题和摘要一起放进 prompt。原因是只放摘要的话上下文信息太少模型无法判断新闻主题只放标题的话模型又缺少事件细节。两者结合准确率会高很多逻辑其实不复杂相当于把食材切好了再给厨师。import ollama SYSTEM_PROMPT ( 你是一个本地新闻摘要助手。请把用户提供的新闻内容压缩成简报条目 包含三个字段分别是摘要、关键点、分类。 分类只能从【科技、财经、体育、文娱、生活、其他】中选择一个。 输出严格按下面的格式不要添加任何解释性文字 摘要...\n关键点...\n分类... ) def build_user_prompt(item): return ( f新闻标题{item[title]}\n f新闻内容{item[summary]}\n\n 请输出简报条目。 ) def generate_brief(item, modelminicpm5-2b): resp ollama.chat( modelmodel, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: build_user_prompt(item)}, ], options{ temperature: 0.3, num_predict: 200, }, ) raw resp[message][content] return parse_brief(raw, item)parse_brief负责把模型输出解析成结构化字段。如果模型偶尔不按格式来我加了一个兜底逻辑将整段输出当作摘要关键点和分类置为空。这个兜底虽然损失了一些信息但保证了主流程不会因为解析失败而崩溃。import re def parse_brief(raw, item): summary re.search(r摘要[:]\s*(.), raw, re.S) key_point re.search(r关键点[:]\s*(.), raw, re.S) category re.search(r分类[:]\s*(.), raw, re.S) return { title: item[title], link: item[link], published: item[published], summary: summary.group(1).strip() if summary else raw.strip()[:80], key_point: key_point.group(1).strip() if key_point else , category: category.group(1).strip() if category else , }3.4 定时任务与邮件推送全部逻辑跑通后剩下的就是自动化。我用的方案是在 Linux 云服务器上跑 cron实际上本地 Windows 机器也可以用“任务计划程序”但服务器的好处是 7x24 小时不关机每天早上 7 点就能收到简报不用开着电脑等待。如果你选择本机跑 cron记得把目标机器设置成自动唤醒否则定时任务形同虚设。我的 cron 写法15 7 * * * cd /home/user/news-brief /home/user/miniconda3/envs/news/bin/python news_brief.py log/brief.log 21这段的意思是每天早上 7 点 15 分执行一次脚本标准输出和错误输出都追加到 log 文件里。比较坑的一处是 cron 执行时的 PATH 环境变量往往很短python 解释器必须写绝对路径否则会因为找不到命令而静默失败。邮件推送的代码我基于 SMTP 实现思路是用前一天生成的 Markdown 文件渲染成 HTML 并发送。这里的坑点是 QQ 邮箱、163 等主流邮箱都要先开启“SMTP 授权码”在代码里使用的密码是授权码而不是登录密码否则会一直报 535 认证错误。我在代码中已经预留了配置区域你只需要替换成自己的邮箱和授权码。import smtplib from email.mime.text import MIMEText from email.header import Header SMTP_HOST smtp.example.com SMTP_PORT 465 SMTP_USER your-emailexample.com SMTP_PASSWORD your-auth-code SEND_TO your-emailexample.com def send_email(subject, html_content): msg MIMEText(html_content, html, utf-8) msg[Subject] Header(subject, utf-8) msg[From] SMTP_USER msg[To] SEND_TO server smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) server.login(SMTP_USER, SMTP_PASSWORD) server.sendmail(SMTP_USER, [SEND_TO], msg.as_string()) server.quit()3.5 完整脚本整合示例把所有模块拼到一起核心执行流程是 fetch → dedup → generate → build_report → email。这里我只给出执行流程的关键代码段完整脚本建议跟着文章顺序自己搭一遍遇到问题反而更有收获。def main(): seen load_seen() all_items [] for feed_url in RSS_FEEDS: items fetch_news(feed_url) for item in items: if item[dedup_key] in seen: continue all_items.append(item) print(f新抓取新闻数{len(all_items)}) brief_items [] for item in all_items: try: brief generate_brief(item) brief_items.append(brief) except Exception as e: print(f生成失败{item[title]}错误{e}) continue mark_seen([item[dedup_key]]) report build_markdown(brief_items) save_markdown(report, datetime.now()) if brief_items: send_email(本地新闻简报 datetime.now().strftime(%Y-%m-%d), report) if __name__ __main__: main()这样一整套流程就跑通了。每天早上自动完成新闻抓取、摘要、整理和推送全程不需要人工干预。4. 常见问题与排查技巧实录4.1 模型加载慢、首字延迟高很多朋友第一次跑的时候会遇到这个情况模型第一次请求等了 20 多秒才出内容于是直接怀疑代码写错了。其实不是本地模型首次请求确实需要把模型从磁盘加载到内存这个过程根据磁盘速度和内存带宽不同通常在几秒到十几秒之间。如果你希望每天跑任务时都跳过这个等待可以让 Ollama 服务常驻启动其中一个方案是调整环境变量让模型在拉取后立即预加载也就是冷启动前先发一个空请求。更省事的方法就是接受这 20 秒毕竟它一天只跑两次。4.2 摘要内容不稳定、乱编数据这是本地小模型最容易出问题的点。MiniCPM5-2B 在事实性和准确性上无法和云端大模型比偶尔会生成明显错误的数字或人名。我的处理策略是在 prompt 里强调“仅根据原文内容进行压缩不要补充原文没有的信息”同时在解析阶段对输出做一次关键词校验如果摘要里出现了原文完全没有出现过的数字就标记为低置信度降级为原文首句。这个逻辑用 10 行代码就能实现但能极大减少“一句话里出现三个编造数字”的尴尬。4.3 上下文超长导致效果下降2B 模型能处理的上下文窗口不小但理解能力和输入长度并不成正比。当单条新闻超过 500 字时摘要质量会明显下降模型容易抓不住重点。我的做法是在清洗阶段就截断正文只保留前 500 字如果一条新闻真的很重要我宁愿多花一次调用把它拆成两段分别生成摘要再人工汇总。实际测试下来截断方案比暴力全量输入的效果要稳定得多。4.4 定时任务莫名失败cron 任务失败的坑非常多最常见的三个是路径找不到、环境变量缺失、权限不足。我的排查顺序是先手动在项目目录执行一次脚本看是否成功成功后再去看 cron 日志确认是否有输出最后用绝对路径把 Python 和项目路径写死。我建议在 main() 开头打印当前时间和工作目录这样 cron 日志里会有足够的信息帮你定位问题。另外如果抓取新闻时用了网络请求还要确认定时任务运行时机器网络正常否则 feedparser 会拿不到空内容任务看似成功但实际上什么都没干。4.5 本地新闻简报系统的避坑速查表我把踩过的坑整理成一张速查表方便你排查问题时对照问题可能原因解决建议首字延迟高模型冷启动加载中接受延迟或让 Ollama 常驻预加载摘要乱编数字小模型事实性弱prompt 强约束解析阶段校验原文关键词输出格式混乱指令约束不够temperature 降到 0.2-0.3加 few-shot 示例单条新闻处理慢输入太长清洗阶段截断到 500 字符以内cron 不执行环境变量或路径错误写绝对路径开启日志检查邮件发送认证失败SMTP 授权码未配置使用邮箱授权码而非登录密码多次收到相同新闻去重逻辑失效用 URL 的 MD5 做唯一键持久化到本地文件内存/cup 占用过高模型常驻 其他程序跑完后调用释放或限制同时运行的模型进程数这张表基本覆盖了我从零搭建这套系统时遇到的大部分问题。遇到类似情况优先对照表格排查会比盲目抄代码更省时间。最后说点体会。搭这套本地新闻简报系统之前我一直觉得本地模型距离“日用”还很远但真正把 MiniCPM5-2B 接入这条流水线之后我最大的感受是小模型的价值不在通用智能而在单一场景的稳定交付。你给它一个窄而明确的菜单它就能干得相当靠谱。只要你别要求它无所不知它的体验完全支撑得起“每天一份自己的报纸”这个小梦想。想偷懒的朋友建议先在桌面跑一次完整脚本感受一下从抓取到推送的整条链路再决定要不要上定时任务。我现在每天早上的第一件事就是打开邮箱里的新闻简报——不到五分钟今天值得关注的事情就已经全部过完一遍了。