ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从零自建AI资讯聚合平台:Python+大模型打造个人信息管道

2026/10/6 14:42:47 拓冰建站 浏览量
从零自建AI资讯聚合平台:Python+大模型打造个人信息管道 每天刷几十个科技媒体、公众号和 RSS 源真正有价值的 AI 动态却常常被淹没在标题党和重复搬运里。我也用过不少现成的 AI 资讯聚合产品实际用下来问题很集中推荐算法是个黑盒你永远不知道它为什么把某条新闻推给你数据源完全不可控热门话题被反复洗稿小众但重要的信息反而没人管再就是核心功能动辄付费免费额度只够尝鲜。后来我干脆自己动手用 Python 从零搭了一套 AI 资讯聚合平台——抓取、清洗、AI 摘要、自动打标签、网页展示全流程自己说了算。这篇文章就是完整复盘架构怎么设计、核心代码怎么写、哪几个坑必须躲给想搭一个真正属于自己的信息管道的人做个参考。有一点 Python 基础就能跟上用到大模型接口的地方我会把参数逻辑讲透。1. 为什么值得自己动手搭一个 AI 资讯聚合平台1.1 现成聚合产品的三个痛点先说结论不是现成产品不好用而是它们解决的是大众流量问题不是个人信息质量问题。第一个痛点是推荐算法不透明平台根据点击率、停留时长、商业化合作来排序内容你看到的热门未必是信息密度最高的反而是最容易被转发、最容易引发情绪的。第二个痛点是数据源单一绝大多数聚合平台接的是头部媒体和平台热点独立博客、专业社区、冷门技术专栏这些高质量信源覆盖率很低而这些地方恰恰是 AI 领域很多新东西首发的地方。第三个痛点是功能浅很多产品只做搬运排版摘要就是截取开头几句话标签分得乱七八糟更谈不上按你自己的兴趣方向做深度过滤。这三个痛点叠加起来你就明白了资讯聚合这件事关键不在聚合本身而在筛选和消化的质量。而筛选标准、信息源选择、消化深度本质上是非常个人化的需求指望一个通用产品同时满足所有人不现实。自己动手就不一样信源我定摘要风格我定标签体系我定看到的东西完全围绕我关心的方向展开这种掌控感是现成产品给不了的。1.2 自建平台能换来什么自建这个平台直接收益有三层。第一层是信息质量我把自己关注的几十个 AI 相关源技术博客、论文动态、开源项目发布、头部媒体统一汇入一个管道经过 AI 摘要之后每天只需要花二十分钟扫一遍高密度摘要而不是花两个小时在各个 App 之间来回切。第二层是流程可控抓哪些源、多久抓一次、摘要写到多长、按什么维度聚类全部是配置文件里几个参数的事想调整随时调整。第三层是技术回报这个项目麻雀虽小五脏俱全涉及爬虫采集、数据建模、大模型 API 调用、向量检索、定时任务、Web 展示一套做下来对全栈链路和 AI 工程化的理解会明显上一个台阶。如果只是从读资讯这个角度看自建的初期体验未必比成熟产品流畅但它胜在长期价值数据全在自己手里可以做趋势分析、做周报、做个人知识库的输入源这些都是商业化产品很难给你的。我搭完用了半年最大的感受是——它改变了我的阅读习惯从被动刷信息流变成了主动管理信息流。1.3 什么样的基础能上手说实话这个项目的技术门槛没有想象中高。需要的基础大概是会 Python 的基本语法知道怎么用 pip 装依赖了解 HTTP 和 Restful API 的基本概念会最简单的 SQL建表、插入、查询愿意读一点大模型接口的官方文档。前端部分如果你完全不想碰可以直接用模板渲染输出一个简洁列表页不用写任何 JavaScript。整个项目大概几百行代码就能跑起来我建议第一次做的时候不要追求功能完善先把最小闭环跑通——抓取、入库、AI 摘要、列表展示后面再逐步加分类、搜索、趋势统计这些增强功能。2. 整体架构设计先想清楚再动手2.1 三个核心模块的分工这个平台在逻辑上可以切成三层每一层职责单一互不干扰。第一层是采集层负责从各个信息源把原始内容拿回来做清洗、去重、入库。第二层是处理层也是AI味道最重的一层对入库的文章做摘要、关键词提取、分类打标关键文章还可以做向量化用于相似度去重。第三层是展示层把处理完的数据以清爽的方式呈现出来按时间线、按专题、按标签浏览。这三层之间用数据库解耦。采集层只管写库处理层只管从库里读未处理的数据、处理后写回展示层只查库渲染页面。好处很明显任何一层挂了或者要升级不影响其他层处理逻辑想换模型不用动采集代码展示想改成前后端分离数据层早就准备好了。如果你一上来就把三层耦合在一个脚本里前期跑通很快后期加功能会非常痛苦我见过太多项目死在这上面。2.2 技术选型背后的考量技术选型我秉持一个原则用最少、最成熟的依赖完成核心功能不为炫技引入重型组件。语言选了 Python生态最全写脚本效率高社区里爬虫、数据处理、大模型 SDK 都是现成的。Web 框架用了 FastAPI轻量、自带异步支持、文档自动生成做一个小型展示站完全够用。采集解析用 feedparserRSS 解析这个库老牌稳定一行代码就能把 XML 转成结构化数据。数据库选了 SQLite零配置、单文件、备份方便个人项目的数据量每天几百条文章它毫无压力。定时任务直接用系统的 cron 或服务器的定时工具不引 Celery 这类重调度框架。为什么不做前后端分离因为信息聚合站的交互复杂度很低服务端模板渲染可以直接把数据填进 HTML 输出少一层调试成本页面加载还更快。等技术列表页也满足不了需求了再拆成 API 前端框架也不迟。这个取舍背后的逻辑是架构永远为当前真实需求服务不为想象中的未来买单。2.3 数据模型怎么设计数据模型是整个平台的地基我设计了四张核心表。sources 表存信源配置字段包括 id、名称、类型rss/api/scrape、URL、分类、是否启用、抓取频率。articles 表存文章主数据字段包括 id、来源 id、原文链接、标题、正文清洗后的纯文本、发布时间、抓取时间。processing 表存 AI 处理结果或直接在 articles 上加列也行我用的是加列方案ai_summary、keywords、category、embedding加一列 processed 标记处理状态。tags 表单独存标签和文章是多对多关系方便后面按标签筛选。这里有两个容易忽略的细节。一个是唯一索引articles 表的原文链接必须加唯一约束这是天然去重的第一道防线比在代码里判断快得多也可靠得多。另一个是 processed 状态字段必须建立索引否则随着数据量增长处理层每次扫描全表找未处理记录会越来越慢。SQLite 上这两个操作都是建表时一句 SQL 的事但能省掉后面大量的麻烦。3. 采集层让信息源稳定流入3.1 RSS最省力的信息源采集层第一个问题是数据从哪来我的答案是优先 RSS它对自建项目是最友好的数据源。RSS 是站点主动发布的标准化内容结构稳定、字段规范标题、链接、发布时间、正文不需要你写解析规则feedparser 一行就能解析。而且正规科技媒体、绝大多数技术博客、很多开源项目的 release 通知都提供 RSS。相比爬取 HTMLRSS 不涉及反爬、页面改版、动态渲染这些麻烦事维护成本低一个量级。用 RSS 要注意一个细节很多站点提供全文 RSS但也有一些只给摘要。对后者可以在采集时只存摘要文本然后让 AI 基于摘要生成要点效果虽然不如全文好但也能用。如果摘要太短比如只有一两句话我的做法是把它标记为低质量不送入 AI 摘要流程只展示标题和原文链接避免生成一堆没有信息量的废话。3.2 API 与网页抓取的取舍RSS 覆盖不了的信源再考虑 API 和网页抓取。API 里比较典型的是 GitHub Trending 和 Hacker News它们都提供了免费的公开接口返回 JSON字段清晰适合做每日热门项目和社区热点这两个板块。网页抓取是最后的手段只建议用于那几个没有 RSS 也没有 API 但内容确实优质的站点比如某些公众号的镜像站或独家专栏。网页抓取建议用 requests BeautifulSoup 的组合不要一上来就上 Selenium 这类浏览器自动化成本和稳定性代价太高。抓取时要写清楚 HTML 解析逻辑正文容器的 class 或 id、标题所在标签、发布时间格式。这里一定要抓取后立刻清洗正文去掉 script、style、广告位、页脚这些噪声节点。另一个容易踩的坑是编码问题有些页面声明是 UTF-8 实际是 GBKrequests 拿到响应后优先用 apparent_encoding 检测别想当然。3.3 去重与增量更新策略去重分三层来做。第一层是数据库唯一索引同一个链接重复插入直接报错跳过。第二层是标题相似度有些内容源会把同一篇文章换个链接、改个标题重发这种靠链接去重拦不住所以入库前把标题做个归一化去掉空格、标点、统一大小写后算哈希相似标题用编辑距离阈值过滤。第三层是语义去重放在 AI 处理层用 embedding 算相似度处理那些标题不同但内容高度重复的转载文章这个后面专门讲。增量更新的核心是记录每个源的最近发布时间或最近抓取时间。以 RSS 为例feedparser 能拿到文章的 published 时间判断一下是否晚于上次抓取到的最新时间就行。对没有时间的源用 Link header 或正文里的日期兜底。抓取频率也要分类控制日更的媒体一天两次足够小时级的科技新闻站可以每小时一次个人博客一周一次就够。频率太高会触发对方限流太低会错过时效性内容我通常用随机间隔比如每 25 到 35 分钟来做小时级抓取降低被封风险。4. AI 处理层从搬运到消化4.1 摘要生成的关键参数采集回来的文章是原始材料AI 处理层负责把它们消化成高密度的信息块。摘要生成是这里面的核心任务。我用的方案是调用大模型 API把标题、正文、一个精心设计的 prompt 一起发给模型让它输出 5 到 8 条要点式摘要。参数上最关键的三个是模型选择、温度、max_tokens。模型选择直接决定摘要质量和成本。我的做法是分级普通新闻用便宜的小参数模型比如各家服务商的轻量版本重要信源论文、深度长文用强模型。温度设为 0.2 到 0.3摘要任务要的是忠实不需要创造性温度高了容易开始自由发挥把原文没有的信息编进去。max_tokens 根据正文长度调整我通常限制在 300 到 500防止返回超长文本。prompt 里我还会明确要求保留具体数字、模型名、机构名不要输出本文介绍了值得注意的是这类废话开头实践证明这个约束能显著提升摘要的信息密度。4.2 分类打标签模型为主规则兜底打标签我用模型为主、规则兜底的双轨策略。模型负责开放式的主题分类我会在 prompt 里给一个固定标签池大模型、机器学习、计算机视觉、NLP、AI 应用、开源项目、行业动态、政策相关等要求模型从中选 1 到 3 个标签必要时允许它新增。标签池的设定很重要太粗等于没分太细模型会经常犯选择困难我优化下来的经验是 8 到 15 个标签最合适。规则兜底是指如果模型返回的标签为空或格式异常就用关键词匹配补一个默认分类保证每条记录都有标签页面展示不会出现空分组。这里有个提高准确率的小技巧prompt 里可以加几个 few-shot 示例比如GPT-5 发布会实录→大模型、行业动态模型会被引导到正确的分类粒度。我也踩过分类不一致的坑——同一个内容源的文章今天打大模型明天打LLM后面在标签体系上做了一个映射表把近义标签合并才彻底解决。4.3 相似文章去重向量检索的轻量实现语义去重这块我用 embedding 向量相似度做。原理不复杂把文章标题或标题第一段用 embedding 接口转成一个几百维的向量然后和库里最近几天的文章向量算余弦相似度超过阈值我取 0.88就判定为重复内容标记为转载或直接跳过。工程实现上不需要搭向量数据库个人项目的数据规模用 SQLite 完全能扛。具体做法给 articles 表加一个 embedding 列存向量序列化后的文本每次新文章处理完取最近 200 条已处理文章的向量用 numpy 计算余弦相似度。200 条 × 每篇文章的向量维度比如 768 维一次矩阵运算毫秒级完成完全够用。真正的重复文章通常集中在发布后的几小时到一天内所以只需要比对新文章的近邻窗口不需要全表扫描。如果你后面数据量涨到几十万条再考虑换真正的向量库不迟。4.4 成本控制的四个办法大模型 API 是自建平台唯一持续花钱的地方我在成本控制上总结了四条经验。第一能缓存就缓存同一个链接的文章只做一次摘要处理结果存库重复执行任务时直接从库里拿不做重复调用。第二先筛选再做摘要标题里就明显是软文、广告或者纯转发的内容用规则先过滤掉根本不送模型。第三控制正文长度超长文章截断到模型上下文窗口内的合理范围比如 8000 字摘要质量不会明显下降但 token 费用可能差好几倍。第四批量小模型优先日常资讯用轻量模型只有深度内容才上强模型。我自己跑下来每天处理 100 多篇文章一个月 API 费用控制在 20 到 50 元以内完全在可接受范围。5. 展示层让聚合结果真正可用5.1 最简方案静态页面定时生成展示层的最低可行方案是定时生成静态 HTML。实现思路非常简单每天抓取、处理完成后用一段生成脚本读取数据库把文章按时间线排好填入一个 Jinja2 模板输出到 index.html然后用任何静态 Web 服务器nginx、Python 的 http.server把它托管出去。这样做的好处是几乎没有运行依赖不需要常驻服务不怕流量攻击部署到哪都方便甚至可以本地生成后用同步工具推到免费静态托管平台上。静态方案的局限也很明显没法做交互式筛选、全文搜索、按标签动态过滤。所以我的建议是如果你只是自己做每日阅读静态方案绰绰有余如果想让平台更活一点就再往前走一步用服务端渲染做动态版。5.2 进阶方案FastAPI 服务端渲染进阶方案是把整个平台做成一个 FastAPI 应用提供三个核心路由首页时间线按发布时间倒序展示文章标题、来源、AI 摘要、标签、标签页按标签过滤、详情页跳转原文或展示本地正文。页面用 Jinja2 模板渲染数据从 SQLite 读取。这个方案的交互体验接近真正的产品但代码量只增加了几十行。我给这个方案加了两个实用功能。第一个是只看未读模式给文章加一个 read 字段点开后标记已读这样不会每天被同一批旧文章淹没。第二个是每日摘要页按天分组每天生成一个今日 Top 20列表把当天相似文章合并成一条主线点击展开看原始链接列表这个功能阅读效率极高比一行一篇文章的传统时间线好用得多。5.3 阅读体验的几个细节展示层决定了你每天愿不愿意打开它所以细节很重要。排版上我坚持三件事摘要直接平铺在标题下方不要求用户点进详情才看到内容标签用不同颜色块区分一屏能扫出当天大方向原文链接永远放在显眼位置AI 摘要可以错但读者永远能一键跳到原始出处。还有一个很容易被忽视的体验点加载速度。信息聚合站本质上是在推销注意力每多等一秒就有读者流失。我在全静态模式下实测首屏加载控制在 300 毫秒以内比大多数商业资讯 App 快得多。这本身就是一个优势——用最轻的技术方案换取最顺畅的阅读体验。6. 实操过程与核心代码实现6.1 环境准备与项目结构我在一台 2 核 4G 的云服务器上部署操作系统 Ubuntu实际占用资源极低。本地开发时 macOS 或 Windows 都没问题。依赖只有六个feedparser、requests、BeautifulSoup4、fastapi、uvicorn、jinja2大模型 SDK 按你选用的服务商安装。如果用 OpenAI 兼容的接口格式现在国内很多模型服务都兼容这个协议代码可以通用。项目结构按模块分保持清晰ai_aggregator/ ├── config.py # 信源配置、模型配置、阈值参数 ├── collect.py # 采集层抓取 RSS/API/网页 ├── process.py # 处理层AI 摘要、分类、去重 ├── generate.py # 展示层生成页面 ├── web.py # 展示层FastAPI 动态版 ├── templates/ │ └── index.html # 页面模板 └── data/ └── news.db # SQLite 数据库6.2 采集与入库代码采集层核心函数长这样。先初始化数据库表再遍历所有启用的信源解析后逐条插入import sqlite3 import feedparser import requests from bs4 import BeautifulSoup from config import SOURCES def init_db(): conn sqlite3.connect(data/news.db) conn.execute( CREATE TABLE IF NOT EXISTS articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT, url TEXT UNIQUE, title TEXT, content TEXT, published_at TEXT, processed INTEGER DEFAULT 0 ) ) conn.execute(CREATE INDEX IF NOT EXISTS idx_processed ON articles(processed)) conn.commit() return conn def collect_rss(conn, source): feed feedparser.parse(source[url]) for entry in feed.entries[:20]: url entry.get(link, ).strip() if not url: continue title entry.get(title, ).strip() content (entry.get(summary) or entry.get(description) or ).strip() published entry.get(published, ) try: conn.execute( INSERT INTO articles (source, url, title, content, published_at) VALUES (?,?,?,?,?), (source[name], url, title, content, published), ) conn.commit() print(f[新文章] {title[:40]}) except sqlite3.IntegrityError: pass # 重复链接唯一索引拦截URL 的唯一索引就是第一道去重防线同一条链接再次抓取时直接跳过。6.3 AI 摘要与分类代码处理层代码负责读未处理记录调模型写回结果。我用的是 OpenAI 兼容接口用 requests 直接调用不引额外 SDKimport sqlite3 import requests import numpy as np from config import API_KEY, API_BASE, MODEL, EMBEDDING_MODEL SUMMARY_PROMPT 请阅读下面的文章输出5-8条要点摘要。 要求 1. 保留具体数字、模型名、机构名等关键信息 2. 每条不超过40字 3. 不要写本文介绍了值得注意的是这类废话 4. 用中文回答 标题{title} 正文{content} def call_llm(messages, modelMODEL, temperature0.2, max_tokens500): resp requests.post( f{API_BASE}/chat/completions, headers{Authorization: fBearer {API_KEY}}, json{ model: model, messages: messages, temperature: temperature, max_tokens: max_tokens, }, timeout60, ) resp.raise_for_status() return resp.json()[choices][0][message][content] def process_one(conn, article_id, title, content): # 1. 摘要 summary call_llm([ {role: user, content: SUMMARY_PROMPT.format( titletitle, contentcontent[:8000])} ]) # 2. 分类打标 tag_prompt f给下面这篇文章选1-3个标签标签池大模型、机器学习、CV、NLP、AI应用、开源项目、行业动态。只输出标签逗号分隔。\n标题{title}\n tags call_llm([{role: user, content: tag_prompt}], temperature0) # 3. 向量化 emb get_embedding(title) conn.execute( UPDATE articles SET ai_summary?, tags?, embedding?, processed1 WHERE id?, (summary, tags, emb.tobytes(), article_id), ) conn.commit()6.4 页面渲染最短可运行版本生成静态页面的代码很简单核心就是查库、填模板from jinja2 import Template INDEX_TPL !DOCTYPE html html headmeta charsetutf-8titleAI 资讯聚合/title/head body {% for item in items %} div classcard h3a href{{ item.url }} target_blank{{ item.title }}/a/h3 div{{ item.ai_summary }}/div divspan{{ item.source }}/span span{{ item.tags }}/span/div /div {% endfor %} /body /html def generate_static(): conn sqlite3.connect(data/news.db) rows conn.execute( SELECT title, url, source, ai_summary, tags FROM articles WHERE processed1 ORDER BY published_at DESC LIMIT 50 ).fetchall() html Template(INDEX_TPL).render(itemsrows) with open(output/index.html, w, encodingutf-8) as f: f.write(html)想直接看动态版就把同样的查询放到 FastAPI 路由里返回一个 HTMLResponse。代码量几乎一样但能支持实时筛选。6.5 定时任务与部署整个流程串起来靠 cron 做定时调度。我在服务器上设置三个任务每 30 分钟跑一次采集每 1 小时跑一次 AI 处理每天早上 8 点生成一次静态首页。cron 配置如下# 每30分钟采集 */30 * * * * cd /home/user/ai_aggregator /usr/bin/python3 collect.py logs/collect.log 21 # 每1小时处理未处理文章 0 * * * * cd /home/user/ai_aggregator /usr/bin/python3 process.py logs/process.log 21 # 每天8点生成页面 0 8 * * * cd /home/user/ai_aggregator /usr/bin/python3 generate.py logs/generate.log 21部署时用 systemd 跑 FastAPI 动态版也行但既然有静态版我就干脆把输出目录直接交给 nginx 托管。整个部署过程下来最花时间的不是代码而是信源筛选——花两天时间收集和测试优质 RSS比写代码更值得。7. 常见问题与排查技巧实录7.1 问题速查表问题现象可能原因解决办法采集时大量重复入库部分源的文章链接会带跟踪参数导致 URL 每次都不同入库前对 URL 做归一化去掉 query 参数中的 utm_source、ref 等跟踪字段AI 摘要输出空内容正文被截断后模型返回异常或正文本身是纯图片页面增加空结果重试逻辑重试一次仍为空就标记为不可处理不要把空摘要写库标签分类越来越乱近义标签累积、模型对冷门文章判断不稳维护标签映射表定期合并近义标签冷门文章默认归入其他某信源突然抓不到对方改版、RSS 地址变更或开始限流监控抓取失败次数连续失败 3 次自动停用并在日志里提醒人工检查处理速度越来越慢未处理记录扫描没有走索引确保 processed 字段有索引量大了改用按时间窗口取未处理记录服务器磁盘被日志撑爆日志无轮转策略用 logrotate 定期切割日志保留最近 7 天即可7.2 独家避坑经验踩过几次坑之后我总结出几条血泪经验。第一条正文清洗的优先级高于一切。AI 摘要的质量完全取决于喂进去的文本干不干净如果正文里混着导航菜单、广告文案、页脚版权信息模型摘要里就会出现一堆莫名其妙的内容。所以清洗环节一定要处理好技术上就在解析时先移除 script、style、nav、footer、aside 这些标签再对剩余文本做空白压缩。第二条prompt 的负面约束比正面要求更有效。我发现单纯说写出要点模型容易输出空话但明确说不要出现值得注意的是这类表述不要输出开场白之后摘要质量立刻提升一个档次。模型对禁止什么的理解通常比对应该怎样更精确这个技巧在所有 AI 工程里都通用。第三条先跑通再优化不要一开始就做编排和重试框架。很多新手会先写消息队列、任务重试、断点续传这些基础设施结果核心功能还没跑起来就被框架拖垮。我的建议是第一版就用简单的 for 循环 try/except错误打日志跑两周看数据质量确认模式稳定后再考虑要不要加重试机制、断点续传、监控告警。对个人项目来说简单到极致才是最好的架构。我自己现在每天早上的固定动作就是打开这个平台花二十分钟把当天最重要的几十条 AI 动态扫一遍。哪些方向在升温、哪个开源项目突然火了、哪篇论文值得精读一眼就能看清。后来我又在它的基础上加了一个每周汇总脚本把本周所有文章按标签聚类自动生成一封AI 周报邮件发给自己。这个平台真正让我体会到的是AI 时代不缺信息缺的是对信息的掌控力——而这种掌控力自己动手搭一套工具就能获得。