
每天早晨打开十几个网页、把同样的新闻翻来覆去读三遍、然后憋出一份像样的行业日报——我把这个状态维持了半年直到做了 AIHOT。这个开源项目的名字很简单AI 加 HOT用意是一个月前写在 README 第一行的那句话让大模型替你把“找热点”和“写日报”这两件重复的事吃掉。AIHOT 的定位不是又一个新闻聚合器而是一个面向垂直行业的自动热点监测与日报生成工具。它定时采集你指定的信息源把原始新闻交给大模型做语义去重、热度打分和摘要生成最终自动产出可直接发布的 Markdown 内容并同步生成一个零成本的静态行业热点站。如果你每天都要花半小时以上盯行业动态、整理情报、写工作日志这个项目就是替你值班的实习生。下面把项目的来龙去脉、核心实现、踩坑过程都放出来包含可直接复现的代码思路和配置方案。1. 项目缘起从手动刷热点到“让系统替我值班”1.1 一个偶然的需求触发了这个项目我过去负责一个垂直行业的内容运营每天的固定节奏是这样的早上打开行业门户、技术社区、几个竞品的公告栏挨个翻一遍“昨日发生了什么”中午把重要新闻摘出来下班前再整理成日报发到团队里。听起来工作量不大但真正跑起来非常痛苦。一个行业的有效信息源至少有二三十个其中一半是长期不更新的“僵尸源”另一半每天可能产出几十条内容真正对团队决策有参考价值的不到五条。我花了大量时间做的其实是信息筛选和格式排版真正体现判断力的“这件事为什么重要”反而没时间写。后来我试过用关键词过滤比如把“融资”“发布”“开源”这些词做成规则效果别提了——同一种意思的表达千变万化规则写多了就误杀写少了就噪音爆炸。于是我想明白了一个问题信息筛选的核心不是命中关键词而是理解内容。这件事正好是大模型擅长的。1.2 为什么选“大模型开源”这条技术路线做这个项目之前我简单评估过三条路线。第一纯规则方案。用爬虫抓标题、用正则过滤关键词、按发布时间排序。优点是便宜、快、可控缺点很致命——它无法理解“这个产品和那个产品其实是同一件事”也无法回答“这条新闻和昨天那条有什么关联”。第二直接用现成的商业情报产品。市面上不少但普遍的问题是贵而且信息源的定制能力有限我想监控的很多小众社区和 RSS 源它们根本不收录。第三自己用大模型做一套。这个方案的好处是数据源完全可配置大模型负责语义层面的判断输出格式也完全由自己定义。“开源”对我来说有两个意义。首先是可控我可以把每天的真实输出内容、提示词、数据源配置全部暴露出来哪条新闻是被什么逻辑选中的一目了然而不是一个不可解释的黑盒。其次是可扩展我只需要把大模型的接入层做成插件就可以在在线 API 和本地部署的开源模型之间无缝切换兼顾了效果、成本和数据隐私。三条路线对比下来结论很清楚大模型负责“理解”开源保证“透明”这件事只有这么干才成立。方案语义理解能力定制程度成本可解释性纯规则过滤弱中等极低完全可控商业情报产品强受限高不可控大模型开源自建强完全可定制中等代码开源、逻辑可见1.3 这个项目适合谁如果你符合下面任何一个特征AIHOT 就值得参考每天需要阅读大量行业资讯并且要把重点汇报给团队的内容运营或行业研究员想搭一个“几乎零成本”的个人热点站不想买服务器也不想维护数据库对大模型应用感兴趣想找一个集数据采集、定时任务、模型调用、静态站点生成于一体的完整练手项目手上已经有私有数据源不想把内容发给外部模型想做一套本地化方案。项目整体使用 Python 实现依赖很少核心模块只有四个采集、存储、模型调度、站点生成。部署难度大约是“能跑 pandoc 写文档”的水平。2. 系统设计与核心模块拆解2.1 整体架构一条清晰的数据流水线AIHOT 采用了非常经典的四层结构每一层职责单一层与层之间通过标准的数据结构传递信息。数据源层是最外围的负责定义我们关注的信息从哪里来采集层把这些来源变成结构化的文章对象语义分析层是整个项目的核心负责用大模型做去重、打分、聚类和摘要表达层把分析结果变成日报和站点页面。数据流大致是这样走的原始文章 → 归一化 → 候选池 → 模型筛选打分 → 排序后的每日重点 → 生成 Markdown 日报和静态站点页面。我刻意没有给“热点发现”做一个单独的大模块因为“热点”这件事本质上不是一个独立的功能而是多源信息经过交叉验证之后得到的结论。所以整个架构的核心动作其实是两个归一化与再表达。2.2 热点采集数据从哪里来采集是很基础的活但数据源却是一个项目能不能变聪明的关键。AIHOT 支持三类数据源。第一类是 RSS/Atom 订阅源。这是最稳定的输入形式feedparser 一个库就能解析绝大多数行业博客、开源社区、科技媒体都保留了 RSS。对一个垂直行业来说找 15 到 20 个高质量 RSS 源已经能覆盖百分之八十的信息量。第二类是公开热榜接口。很多资讯平台和搜索引擎都有自己的热榜数据这类接口返回的结构通常是 JSON字段直接就是标题、热度值、链接解析成本极低。需要注意的是一般都需要配置 User-Agent 和请求间隔避免给对方服务器造成压力。第三类是普通网页源码。某些重要信息源没有提供 RSS也没有热榜接口这种情况下只能靠抓取 HTML 里的特定 DOM 节点。我一般会为这类源单独写一个解析函数把标题和链接用 CSS 选择器提出来。这部分代码虽然繁琐但只要源站改版概率低写一次就能用很久。所有数据源在配置层面都统一成一个列表每条源记录包含名称、类型、URL、权重和抓取频率。权重参数很重要核心源权重高在后续打分时会获得额外的加权。2.3 热点识别大模型怎么判断“值得看”采集层拿到的原始文章集合距离“热点”还有很远的距离。我最初尝试直接用大模型一次性处理全部候选文章结果又慢又贵而且输出不稳定。后来拆成了三层漏斗第一层是机械过滤。把标题和正文长度过短的、重复的、明显是广告的内容剔除逻辑简单用规则就能完成。第二层是局部加权。结合时间衰减函数和源权重给每篇文章算一个基础分。公式很朴素基础分等于源权重乘以时间衰减系数时间衰减系数用指数形式超过 48 小时的文章权重衰减得非常快。这样能保证“今天的新闻”天然排在“上周的新闻”前面。第三层才是大模型判断。我会把候选集中最相关的几条新闻标题和摘要打包发给模型让模型做三件事判断它们是不是同一个事件、给每个事件打一个 1 到 10 的“关键度分”、用一句话解释打分的理由。模型输出 JSON后续解析非常稳定。这三层漏斗的好处在于大模型的调用次数被控制在一个很小的数量级成本可控而且每一层都有自己的判断逻辑不会因为模型抽风导致整体结果不可用。2.4 日报生成与站点发布日报是 AIHOT 的最终交付物。默认的日报结构是四段式今日焦点、分类速览、趋势观察、原文链接汇总。今日焦点放不超过五条最重要的内容每条配一段由模型生成的“为什么重要”分类速览按行业子领域把新闻分组每条只保留一行摘要趋势观察是模型对当天整体态势的概括性判断比如“本周出现三个同一赛道的融资事件说明这个方向正在升温”。站点发布则是把日报固化成网页。我用的是纯静态方案每次运行时会生成当天的 HTML 文件和一个倒序排列的索引页。静态方案最大的好处是不需要服务器推到网页托管平台或者对象存储上就能跑成本基本为零。3. 关键实现核心代码是怎么写的3.1 采集层让几十个来源统一成一个结构采集层最难的是把不同来源的数据“洗”成统一结构。我给每个文章对象定义了五个字段title、url、summary、published_at、source。RSS 源直接用 feedparser 解析热榜接口用 requests 拉 JSON。下面是核心采集代码的结构实际使用时可以根据自己的数据源扩展import hashlib import time import requests import feedparser # 数据源配置示例 SOURCES [ { name: example_blog, type: rss, url: https://example.com/feed.xml, weight: 1.2, }, { name: example_hot_keywords, type: json_api, url: https://example.com/api/hot, weight: 1.5, }, ] def normalize_article(title, url, summary, published_at, source): key hashlib.md5(title.encode(utf-8)).hexdigest() return { id: key, title: title.strip(), url: url, summary: (summary or ).strip(), published_at: published_at, source: source[name], weight: source[weight], } def fetch_rss(source): feed feedparser.parse(source[url]) articles [] for entry in feed.entries: title getattr(entry, title, ) link getattr(entry, link, ) summary getattr(entry, summary, )[:500] published_at getattr(entry, published_parsed, time.gmtime()) articles.append(normalize_article(title, link, summary, published_at, source)) return articles def fetch_json_api(source): resp requests.get(source[url], headers{User-Agent: AIHOT/0.1}, timeout10) data resp.json() # 这里根据实际接口结构调整字段提取逻辑 articles [] for item in data.get(data, []): title item.get(title, ) url item.get(url, ) score item.get(hot, 0) articles.append(normalize_article(title, url, , time.gmtime(), source)) return articles def fetch_all(): all_articles [] for source in SOURCES: if source[type] rss: all_articles.extend(fetch_rss(source)) elif source[type] json_api: all_articles.extend(fetch_json_api(source)) time.sleep(1) # 保持礼貌的抓取间隔 return all_articles这段代码没有什么高深技巧但有一个细节值得强调每篇文章的 id 是标题的 MD5。这个 id 会一直用到去重环节确保同一篇新闻即使在不同源出现也能被识别成同一条内容。3.2 大模型调用提示词决定了日报质量采集只是基础真正决定 AIHOT 质量的是提示词。我踩了很多坑之后把提示词拆成三段身份设定、任务规则、输出格式。身份设定是告诉模型它扮演什么角色比如“你是一位 TMT 行业分析师”。这个设定不能写得过于宏大否则模型容易自作主张输出一些空洞的“行业趋势”。任务规则要非常具体明确告诉模型它必须基于给定的新闻列表做判断不能使用列表外的知识。输出格式则固定为 JSON包含事件聚类结果、热度评分和理由。一个简化后可用的提示词模板如下你是{industry}行业的资深分析师。 你将收到今天采集到的新闻标题和摘要请完成以下任务 1. 将描述同一事件的新闻合并为一组并选取最完整的一条作为主条目 2. 对每个事件给出关键度评分1到10分10分代表可能影响行业全局 3. 用不超过50字解释你给这个分数的原因。 判断原则 - 只有当事件对行业决策有实际影响时才给6分以上 - 重复宣传稿、公司日常新闻不建议超过4分 - 评分必须基于给定新闻内容不得猜测事实。 输出格式为JSON数组每个元素包含 {event: 事件简述, articles: [关联文章标题列表], score: 评分, reason: 判断理由}在这里我强烈建议把输出格式直接问到 JSON。一开始我让模型“自然语言输出”结果每个源生成的分析风格都不同下游做站点发布时要把文本重新解析组织成 HTML非常痛苦。固定输出 JSON 之后整个链路一下子稳定了。调用层则通过一个统一的接口类完成支持切换不同的模型后端import json MODEL_NAME qwen-plus # 可按需替换接口兼容 OpenAI 格式即可 def analyze_articles(articles, industry): prompt PROMPT_TEMPLATE.format(industryindustry, articlesformat_articles(articles)) resp llm_client.chat.completions.create( modelMODEL_NAME, response_format{type: json_object}, messages[ {role: system, content: 你是一个严谨的数据分析助手只会输出合法JSON。}, {role: user, content: prompt}, ], ) return json.loads(resp.choices[0].message.content)llm_client 可以是任何提供 OpenAI 兼容接口的模型服务也可以是本地通过诸如 Ollama 之类工具启动的开源模型只需要把 base_url 指到本地端点。3.3 定时调度与增量更新AIHOT 的日常运行完全不需要人管只要配置一个定时任务。我使用的是 APScheduler它可以在 Python 进程内直接管理定时任务比系统 crontab 更容易跟随项目一起部署。from apscheduler.schedulers.blocking import BlockingScheduler def daily_run(): articles fetch_all() filtered mechanical_filter(articles) hot_events analyze_articles(filtered, industry开源软件) render_daily_page(hot_events) scheduler BlockingScheduler() scheduler.add_job(daily_run, cron, hour7, minute30) scheduler.start()把 daily_run 包一层日志记录和异常捕获就可以稳定挂在后台。增量更新方面不需要维护太复杂的增量状态因为每次运行都会重新抓取当前数据然后在去重环节用本地 SQLite 表记住已经处理过的文章 id保证同一篇文章不会在日报里出现第二次。这里有一个我自己很推荐的细节把 SQLite 去重表和静态站点生成目录放在同一个代码仓库里。每次运行后都留痕哪天日报突然出现了异常内容可以回溯到是哪一次抓取的哪条新闻造成的。4. 部署过程中踩过的坑4.1 重复内容刷屏同一件事反复出现在日报里AIHOT 刚跑起来的第一周日报里经常出现“同一事件被三家媒体报道在焦点区里占了三个位置”的情况。根源是不同信息源对同一事件的标题写法差异很大“某公司发布新版本”和“某公司宣布下一代工具正式上线”其实是同一件事MD5 去重根本拦不住。解决思路是多做一层标题归一化。标点符号统一为半角数字统一转为阿拉伯数字去掉“重磅”“快讯”这类常见动词和修饰词然后再算 MD5。这样处理之后重复率明显下降。我还用了一个更狠的方案把归一化后的标题丢给大模型做一次批量聚类模型输出每个事件包含哪些标题日报里只放主条目其余作为引用链接。建议千万不要对大模型说你相信它每次都能正确聚类所有聚类结果必须配合一个简单的字符串相似度校验相似度高于 90 的直接合并。4.2 模型接口超时和限流日报有时候会“卡死”上线后遇到最闹心的问题是接口不稳定。新闻数据量大时一次请求可能要处理几十篇文章模型响应时间长不说偶尔还会触发限流。一开始我没有做超时控制导致定时任务挂在网络请求上日报迟迟不生成。后来做了三件事所有模型调用统一加 30 秒超时超时后按指数退避策略重试三次如果重试仍然失败则自动降级为“仅输出标题列表”的日报模板不让整个任务失败。降级方案很重要它保证了日报每天都会产出哪怕当天内容质量稍差也不会断更。另外单次传给模型的文章条数必须设上限。我一开始让模型处理全部 200 条数据响应速度和效果都差后来把每条 prompt 的文章数限制在 30 条以内超出的部分让模型分批处理最后再合并排序。实测效果非常明显稳定性和单次成本都改善了一个量级。4.3 模型写出来的日报“看起来很对实则什么都没说”这是大模型应用里最典型的陷阱输出内容语法通顺、结构完整但对读者来说没有信息量。比如“今天某某领域出现多条动态整体呈现增长态势”这种话谁看了都知道是在凑字数。我的解决办法是把“判断理由”字段强制拆成两部分具体的事实依据和对读者行动的参考价值。提示词里明确要求模型必须引用新闻中的具体数字或主体来支撑理由没有证据的定性判断一律不给高分。另外我还会在每个分类下注入行业核心关键词词表比如开源赛道的“license”“star”“社区治理”这些词让模型在打分时优先关注这些维度。效果提升最明显的是把“趋势观察”这个字段的生成约束从“写一段话”改成“列出三个有明确因果关系的现象”。模型被迫去做因果推理而不是复述新闻标题日报的干货浓度一下就上来了。4.4 Token 成本悄悄失控月度账单吓人一跳大模型按量计费AIHOT 如果每天都全量处理几百条新闻成本其实不低。我在最初版本犯了所有新手都会犯的错为了“不留遗漏”把每篇文章的正文都塞进模型。后来一算账每个月光日报生成就烧掉一笔可观的费用内容质量却没有同比提升。成本控制的核心原则是把贵的判断留给真正值得的内容。现在流程改成了先用规则过滤掉低质量文章再对候选事件做模型分析每天只需要对最多三十个候选事件调用深度生成其余内容统一走标题列表。按照这个配置如果使用国内可正常访问的模型服务一天的成本可以压到非常低本地部署开源模型的话则主要看电费。建议在代码里加一个每日 token 用量统计超过设置阈值就暂停深度分析并切换到降级模式。不要指望自己“肉眼观察成本”,一定要让数字说话。5. 实测效果与后续还能怎么玩5.1 运行一个月后的真实效果AIHOT 目前已经稳定运行了大约一个季度每天早晨七点半自动执行平均耗时为三分钟左右三十秒用于抓取剩下的时间用于模型分析和页面渲染。采集层的有效信息源有二十多个每天产生的原始文章大约 120 条经过机械过滤和多源合并之后剩下 40 个左右候选事件最终进入日报的焦点区的大约是 5 到 8 条。最开始我认为“自动化日报的质量一定不如人工”但运行一段时间后反而发现它有一个人工很难坚持的优势稳定和一致。人写日报的状态受情绪和工作量影响今天写五条重点明天可能就写三条AIHOT 每天用同一套标准运行,即使某天模型判断略有波动整体质量始终维持在一个基准线以上。作为个人项目经理这种“稳”比“偶尔惊艳”重要得多。5.2 围绕大模型的几个扩展方向这个项目的扩展空间很大说几个我最想做的方向。一是引入向量数据库做历史趋势分析。现在的分析是“当天孤立判断”如果把过去三十天的新闻内容存进向量库就能让模型识别“某个话题连续几天升温”的趋势性热点而不是只看单天爆发。二是把信息源的范围从新闻扩展到用户讨论。很多行业的真实信号不是来自官方新闻稿而是来自用户群、社区帖子和 issue 讨论。可以定期把这些平台的内容导出成文本作为增量输入喂给模型。三是输出形态更多样化。目前只生成 Markdown 日报和 HTML 页面后续可以生成邮件模板通过 SMTP 定时发到团队邮箱还可以生成一条知识库记录自动同步到团队内部的文档系统。四是让模型自己维护“行业关注点清单”。每隔一段时间让模型基于过去一段时间的日报提出现在应该新增关注的信息源和关键词然后人工确认后写回配置。这样就形成了一个半自动的运维闭环。从我个人的实际体会来说AIHOT 做出来之后最关键的价值不是省下了每天半小时而是它逼着我把“什么算是热点”这件事想清楚了。以前我凭感觉判断现在我把判断标准写成了规则、写进了提示词可以让任何一个新对接的模型学会这种感觉挺踏实的。最后再分享一个小技巧不要一开始就追求功能大而全先让它稳定输出一周的“不完美日报”再逐步调提示词和数据源。AIHOT 这个项目的骨架搭起来很快真正值钱的部分是后续持续打磨数据源权重和模型判断标准的过程。你花在调教它上面的每一分钟都会变成它替你节省的每一天里更精准的那份日报。