ARTICLE DETAIL

资讯详情

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

AI Agent新利器Last30Days:破解搜索时效性难题的30天信息抓取实战

AI Agent新利器Last30Days:破解搜索时效性难题的30天信息抓取实战 做AI Agent开发这段时间我遇到一个特别头疼的问题很多实时信息传统搜索引擎根本搜不到或者说搜到的已经晚了。竞品凌晨更新了官网舆情平台下午才有结果等到普通搜索能查到时黄金操作窗口基本已经没了。这个痛点催生了一批“面向时效性的信息抓取工具”Last30Days-skill就是其中一个很典型的开源项目。它以“技能包”的形式把最近30天的信息发现、抓取、评分和输出串成一条完整流水线核心目标就是跨越传统搜索引擎的信息壁垒把那些尚未被主流索引覆盖的新鲜内容提前拉到你的桌面上。我花了两周时间啃源码又跑了几个真实case这篇文章把它的架构设计、信号评分体系和实战用法一次说透适合AI Agent开发者、舆情监测工程师以及所有被“搜索时效性”折磨过的人。1. 项目概览与设计初衷1.1 为什么偏偏是“最近30天”传统搜索引擎的收录逻辑决定了它对“新内容”天然不友好。一个网页从发布到被爬虫抓取、清洗、入库、排序中间往往隔着几个小时甚至好几天就算进入了索引新页面在PageRank之类的长期权威信号上也几乎拿不到什么权重。所以你会发现想查“上周发生的事”搜索结果里翻来覆去都是老文章、综述页和SEO农场。信息传播本身有一条很清晰的周期曲线事件发生后的几分钟到几小时内只有原始信源在更新比如官网新闻、社交平台账号、Telegram频道、RSS源几个小时后垂直媒体和论坛开始讨论再过一两天部分新闻聚合站会跟进而搜索引擎开始给出像样的结果通常是24小时到72小时之后长尾页面可能要更久。这条曲线决定了“最近30天”正好是传统搜索最薄弱的窗口也是信息不对称最严重的阶段。Last30Days-skill把这个窗口做成了一等公民。它不试图替代搜索引擎而是专注解决一个问题在信息刚出现、尚未被大范围索引的时候把这些信号尽可能完整地捞回来。30天不是一个拍脑袋的数字它是“足够覆盖绝大多数业务场景”的时间边界。竞品动态监控、开源社区新技术发布、漏洞情报跟踪、行业政策变化这些场景的信息有效期往往就在几天到几周之间抓30天数据再按评分截断既不会漏掉长尾重要事件也不会被无限期的历史信息干扰。1.2 Last30Days-skill 到底解决了什么问题说直白一点它解决的是“传统搜索看不见新东西”的问题但手段不是去跟搜索引擎抢排序而是换一条路绕过索引层直接去信源端捞数据。这个项目以技能包的形式存在意味着它可以很方便地接入到AI Agent的工作流里让大语言模型不再只会调用普通搜索API而是拥有一个专门的“时效性雷达”。我试用下来的感受是它和传统搜索的差异非常明显。普通搜索返回的是按权威度排序的全网混合结果而Last30Days-skill返回的是按时间衰减和信号强度排序的近期信息字段更结构化包括标题、URL、发布时间、来源类型、互动数据和综合评分。对Agent来说这种结构化输出比一段搜索引擎结果摘要更容易做推理和筛选。这个项目还特别适合两类人。第一类是Agent开发者你不需要自己写一堆RSS解析器和去重逻辑直接把它作为工具注册进去就能用第二类是舆情和情报分析工程师你需要的是“新鲜度优先”的信息流而不是“权威度优先”的搜索结果。当然如果你想用最少的代码搭一个自己的新消息提醒系统它同样是个很好的参考模板。2. 项目整体架构拆解2.1 五层架构从信号源到输出Last30Days-skill的整体架构并不复杂但分层做得非常清楚。整个系统从下往上分成调度层、采集层、解析层、评分引擎和存储输出层每层之间通过定义好的数据结构通信互相不耦合。我最初看代码时有点意外这种小型技能项目通常喜欢在一个文件里把所有逻辑写完但这个项目明显是按“可维护”的标准设计的。各层职责和关键设计如下表架构层主要职责关键组件设计要点调度层定时触发抓取任务控制频控与去重任务队列、时间轮调度器支持按信源设置独立抓取频率避免高优源被低优源拖累采集层从不同信源获取原始数据Source Adapter、异步HTTP客户端每种信源一个适配器RSS/API/页面抓取统一出口解析层抽取正文、标准化时间、识别实体正文提取器、实体识别模块时区统一转为UTC页面型信源走可读性提取评分引擎计算时效分、权威分、互动分、内容质量分、相关度信号评分器、归一化模块权重可配置所有信号先归一化再加权存储与输出层保存结果并提供查询接口SQLite/Redis、CLI、Webhook默认本地零配置也支持通过Redis做增量状态同步解析层是最容易被低估的一块。很多做类似项目的人把大量时间花在配置信源上却忽略了页面结构会变、时区会坑人、时区解析错误会导致评分直接失效。Last30Days-skill在解析层做了一个很聪明的决策凡是支持RSS或官方API的信源优先走结构化数据只有结构化数据缺失时才回退到HTML正文提取。这样既保证了解析效率又顺便降低了反爬风险。2.2 数据流向与关键机制整个系统的数据流是一条清晰的单向管道理解它你就理解了项目的核心设计。调度器在每个时间片触发任务后采集层对应适配器开始抓取原始数据得到HTML或JSON后交给解析层解析层把数据规范化为统一的Document结构并抽取正文和发布时间随后进入去重模块再交给评分引擎打分最后写入存储并触发Webhook通知。去重是这个项目里很值得研究的一个点。它没有用简单的URL去重而是采用“URL指纹 内容SimHash 时间窗”三重判断。为什么不用精确哈希因为同一件事在不同信源上有不同表述标题可能改了正文可能换了一段但核心句子还是相似的。SimHash能把这种近似重复识别出来配合URL指纹可以快速排除大量转载内容。我给一个生活化类比URL指纹相当于认身份证号SimHash相当于看脸两者结合才能拦住“换了身份证号但脸没变”的搬运号。存储层在这个项目里被刻意做轻了。它不依赖Elasticsearch这类重型搜索引擎默认就是SQLite按时间倒排关键词过滤用简单索引。这个选择背后的逻辑很现实30天窗口的数据量本质上很小如果每个信源每小时的更新量只有几十条一天下来也就几万条SQLite完全可以胜任。引入太重的外部组件反而会让部署门槛陡增违背了“skill包随手就能跑起来”的初衷。2.3 为什么“轻松跑起来”如此重要我见过不少开源项目功能很强但环境依赖能把人劝退。Last30Days-skill把存储和调度都做成“可以零配置启动”的设计是它能在实战中快速落地的重要原因。项目默认SQLite调度不用Celery而是用进程内的时间轮调度器。这意味着你在一台普通服务器上装好Python依赖就能立刻跑起来做验证而不需要先搭一套分布式系统。当然设计者们也留了后路。如果你有多个实例同时采集或者需要共享去重状态可以切换到Redis后端把调度状态和增量状态集中管理。这种“先用简单方案跑通再按需升级”的思路我特别认同。很多时候开发者容易一上来就想着高可用、分布式结果复杂度和收益完全不成比例。先用单体把流程跑通观察实际数据量再决定要不要加Redis这才是更务实的路线。3. 信号评分体系让“新鲜”变得可度量3.1 五个核心评分维度拆解信号评分体系是整个项目最有含金量的部分。它解决一个核心问题同一时间窗口内抓到的信息可能有很多条到底哪一条更值得被优先看到如果只按时间排序那些互动高、来源可靠但稍早一点的信息会被完全淹没如果只按来源排序又会让权威网站刷屏。项目用五个维度加权试图在“新”“准”“热”“优”之间找一个平衡点。默认配置下五个维度大致如下评分维度默认权重范围信号来源说明时效性0.30 - 0.40发布时间与抓取时间的差值时间越短分越高采用指数衰减来源可信度0.15 - 0.20信源类型、域名历史、人工标注官方网站和一手信源基础分高互动热度0.15 - 0.25点赞、转发、评论、浏览计数做对数归一化防止极端值垄断内容质量0.10 - 0.15正文长度、结构化数据、标题规范压制低质拼接和标题党相关性0.10 - 0.20与查询关键词/兴趣画布的语义匹配度默认用BM25可扩展embedding这个体系最聪明的地方是它没有把“时效性”简单当作一个0/1特征而是建模成连续衰减函数。我在很多类似项目里见过一种错误的做法只筛选最近30天的数据然后按时间倒序排序完全不考虑内容质量。结果就是每天抓到一堆垃圾。Last30Days-skill的高分条目通常不是最新的一条而是“足够新且足够有信号”的一条。3.2 衰减模型与归一化为什么是指数衰减项目的时效性计算公式是fresh_score base_score * exp(-lambda * age_hours)其中base_score是满分100age_hours是条目发布后经过的小时数lambda是衰减系数。默认场景下lambda会根据内容领域自动调整事件型、突发型内容lambda取0.02左右意思是经过35小时时效分跌到一半常规技术内容lambda取0.005意味着几天前的优质文章依然能保持较高分数。为什么选指数衰减而不是线性衰减因为信息价值的下降并不是匀速的。一个突发新闻发出后前几个小时价值极高之后快速贬值一两周后基本只剩考古意义而一篇教程的价值下降则慢得多它可能在一个月内仍被持续阅读。指数衰减正好能拟合这种“先陡后缓”的曲线。线性衰减会让“昨天的事件”和“十天前的事件”差距不够大产生误判。互动热度归一化也值得一提。项目没有直接用原始点赞数参与加权而是用log(1x) / log(1max_n) * 100把互动量压在0到100区间。为什么要取对数因为社交网络的数据极度偏态绝大多数内容互动量在个位数少数爆款是几万甚至几十万。如果用线性归一化普通内容的互动分会被压到1以内等于这个维度废掉了取对数之后0到2000之间的差异仍然清晰2000到20万的差异被压缩这更符合人对热度的感知。3.3 一次完整的评分计算演示只看公式还是不够直观我拿一个实际case来演算。假设查询主题是“AI Agent”抓到一个条目来自某科技媒体发布时间是12小时前来源可信度85分转发35次评论8条正文长度800字语义相关度0.7。喂给评分引擎默认权重取时效0.35、来源0.20、互动0.20、内容质量0.15、相关性0.10。时效分先算100 * exp(-0.02 * 12) 78.7加权后贡献27.5分。来源分85直接乘0.20贡献17分。互动数据合并为43次假设当前时间窗内最高互动是200次那么互动分是log(44)/log(201) * 100 ≈ 62.9加权后贡献12.6分。内容质量分是78分乘0.15贡献11.7分。相关度70分乘0.10贡献7分。最终综合分是76.8分。这个76.8分看起来是“还不错”的水平但光看总分还不够项目会把各维度分项也输出。为什么因为Agent消费这些数据时不同业务关注点不同做舆情预警的更看重时效性和互动热度的组合做技术调研的更看重来源可信度和内容质量。把分项全部暴露出来比只给一个综合分要灵活得多。3.4 评分反馈回路与调参技巧评分体系不是一锤子买卖。项目内置了一条简单的反馈回路如果用户对某条结果做了“收藏”或“点击查看原文”系统会记为正样本并小幅上调该来源和该关键词相关权重如果“忽略”则不做调整或微降。这种线上反馈机制让评分引擎能在运行一段时间后逐渐贴近你的真实偏好。我实际调参时发现了一个很重要的技巧不要急着改权重先看数据。把一周跑出来的结果导出成表格人工标记哪些条目是真正有价值的然后反推当前权重错在哪里。如果高分条目集中在“高转发但低质量”的爆款上那就调低互动热度权重并在内容质量维度增加标题夸张度惩罚如果权威站的老文章排名一直下不来那就加大时效性权重。权重调整的本质不是追求一个绝对正确的公式而是让排序结果符合你所在领域的业务直觉。4. 实战使用指南快速跑通Last30Days-skill4.1 环境准备与安装我推荐在干净的Python 3.10以上环境里运行最好先用venv建一个虚拟环境避免和系统依赖冲突。项目仓库clone下来后安装依赖非常直接只需要执行pip install -r requirements.txt它会自动装好异步HTTP客户端、RSS解析库、HTML正文提取库和YAML配置解析库。初次启动建议不要急着连Redis。先用SQLite模式跑起来因为配置最简单不依赖外部服务也方便随时看数据。项目默认会生成一个data/目录里面存放SQLite数据库和抓取状态文件。等确认整个流程能正常工作、数据量也开始增长之后再考虑把状态存储切到Redis实现多实例间的去重和调度协调。环境上还有一个要注意的点抓取境外信源时服务器的网络位置会直接影响抓取成功率和延迟。这不是项目本身的问题而是网络环境问题。我的做法是在部署节点选择上尽量贴近目标信源所在区域同时把单个信源的请求频率控制在几十秒一次这样对目标站点比较友好抓取稳定性也会高很多。合规方面只采集公开信息、遵守目标站点robots声明和服务条款是底线。4.2 核心配置一份可以直接抄的YAML项目的核心配置集中在config.yml。我把自己用得比较顺的一份配置贴出来并解释每一块的含义time_window: days: 30 sources: - type: rss name: techcrunch url: https://techcrunch.com/feed/ enabled: true - type: hackernews name: hn max_items: 50 enabled: true - type: github_releases name: gh_releases repos: - openai/evals enabled: true scoring: timeout_decay: 0.01 weights: freshness: 0.35 authority: 0.20 engagement: 0.20 quality: 0.15 relevance: 0.10 storage: backend: sqlite path: ./data/last30.db state_file: ./data/state.json notify: webhook_url: min_score: 70time_window.days是主窗口项目所有评分和去重都围绕这个窗口展开sources里每个信源都有独立的启用开关方便临时下线故障源scoring.timeout_decay对应前面公式里的lambdanotify.min_score默认设为70意思是只有综合分超过70的条目才会通过Webhook推出去避免Agent被低质量信息刷屏。这里有一个容易踩坑的点time_window.days不是越大越好。窗口越大去重集合越大评分时的计算量也越大而且老条目累积多了会影响“新鲜内容”的占比。我自己的经验除非要做趋势分析否则日常监控保持在15到30天就够。真需要长周期数据可以另外做离线聚合而不是让实时抓取的窗口无限膨胀。4.3 命令行与Agent接入项目提供了一条简单的CLI命令来触发一次手动抓取last30days run --query AI Agent --hours 72 --format json --min-score 60这个命令的含义是抓取最近72小时内与“AI Agent”相关的信息只输出综合分超过60分的JSON结果。手动触发在调试信源时非常有用你可以随时跑一次看看新配的源能不能正常解析评分是否合理而不需要等定时调度。输出结果是标准的JSON数组每条记录包含title、url、published_at、source、score以及各个维度的分项分数。字段设计得相对精简目的是方便Agent直接消费。比如你可以把系统提示词写成“当用户需要了解AI Agent的最新动态时优先调用last30days工具并基于返回的score字段判断信息可信度”。接入AI Agent的方式也很灵活。项目支持把抓取结果通过Webhook推送到你的Agent事件流里也支持作为类似MCP协议的工具被动态调用。我的做法是注册成功一个工具函数LLM在回答时效性问题时自动触发一次实时抓取再把结果和原始URL一起交给LLM做摘要。这样既保证了信息新鲜又让LLM可以引用来源减少幻觉。4.4 自定义信号源半小时接入一个新的数据源项目最让我满意的是扩展信号的流程非常规范。每个数据源都继承同一个Adapter基类你只需要实现两个方法一个是拉取原始数据另一个是把原始数据转换成统一的Document结构。class MySourceAdapter(SourceAdapter): source_type my_source async def fetch(self, params): response await http_client.get(self.config[url]) return response.json() def parse(self, raw): return Document( titleraw[title], urlraw[link], published_atnormalize_time(raw[published]), contentraw[summary], metadata{source: my_source} )就这么简单。最难的部分反而是理解目标信源的数据结构。比如有的RSS源把时间写成RFC822格式有的JSON API返回的是Unix时间戳还有的HTML页面需要你先观察一下正文节点再写提取规则。只要把原始数据看明白、把时间标准化处理好新适配器半小时内就能跑通。我在接入GitHub Releases时整个过程包括写适配器、跑测试、调解析约半小时接入一个结构比较奇葩的论坛源第一版用了两小时主要时间花在处理类Unix时间戳的时区偏移上。5. 踩坑记录与排查技巧5.1 搜不到“最近30天”内容的三种原因我刚开始用的时候最困惑的问题是明明信源里有内容为什么项目输出空白后来排查下来原因往往不是抓取失败而是三个隐藏问题。第一是时区处理错误。很多信源返回的时间不是UTC而是本地时间。如果适配器没有做时区标准化项目会把你本地的“晚上8点”当成UTC的“晚上8点”然后往前推8小时导致发布时间被算成未来时间于是被时效性过滤掉。排查办法很简单随便找一条原始数据对照它在信源页面上显示的时间和数据落库的时间差8小时就是时区没处理对。第二是去重模块误伤。同一个事件被不同信源转载SimHash相似度超过阈值后会被当作重复内容丢弃。这在大多数情况下是好事但如果主信源被屏蔽或者内容被删前一个版本已经入库后一个质量更高的版本就没机会露脸。我的做法是把“详情URL”也做为输出候选即使内容重复也保留最新抓到的URL。第三是评分阈值卡得太高。如果min_score设成80但当前信源的互动数据普遍很低几乎所有条目都达不到阈值。这时候要先观察一下原始分项看看是哪个维度在拖后腿。如果所有源都低分大概率是时效衰减系数设得太陡或者互动归一化的分母当前窗口最大互动数被某个爆款抬得太高。5.2 信源限流、页面结构调整与稳定性反爬和限流是采集类项目躲不开的问题。项目自带限速队列和随机User-Agent但当你把多个信源放在同一个进程里跑的时候某些网站还是会在短时间密集请求后返回403或者验证码页。我遇到过一次HackerNews接口被限流原因是调度间隔设成了1分钟而对方允许的配额是每分钟30次请求虽然没超过但因为和其他源共享IP还是触发了风控。处理这类问题我的经验是“优先生态友好型数据源”。优先配RSS、官方API、GitHub Releases、arXiv这类提供结构化接口的信源它们的限流策略清晰解析也稳定。对必须抓HTML的信源一是把请求间隔调到5秒以上二是做好指数退避重试三是对每个信源做独立健康检查。如果连续三次抓取失败就自动暂停该源并告警避免整个任务卡死。还要预防页面结构调整。HTML正文提取依赖CSS选择器或XPath站点一旦改版选择器可能一夜之间失效。目前项目的处理方式是如果正文提取结果为空会自动降级到通用可读性算法如果通用算法也拿不到就把整段HTML截断后作为标题备注输出。这样至少不会漏掉URL只是内容质量分下降等到你更新解析规则后再次抓取就能恢复。5.3 评分总是不准先调数据再调权重评分不准是使用过程中最常见的抱怨但大多数时候问题不在权重公式而在输入数据不干净。比如某个源把发布时间错填成“文章最后一次修改时间”导致一篇三年前的老文章每次被编辑都会获得“新时间”时效分虚高。这个坑特别隐蔽我在抓某个开源项目的Release时遇到过版本历史页面显示的时间其实是上架时间不是当前版本的发布时间最后只能靠“对同一URL的历史评分做差分”才发现异常。如果确认数据没问题再考虑调权重。爆款标题党分数偏高时调低互动热度权重同时增加内容质量维度的“标题夸张度”惩罚项权威源旧文章一直压制新信息时把时效性权重拉到0.4左右把来源可信度上限从100调到95。每次只调一个维度观察一天数据不要同时改三个权重不然你根本无法判断是哪个改动起的作用。6. 扩展思路与个人经验6.1 从30天窗口扩展到长周期趋势既然评分体系已经把时间衰减做成连续函数把窗口从30天扩展到90天甚至更长其实只需要调整配置。但需要注意长周期下“互动热度”的意义会变得不一样一个发布后一小时互动500的内容和一个发布后一周互动5000的内容价值完全不同。项目目前的处理办法是在保存评分时同时记录“抓取时的分项分数”而不是只存一个最终分。这样你就能按历史分回溯看一条内容在不同时期的新鲜度变化用来做趋势分析。我自己做过一个试验把某个技术关键词的评分记录存了90天然后按月聚合每天的最高分条目最后得到了一条“这个话题什么时候开始变热”的曲线跟人工翻阅的结果基本吻合。这说明评分体系不仅能用来做实时筛选也能用来反推信息的时间线。6.2 我自己的几条部署体会最后分享几个纯经验层面的东西。第一不要一上来就接几十个信源。先用三到五个高质量信源跑一周把解析、评分、通知流程全部走顺再慢慢加源。源越多出问题的概率越高排障成本也越高。第二SQLite模式真的够用。我在日均抓取两万条左右的情况下SQLite查询和写入都没有成为瓶颈真正需要优化的反而是抓取调度的频控逻辑。第三Webhook通知比轮询好用得多。给Agent加一个“有新消息才唤醒”的机制比让它每隔几分钟主动拉一次要节省大量token和时间。这类工具最终的价值不在于抓了多少条数据而在于筛出了几条真正值得看的信息。评分体系的本质是在“新”和“有用”之间反复折中而这个折中只能靠真实数据来校正没有任何一个默认配置能适配所有场景。花几天时间把日志、评分分项、人工判断关联起来看远比临时改权重更有效。这大概是这个项目给我最大的启发。
返回列表