资讯动态

OneSearch实践:多源聚合搜索中的去重排序与适配器设计

发布时间:2026/10/23 8:23:23 来源:尧图企业网站定制
经常做技术调研的人应该都有这种经验一个问题往往要在四五个不同的地方分别搜索官方文档看一遍社区帖子翻一遍代码仓库的 issue 再查一遍最后还要去博客平台碰碰运气。时间花了不少结果往往是同一篇文章被重复看到好几遍。我就是受不了这个来回切标签的流程才动手写了一个聚合检索的小工具代号叫OneSearch。它做的事情很简单只留一个搜索框输入关键词以后同时去多个目标源检索把结果合并、去重、排序后统一展示。这篇内容我打算把 OneSearch 从需求拆解到技术实现完整复盘一遍。如果你也有多平台重复检索、结果分散、入口混乱的痛点或者想做一个类似的个人工具这篇应该能帮你少走不少弯路。里面会涉及产品形态的取舍、架构设计、适配器模式、去重排序策略、前端交互细节以及我实际运行几个月后踩过的坑。1. 项目定位与需求拆解1.1 最初的需求模型为什么需要一个搜索框做技术类内容时我每天的检索路径基本是固定的遇到一个报错先去文档站确认 API 用法再去技术社区看有没有人遇到类似问题接着去代码仓库搜相关 issue最后可能还要到博客站找更深入的分析文章。这个流程有三个很明显的问题。第一入口太多。每个站点的搜索框风格、快捷键、翻页逻辑都不一样切来切去非常容易断思路。第二重复劳动。同一个问题在不同站点搜出来结果大量交叉经常是同一篇内容换个 URL 又出现一次人工去重很费神。第三排序标准不统一。有的站点按时间排有的按热度排有的按相关度排落在同一个任务里根本没法横向对比。所以 OneSearch 的核心需求模型其实只有三条统一入口、聚合结果、去重排序。它不打算替代任何一个站点的完整搜索能力只解决我第一轮想看什么的问题。这个定位很重要因为一旦想多了工具就会变成另一个搜索引擎项目范围会迅速失控。1.2 产品形态的取舍Web 服务比浏览器插件更合适需求清楚以后摆在面前的是产品形态选择。我认真比较过四种方案。形态优点缺点适用场景浏览器插件调用方便可读取当前页面上下文需适配多个浏览器权限和自动更新麻烦重度单机用户独立 Web 服务设备无关手机电脑都能用前后端分离清晰需要自己部署和维护个人工具、团队小工具命令行工具轻量适合脚本化调用交互体验差不适合日常浏览式检索自动化流程桌面客户端启动快系统集成好打包分发成本高跨平台工作量大需要离线能力的场景我最后选了独立 Web 服务而且是跑在 localhost 或局域网里的那种轻量服务。原因很实际我个人需要在手机和电脑之间切换使用写代码的时候在电脑前随手拿起手机也想查一下刚才搜过的东西。Web 服务天然跨设备不用处理浏览器插件的多端兼容问题后端逻辑也能独立演进。这个选择还带来一个额外好处因为前端只是一个单页后端完全通过接口提供检索能力后面想做命令行调用、自动化脚本接进来都非常容易。2. 技术选型与整体架构设计2.1 前端尽量轻量化不折腾框架OneSearch 的前端我没有上重型框架就用原生 HTML、CSS 加少量 JavaScript。原因有两个。第一这个页面本身不复杂。核心就是一个输入框、一个结果列表、一个设置面板。第二我希望搜索结果几乎瞬时呈现少一层框架就少一分加载和解析开销。实测下来首屏从打开到可以输入基本没有等待感。前端的主要逻辑集中在几个点输入防抖、请求合并、结果渲染和键盘导航。防抖用的是很简单的 300ms 延时避免每次敲键都向后端发请求。请求合并则是把用户连续输入但还没来得及执行搜索的状态统一取消只保留最后一次关键字请求。渲染层没有用虚拟滚动因为我把每个源的结果上限做了限制一次最多展示几十条原生 DOM 操作完全够用。交互上有一条很重要的原则不要让用户等待所有请求完成才看到结果。后端是分批返回的前端收到一个源的数据就先渲染一个区用户会感觉到搜一下内容一条条出来而不是转圈半天。2.2 后端检索核心与调度机制后端我选了 Python。选它不是因为性能最好而是因为做网页解析、文本处理这套生态非常成熟写适配器的效率很高。OneSearch 本质上是一个检索调度器它对性能的要求不在于单次请求多快而在于并发调度和容错处理是否稳定。每个检索源对应一个适配器适配器向后端返回统一结构的结果。它的核心字段大概是这样的{ source: docs, title: 页面标题, url: 原始链接, snippet: 摘要内容, score: 0.96, published: 2024-01-15 }后端收到前端的搜索请求后会遍历所有已注册的适配器并发发起检索。并发这里不是简单开几个线程就完事我给每个源都设置了独立的超时时间默认是 5 秒。这个时间非常关键如果某个源的接口突然变慢不能让它拖垮整个页面的响应。超时的源直接返回空结果前端会有对应的提示。同时后端有一个全局的信号量控制同时跑多少个源的请求避免一次性把本机网络连接打满。信号量的默认值我设为 4对于个人使用来说已经能保证大多数场景的响应速度同时不会让某个目标站点觉得你在猛烈抓取。2.3 数据去重与排序策略聚合检索最大的麻烦不是把结果凑到一起而是把结果整理得干净、可读。去重我做了三层。第一层是 URL 规范化。很多站点会给链接加跟踪参数、排序参数有的还会把协议从 http 跳到 https。我会把这些无意义的参数去掉再统一协议和域名大小写最后对比 URL 是否重复。第二层是标题归一化。有些内容被不同站点转载URL 完全不同但标题几乎一样。我把标题里的空格、标点、特殊符号统一删掉再算相似度。标题完全相同的直接视为重复编辑距离低于一个阈值的也标记为高度疑似重复。第三层是来源优先级。如果两条结果疑似重复但它们的来源权威度不一样我会保留权重更高的那个源。比如同一个内容官方文档的结果通常比博客转载更值得保留。排序策略我用了一个非常朴素的加权公式score 0.45 * authority_score 0.35 * keyword_density 0.20 * recency_score这里的authority_score是每个源预设的权重比如官方文档是 1.0社区帖子 0.8博客 0.7。keyword_density是关键词在标题和摘要中的出现密度recency_score则是一个随时间衰减的分数越新的内容得分越高。三个系数根据实际使用体验调过几次目前的配比比较均衡相关性优先但对过时内容有一定惩罚。3. 核心模块实现与实操细节3.1 检索源适配器的设计模式所有检索源都通过同一个抽象接口对外提供服务。这样做的好处是新增一个搜索源不需要改动调度核心只需要写一个适配器类并注册进去。适配器的接口非常简单class SearchAdapter: name base timeout 5 def search(self, keyword: str, limit: int 10) - list[dict]: raise NotImplementedError返回的 list 里的每个 dict就是我前面说的统一结构。具体实现里不同源的接入方式千差万别。有的源有开放 API直接发请求、解析 JSON 就行有的源提供 RSS 订阅把查询词拼进 RSS 地址然后解析 XML还有的源什么都没有只能请求普通搜索页面再用正则或解析库提取结果。这里有一个重要的设计原则适配器必须完全隔离异常。我在调度层统一捕获每个适配器的异常某个源抛错了只记录错误并返回空绝不会让整个 OneSearch 挂掉。这个设计后来救了我很多次因为有些站点的页面结构说改就改解析代码很容易直接抛异常。另外每个适配器都要遵守基本的网络礼仪自定义合理的 User-Agent控制请求频率遵守目标站点的 robots 约定。做聚合工具不是为了突破谁的限制而是在公开合法允许的范围内提升自己的检索效率。3.2 关键词高亮与结果摘要生成搜索结果如果没有关键词高亮扫起来会非常累。高亮处理里最容易被忽略的是安全问题。搜索结果里的文本来自外部站点直接拼进 HTML 会有代码注入风险。我的处理方式是先对整个字段做 HTML 转义再在转义后的文本里定位关键词位置包上mark标签。这样既能高亮又不会引入可执行的脚本。摘要生成比想象中麻烦。很多源给的结果里没有摘要字段只有标题和链接这时候我会尝试抓取目标页面的正文取第一段文字作为摘要。如果正文太长就按关键词出现的上下文窗口截取找到关键词第一次出现的位置往前截几十个字符再往后截几十个字符拼成一段有上下文意义的文字。这里有个实操细节截取摘要时要注意不要把一个完整的词截成两半更不要在句子中间硬生生掐断。我目前的实现是截到最近的一个句号或者逗号附近不够完整就把范围放宽一点宁可摘要长一些也不要读起来像断头文。3.3 前端交互设计结果页布局与快捷键OneSearch 的前端交互我刻意模仿了几种主流检索产品的优点但去掉了它们各自槽点。布局上左边是主导航侧栏显示已经接入的搜索源中间是主结果区每个结果卡展示标题、来源标签、摘要、时间和链接。来源标签用不同颜色区分一眼就能看出这条结果来自哪里。键盘操作这块值得单独说一下。输入框聚焦以后按回车触发一次聚合搜索。结果列表加载出来后可以直接用上下方向键在结果间移动光标按回车在当前高亮的结果上打开新标签页。我还加了几个常用快捷键CtrlK聚焦搜索框、CtrlJ切换到下一个源分类、Esc清空当前搜索并回到初始态。这些快捷键刚开始不觉得有多重要但真正用了几天之后就会发现鼠标移动的每一次停顿都在打断思路。键盘全流程操作可以让搜索-扫视-打开-再搜索这个循环流畅很多。3.4 本地部署配置与运行方式OneSearch 的部署很简单本地直接跑 Python 服务就行。我用的是标准库自带的 HTTP 服务和少量第三方库所以环境准备成本不高。项目结构大致是这样onesearch/ ├── adapters/ │ ├── __init__.py │ ├── docs_adapter.py │ ├── community_adapter.py │ └── blog_adapter.py ├── core/ │ ├── dispatcher.py │ ├── dedup.py │ └── scorer.py ├── static/ │ ├── index.html │ ├── app.js │ └── style.css ├── config.yaml └── app.py启动命令就一条pip install -r requirements.txt python app.py默认监听127.0.0.1:8080。如果要在手机等设备上用需要把监听地址改成0.0.0.0同时注意局域网防火墙设置。配置文件负责维护源列表、各源权重、超时时间、缓存时长等参数改配置不用动代码。4. 常见问题与排查技巧实录4.1 部分源抓取不到内容怎么排查OneSearch 运行这么久遇到最多的就是某个源突然搜不出东西了。现象很统一其他源正常就那一个源返回空。排查顺序我总结成一套流程先用浏览器直接打开目标源的搜索页面看是不是真的能搜出内容。打开 OneSearch 的后端日志看这个适配器有没有报错。如果没有报错翻代码里解析结果的部分对比页面结构是不是变了。如果页面结构变了更新解析逻辑如果没变怀疑是请求频率或者来源 IP 被临时限制。针对第 4 种情况我给适配器加了连续失败熔断机制。某个源连续失败 3 次自动熔断 30 分钟期间不再发请求避免被误判为恶意访问。熔断结束后会恢复同时记录一次告警。这个机制上线以后源状态变得非常稳定至少不会因为一个源的问题影响整天的使用。4.2 结果重复、广告和低质量内容如何过滤聚合搜索必然会把不同源的重复内容带出来。除了前面说的 URL 和标题去重我还维护了一条低质量结果过滤规则。这条规则的核心是对标题特别短的、摘要里有明显推广词和跳转参数的、发布时间明显异常的直接降低权重或者干脆不展示。比如有些站点会输出下一页相关推荐这种导航类结果它们也会被当成搜索结果抓回来必须过滤掉。我用了两个关键词黑名单和长长短短的规则模板一开始黑名单写得很长后来发现不如做一个简单分类器来得干净。分类器的思路很朴素把标题、URL、摘要拼成一条文本提取特征跑一个非常小的规则评分。评分低于某个阈值就丢弃。这个分类器不需要机器学习几条 if 规则就可以覆盖九成以上的噪声。真正有用的经验是过滤规则要写成可解释的出了问题能一眼看出来是哪条规则干的不要搞一堆不可调试的正则。4.3 并发请求导致部分源响应超时刚开始没有全局信号量的时候前端一次搜索会同时向 8 个源发请求本机网络连接立刻飙升。有些源对并发特别敏感直接返回超时或者 HTTP 429。我的解决办法是全局限制并发数。信号量设为 4同时最多只有 4 个适配器在发起请求。其他适配器排队等待。这个量级对于个人使用已经完全够用而且能明显降低源站的压力。另外缓存也帮了大忙。同样的关键词在短时间内被重复搜索直接命中缓存不用重新请求外部源。缓存时间是 10 分钟超过就过期。如果某个源特别慢我还会单独把它的时间调短避免占用整个搜索窗口太久。4.4 前端首屏体验优化最初版本的前端是等所有源都返回完毕才渲染结果结果就是等待时间等于最慢的那个源。后来改成流式渲染哪个源先返回就先显示哪个。实现上不算复杂后端发起搜索后把每个源的结果通过流式响应逐批推给前端。前端每收到一批数据就追加到对应区域。这个改动让首屏时间从原来的 5 秒左右降到了 1 秒以内实际体验提升非常大。另一个细节是输入框的防抖。我见过很多聚合工具输入一个关键词浏览器立刻发起请求一边输入一边搜既浪费流量又让前端界面不断闪烁。OneSearch 里我把防抖时间设为 300ms并且每次输入都取消掉上一次未完成的请求只有停顿 300ms 之后的关键词才会真正触发搜索。5. 使用体验沉淀与后续扩展5.1 用了一段时间后的真实感受OneSearch 稳定跑了几个月后我对它的评价是值得做也值得维护。效率提升是很明显的。以前做一个技术方案调研可能要花一两个小时在几个站之间反复切换现在一个搜索框几秒钟就能看到所有源的关键结果。去重排序之后扫一眼基本就能判断哪些值得点进去细看。那种翻开七八个标签页发现都是同一篇文章的挫败感基本上没有了。但也要说句公道话这类工具的效率红利很大一部分来自维护成本。源站点的接口和页面结构不会永远不变适配器需要定期检查更新。这不是一劳永逸的东西更像是在养一盆需要浇水的植物。对我这种喜欢折腾工具的人来说这个维护过程本身也是乐趣但如果只想要开箱即用的工具市面上成熟的聚合搜索服务可能更合适。5.2 后续值得继续做的三个方向第一个方向是本地索引和搜索历史。现在每次搜索都要实时请求外部源如果把历史搜索结果缓存到本地构建一个轻量索引后续相同或相似的关键词可以直接从本地快速出结果外部源只在缓存过期时才重新请求。这样速度可以更快源站压力也更小。第二个方向是订阅式检索推送。有些关键词我会隔几天搜一次比如某个软件的更新说明、某类技术方案的实践文章。与其每次都手动搜不如让 OneSearch 定时执行搜索把新增结果汇总成一个简单的动态列表。这本质上是一个定时任务加结果对比逻辑不复杂但日常价值很高。第三个方向是个性化排序。目前权重是全局配置后续可以记录用户点击习惯看用户更倾向于打开哪个源的结果然后自动调整个人权重系数。这样用久了之后每个源的排序会更加贴近个人口味。5.3 给想动手做类似工具的朋友一个建议如果只让我说一条经验那就是第一版不要追求功能全先把一个搜索框、多个源、去重、排序这条主线跑通。我当时的第一版一共只用了不到两天很多细节都是后面慢慢补的。先解决最痛的那一步剩下的功能都会在真实使用中自己浮现出来比一开始就设计一堆界面和配置要靠谱得多。我个人最大的体会也在这里工具开发最怕的不是技术难点而是需求边界不清楚。OneSearch 从诞生到现在功能增加得很少但每加一个功能都确实提升了日常使用效率。先把每天重复最多的流程自动化你会发现这比任何花哨的功能都更实在。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑