ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

用Python搭建AI资讯聚合平台:自动采集、去重与摘要

用Python搭建AI资讯聚合平台:自动采集、去重与摘要 这一两年我身边做技术的朋友几乎都陷进同一个困局AI圈子里的信息实在太多了。早上刷一遍热搜中午刷一遍公众号晚上睡前还要刷一遍论文和社区感觉又是收获满满的一天真到要写东西的时候脑子里能留下的东西寥寥无几。我自己的转折点是有一次想查某个模型的评测对比翻了二十多个标签页发现其中一半是同一篇新闻的转载另一半是二手解读。就是从那天起我决定自己动手搭一个AI资讯聚合平台把采集、去重、摘要、推送全部自动化用工具来解决信息过载而不是靠意志力硬扛。这篇文章就把整套搭建思路、关键代码和踩坑记录完整写出来给同样被信息淹没的开发者、产品经理或者想追踪AI动态的朋友做个参考。我不会讲太高深的东西但每一步都是可以落地复现的。1. 为什么要自己搭信息过载是真实的生产力杀手1.1 看起来在学AI的假象先说一个扎心的观察我们每天刷到的AI内容里真正的新信息占比其实很低。同一篇论文官方博客发一遍机器之心写一篇解读知乎有人翻译一遍微博再转一轮最后你刷到的可能是同一个东西的第五个版本。你以为自己在高效学习实际上是在反复阅读同一件事的不同转述。而AI领域恰恰又是内容密度极高的领域。大模型几乎每个月都有新东西Agent框架、多模态应用、AI编程工具、开源模型迭代每个方向都有人输出内容。我粗略统计过如果关注20个核心信息源一天下来能积累150到200条资讯其中真正值得人眼阅读的可能不到30条。人工筛选这30条的代价是每天至少多花一个半小时。这个账算清楚之后我就确定了一件事必须用一套自动化的管道来替我完成收集和初筛人只负责最后一步的判断和消费。1.2 现成产品为什么总觉得差一口气市面上不缺资讯类产品今日头条、各类科技媒体App、开发者社区的热榜我基本都用过但每个都有明显的短板。聚合新闻App的问题是推荐算法不透明。你越看什么它越推什么看起来省心实际上把你关进信息茧房。比如我关注开源推理框架它就疯狂推推理优化大模型评测方向的优秀内容反而被埋掉了。关注一堆公众号的问题在于碎片化。信息分散在好几个入口里没有统一的已读状态没有跨源的重复检测看一篇忘一篇。手动逛社区的问题在于效率。Hacker News、Reddit、知乎热榜、GitHub Trending每个都要单独打开还要自己用脑子去合并同一件事的多篇报道——这本来就是机器擅长的事情。所以我的结论是市面上缺的不是信息更多而是过滤更好的产品。与其等一个理想产品出现不如自己动手写一个。聚合平台并不复杂核心就三件事把分散的信息源收到一起把重复和低质的内容过滤掉把剩下的精华按我喜欢的方式推送给我。1.3 我给自己定的需求清单动手之前我给自己列了一份明确的需求清单后面所有技术选型都是围绕这份清单展开的自动采集每天定时抓取预先选定的信息源包括RSS、官方博客、社区热榜智能去重同一事件或同一篇文章只保留质量最高的一条AI摘要每条资讯生成一句话摘要和三条核心要点不看原文也能抓住重点分类打标签按Agent、大模型、AI编程、多模态、开源项目、行业动态等维度整理多端触达网页端浏览历史库同时支持RSS订阅输出和定时推送到手机。这些需求并不激进但它们组合在一起刚好能解决我每天面临的实际问题。接下来就是架构选型和数据管道设计。2. 整体架构与数据流先想清楚再动手2.1 技术选型够用就好别一上来就微服务我见过很多人一上来就规划Kafka、Redis、分布式爬虫集群其实一个个人用的资讯平台完全不需要这些。我最终的技术选型非常简单一切以够用、好维护、便宜为标准。组件我的选择为什么开发语言Python 3.11生态成熟数据处理方便RSS和爬虫都有现成库Web框架FastAPI轻量、自带异步支持、接口文档自动生成数据库SQLite个人使用场景写入量不大单文件零运维以后数据量大了可以平滑换PostgreSQL定时调度APScheduler进程内调度不引入额外中间件几行代码就能跑起来RSS解析feedparser解析RSS和Atom的标准库处理多种格式不用自己写解析器爬虫兜底requests BeautifulSoup覆盖不支持RSS的站点requests很轻BS4做DOM解析足够LLM调用OpenAI兼容接口可以灵活切换不同模型实测便宜好用部署一台2C4G云服务器 systemd直接跑Python进程前期不上Docker减少维护负担选SQLite的时候我犹豫过一下后来想通了个人聚合平台一天的写入量也就是几百条SQLite完全扛得住。把简单的事情用复杂的方式做才是最大的风险。2.2 数据管道设计整个平台的数据流是一条单向管道每一步只做一件事边界清晰采集RSS/爬虫 → 标准化标题、正文、发布时间、原始链接 → 去重URL去重标题相似度 → 入库带未读/已读状态 → LLM加工摘要、分类、标签 → 服务Web展示、RSS输出、定时推送这个管道设计有一个核心原则数据是流不是堆。每一层拿到上层的数据处理完丢给下层自己不留多余状态。比如采集层只负责把原始内容变成标准字典去重层只负责判断这条是不是重复不关心内容质量LLM加工层只负责把正文变成摘要和标签不关心这篇存在哪个表里。这样每个环节出现问题都能很快定位和修复。2.3 部署形态脚本优先容器次之个人项目的部署我强烈建议从最朴素的方案开始。我的做法是写一个Python进程里面同时启动FastAPI服务和APScheduler调度器然后用systemd把它托管起来开机自启崩溃自动重启。日志直接写到文件配合logrotate做轮转。Docker很重要但那是给多服务、多环境一致性的场景用的。对我这种单机个人应用少一层抽象排查问题就少一层阻碍。后面如果真需要迁移或分享给别人部署再补一个Dockerfile半小时就能搞定——这个决策顺序比较重要。3. 信息源接入RSS为主、爬虫兜底常识之外的细节3.1 优质信息源清单怎么搭信息源是整个平台的地基。我花了不少时间维护这份清单目前稳定在80个左右按类型分四类类型例子更新频率内容特征官方博客OpenAI Blog、Hugging Face Blog、Google AI Blog、Anthropic等低权威适合做重点跟进行业媒体机器之心、量子位、The Verge AI、InfoQ AI等高覆盖快可读性好论文与开源arXiv cs.AI、Papers with Code、GitHub Trending高原始素材适合深度关注社区热榜Hacker News、Reddit的机器学习板块、知乎AI热榜高讨论度高能看到开发者真实关注点判断一个信息源该不该加我给自己定了几条标准更新频率是否稳定至少每周更新原创内容占比是否高转载过多的源权重降级和已有信息源的内容重叠度是否大重叠度高就不加了。信息源宁缺毋滥80个源每天产出100多条原始资讯经过过滤后剩下30条左右这个摄入节奏对我刚刚好。3.2 feedparser解析RSS的统一处理RSS的解析比想象中容易feedparser一行代码就能搞定但实际写的时候有几个细节需要处理。这是我最开始用的采集函数import feedparser def fetch_rss(url): feed feedparser.parse(url) items [] for entry in feed.entries: items.append({ title: entry.get(title, ).strip(), link: entry.get(link, ), summary: entry.get(summary, ), published: entry.get(published, ), source: url }) return items这里有两个关键点。第一不要只取title和linksummary一定要存。很多RSS源的summary里已经包含了文章的核心内容和关键结论抓下来之后可以直接作为LLM摘要的输入素材省去一次详情页抓取成本和稳定性都好很多。第二published字段的格式五花八门不同源之间的时间格式完全不统一我统一用dateutil的parser去解析解析失败就回退到当前时间并打日志避免一条脏数据卡死整个管道。3.3 爬虫兜底与边界问题支持RSS的源大概只占我关注源的六成。剩下的像一些只有HTML页面的产品博客和社区榜单就得靠爬虫兜底。我用requests加BeautifulSoup抓列表页提取文章标题和链接逻辑不复杂。但爬虫有几个原则必须守住遵守robots.txt这是最基本的礼貌也算是对站点资源的尊重控制抓取频率每个源两次抓取之间至少间隔5分钟宁慢勿快优先抓列表页而不是详情页列表页能拿到标题和摘要就够了详情页又慢又费对方资源。爬虫是兜底手段不能成为主力。我见过有人靠爬虫硬刚几百个站点结果今天这个改版、明天那个加验证码维护成本迅速超过收益。我的经验是能用RSS的坚决用RSS爬虫只用来覆盖少量高价值源数量控制在15个以内。4. 内容去重与质量过滤同样的新闻别刷三遍4.1 为什么去重是聚合平台的灵魂信息聚合做得不好最典型的表现就是重复轰炸。没有去重机制时我统计过一天的原始数据arXiv上同一篇论文会出现在Paper Digest、多个微信公众号转发、Reddit讨论串里衍生出七八条记录。如果不去重就算AI摘要做得再好推送给我的依然是七八条内容几乎相同的新消息。去重是信息质量的第一道关卡这道关卡垮了后面的所有加工都白费。4.2 第一层URL和标题归一化去重的第一层也是最简单的一层是URL去重和标题归一化。URL去重很直接很多站点会在链接后面挂跟踪参数utm_source、from、spm之类同一个页面会生成不同的URL入库前统一去掉这些参数取canonical形式。标题归一化则要处理大小写、标点、空白字符的差异import re import hashlib def normalize_title(title): title title.lower() title re.sub(r[^\w\s], , title) title re.sub(r\s, , title) return title.strip() def title_hash(title): return hashlib.md5(normalize_title(title).encode()).hexdigest()同一篇文章的转载标题一般会有细微差异归一化之后大多能直接命中。这一层能拦截掉大概五成重复内容成本几乎为零。4.3 第二层相似度去重归一化挡不住标题党式的改写比如原文叫Anthropic发布Claude 3.7转载改成Claude新版来了编程能力大幅提升标题哈希就完全对不上。这一层我用了经典相似度算法from difflib import SequenceMatcher def similarity(a, b): return SequenceMatcher(None, a, b).ratio() def is_duplicate(new_title, recent_titles, threshold0.85): for stored in recent_titles: if similarity(normalize_title(new_title), stored) threshold: return True return False具体思路是每篇新入库的文章跟最近7天内的已入库标题逐一比较相似度超过阈值的判定为重复保留来源权重高的那一条另外一条沉入库底。这个方案在大约90%的场景下表现很好而且不需要额外的算力开销。4.4 LLM语义去重兜底相似度算法有个盲区标题改得面目全非但讲的是同一件事。比如A公司与B公司达成合作变成重磅两大巨头联手字符级别几乎没有重合但语义完全一致。这类重复光靠编辑距离挡不住。我的兜底方案是让LLM做语义去重。具体做法每隔几小时把最近24小时内通过相似度过滤后剩余的标题打包发给模型一次给10到20条让它输出其中语义重复的分组编号。这种处理成本很低一次调用的费用几乎可以忽略但能把相似度算法的漏网之鱼捞回来不少。值得提醒的是LLM去重不适合放在主链路上。如果每来一条新文章都调一次模型延迟和成本都不可控。它应该是批处理任务挂在管道尾部定时跑一轮就好。4.5 质量过滤规则去重后还有一道质量过滤关卡。我的规则很简单但每一条都来自实际经验黑名单词过滤标题或摘要里出现广告推广抽奖限时优惠的直接丢弃长度门槛摘要少于50字的文章基本没有信息量直接丢弃来源权重官方源权重最高行业媒体次之聚合转载类源最低。重复时保留权重高的时效性发布时间超过3天的内容不再进入推送池只看很老的深度文章时去历史库手动查。5. 大模型参与的资讯加工摘要、分类与每日简报5.1 摘要Prompt的设计思路原始资讯进入数据库之后下一步就是让大模型参与加工。第一步是生成摘要。我给模型设定的角色不是总结机器人而是资深AI编辑——这个角色定位的差异直接决定了输出质量。你是一位资深的AI领域编辑。请阅读以下资讯提取核心信息输出 - 一句话摘要30字以内说明这条资讯的核心事实 - 核心要点3条每条不超过50字覆盖关键信息 - 涉及技术点用逗号分隔如 Agent, 大模型, 推理优化 - 适合读者从开发者/产品经理/研究者/大众中选择 资讯标题{title} 资讯正文{content}这个Prompt里最关键的约束是格式。结构化输出放进JSON里后面分类、推送、展示都靠这些字段。要注意摘要不是把正文缩句而是要回答这件事为什么值得关注。比如一篇模型评测文章摘要应该写某模型在代码生成任务上超过了之前最强的开源模型但推理速度慢了一倍而不是某模型发布了一篇评测文章。5.2 分类与打标签规则LLM混合策略分类我一开始想过纯靠LLM做下来发现成本偏高而且速度不稳定。后来改成了规则优先、LLM兜底的混合策略。规则层逻辑很简单标题里出现Agent就归入Agent类出现多模态就归入多模态出现融资/收购就归入行业动态。这条规则能覆盖大概六成内容速度快、免费。剩下的四成比如这家创业公司发布了新一代推理模型这种没有明确关键词的才交给LLM分类。实测下来混合策略的准确率和纯LLM方案几乎一致成本却降了六成多。同样的思路也用在标签生成上热门标签由规则命中冷门标签由LLM补充。5.3 每日早报的生成逻辑聚合平台最让我省心的功能是每天早上固定时间推送的今日早报。它不是因为每天推送一条消息这个形式而存在而是因为它在推送前做了很重要的信息压缩。早报的生成逻辑是先取出过去24小时入库的文章按分类筛选出权重最高的前10篇把这10篇的标题、摘要、链接拼接成一段上下文然后让模型生成今日最值得关注的5件事每件事附带一句话点评。这个点评不是复述摘要而是给出一些背景信息或者点出让读者注意的角度。def generate_daily_report(items): context \n.join( f{i1}. {item[title]}\n{item[summary]}\n{item[link]} for i, item in enumerate(items[:10]) ) prompt f你是一位AI领域的资深编辑。基于以下今日资讯挑选最值得关注的5条 每条写一句简评不超过40字简评要有信息增量不要复述标题。 按重要程度排序输出。 资讯列表 {context} # 调用LLM接口 return call_llm(prompt)早报生成放在每天早晨8点生成完直接推送到手机通勤路上就能读完一天最重要的AI动态。6. 展示与触达网页、RSS、推送三管齐下6.1 轻量级Web展示推送适合看今天有什么但要回溯上周那篇讲Agent记忆机制的文章还是需要一个网页端历史库。我用FastAPI加Jinja2模板做了一个极简页面按分类展示带未读/已读状态和关键词搜索没有上任何前端框架。from fastapi import FastAPI, Request from fastapi.templating import Jinja2Templates app FastAPI() templates Jinja2Templates(directorytemplates) app.get(/) def index(request: Request): items get_items_by_category(all) return templates.TemplateResponse(index.html, { request: request, items: items })页面不需要好看需要的是高效。我的页面核心就是三列标题、分类、摘要。标题可点击跳转原文摘要展开能看到AI生成的要点。够用了。6.2 让平台重新输出RSS和JSON有个细节我强烈建议做聚合平台本身要重新输出RSS和JSON接口。这样平台消费的就不是最终产品而是睡了一觉醒来的清洗后数据可以用任何RSS阅读器继续订阅也能被其他脚本二次处理。RSS输出用Python的feedgen库就能实现。JSON接口更简单FastAPI返回dict即可。这个功能最大的好处是解耦你的展示端和推送端可以被任意替换数据资产的开口掌握在自己手里不会被任何平台的界面逻辑绑架。6.3 定时推送选对渠道别每天轰炸推送渠道我前后对比过几个最终稳定用三条微信Server酱、邮件用于存档、企业微信Webhook给团队共享用。推送到平台的节奏也要克制。我最后固定为每天3个时间点时间内容渠道08:00每日早报5条重点简评微信12:30午间快讯5条短讯微信21:00晚间精选当天最值得深读的3篇邮件存档刚开始我也试过来一条推一条结果半天就把自己推烦了直接关掉通知。资讯推送的本质是在合适的时间给你合适的信息频率太高等于没有频率。7. 上线后的调优与踩坑记录7.1 大模型API成本的坑我上线第一周就差点把API额度烧没原因是所有资讯的长摘要都用最强模型跑。后来改成分级策略普通资讯用便宜的基础模型生成短摘要和标签只有被规则标记为重要的内容才用更强的模型精读每日早报单独用最强模型生成一次。这个策略落地后每月API费用直接降了一个数量级。另外一个容易忽略的调优点是缓存。同一篇文章如果被多个源转载去重后虽然只保留一条主记录但摘要结果可以按原文hash缓存避免重复调用LLM。7.2 APScheduler调度器的两个坑APScheduler用起来简单但坑也不少。第一个坑是时区我一开始没指定时区调度任务默认跑在UTC时区结果每天早晨8点的早报凌晨4点就到了。解决方式是在初始化调度器时显式指定时区from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger scheduler BackgroundScheduler(timezoneAsia/Shanghai) scheduler.add_job( generate_daily_report, CronTrigger(hour8, minute0, timezoneAsia/Shanghai) )第二个坑是重复触发。服务器重启后如果旧进程没被完全杀掉新老两个进程会同时跑同一个任务导致推送重复。我用systemd托管后在启动前加了一条pkill旧进程的命令这个问题就再也没有出现过。7.3 去重阈值的两难去重阈值是个既要又要的问题。阈值设太高相似但不同的文章被误杀设太低漏掉真实重复。实测下来不同内容类型的表现差异很大新闻类标题结构接近0.80以上比较合适技术博客类的标题千变万化0.75左右才能兼顾召回和准确率。我的最终方案是给不同的信息源类型配置不同的去重阈值而不是全局统一。官方源的内容本来就独特阈值可以设到0.85媒体转载活跃的源降到0.75。7.4 RSS源静默失效的排查经历这是最隐蔽的一个坑。feedparser遇到失效的RSS源不会报错它只是解析出一个空feed你的程序看起来正常运转实际上这个源已经连续一周没有新内容了。我一度以为某个技术媒体停更了后来检查日志才发现是它把RSS地址的路径改了我却浑然不觉。我的解决方案是加一个心跳检测任务每个源连续3次抓取结果为空就自动标记为失效推送一条告警给管理员。这样从某个源失去关注到被发现失效最多延迟一天。7.5 时间解析与编码问题不同RSS源的时间格式差异大我的处理是用dateutil的parser统一解析对实在解析不了的字段记录原文和异常堆栈然后回退当前时间。注意别让回退时间静默发生一定要有日志否则以后排查时间错乱的数据时完全没有线索。编码问题是另一个容易被忽略的点。部分老站点的RSS没有声明编码feederparser解析出来的中文会乱掉。我在采集层会先做一次编码检测给requests的response对象显式设置encoding能解决九成乱码问题。我自己在实际运维这套平台的过程中最有感触的一点是聚合平台最难的不是代码而是信息源的持续维护。前两周我几乎每天都要手动调整信息源的黑白名单把总是产出低质内容的源降权或移除把质量稳定的源提权。用了大约一个月信息流质量才进入稳定的状态。现在这套平台每天只花很少的API费用却能稳定地为我过滤出真正值得关注的内容。如果你也想做一个类似的东西我有一个小建议先别急着写代码花两三天维护一份你真正信任的信息源清单跑通流程之后再逐步用自动化替代手动操作。工具可以一步步升级但过滤信息的思路才是整个体系的核心。
返回列表