实现全文搜索功能的核心技术与踩坑经验分享

a'ゞ玉银 交互 阅读 1,124
赞 26 收藏
二维码
手机扫码查看
反馈

全文搜索的坑,我踩了两天才爬出来

最近在做一个文档管理的功能,涉及到全文搜索。本来以为是个简单的活儿,结果被折腾得够呛。最后总算搞定了,但中间踩了不少坑,这里记录一下。

实现全文搜索功能的核心技术与踩坑经验分享

最开始的需求很简单:用户输入关键词,从后端返回的数据里找出匹配的内容,并高亮显示。听起来没啥难的,对吧?但实际上问题就出在这个“简单”上。

第一版代码:直接用indexOf(),天真了

一开始我想着,这不就是字符串匹配嘛,直接用JavaScript自带的indexOf()不就行了?于是写了这么一段:

function searchKeyword(data, keyword) {
  return data.filter(item => item.content.indexOf(keyword) !== -1);
}

看起来挺简洁,跑起来也确实能查到包含关键字的项。但很快发现问题了:大小写敏感、全角半角字符的区别、还有中文分词的问题都冒出来了。

比如用户搜“前端”,我的代码是按字面匹配的,如果内容里写的是“前-端”或者“前端开发”,那就查不到。更别说多音字、同义词这些复杂情况了。

折腾了半天发现:正则表达式也不够用

后来我试了下正则表达式,想着这样至少可以解决大小写的问题:

function searchKeyword(data, keyword) {
  const regex = new RegExp(keyword, 'i'); // 忽略大小写
  return data.filter(item => regex.test(item.content));
}

结果还是不行,因为中文没有空格分隔单词,像“前端工程师”这种长词组根本拆不开,而且性能也开始变差。尤其是当数据量大的时候,页面卡得让人怀疑人生。

这里我踩了个坑:盲目优化代码结构,却忽略了算法本身的局限性。其实这时候就应该意识到,纯前端实现可能不是最佳方案。

核心代码就这几行:引入Lunr.js

经过一番调研,我发现了一个轻量级的库叫Lunr.js(https://lunrjs.com/),专门用来做全文搜索。它支持中文分词、模糊匹配和权重排序,简直是为这个场景量身定做的。

安装很简单:

npm install lunr

然后我改了一下代码,把数据先索引化:

import lunr from 'lunr';

// 构建索引
const index = lunr(function () {
  this.ref('id');
  this.field('title');
  this.field('content');

  documents.forEach(doc => {
    this.add(doc);
  });
});

// 执行搜索
function searchDocuments(keyword) {
  return index.search(keyword).map(result => {
    const doc = documents.find(d => d.id === result.ref);
    return { ...doc, score: result.score };
  });
}

这里有几个关键点需要注意:

  • this.ref():设置唯一标识符,通常用数据库主键。
  • this.field():定义需要检索的字段,比如标题和正文。
  • score:返回的结果自带相关性评分,可以根据这个排序。

效果立竿见影!不仅支持模糊查询,还能处理中文分词的问题,比如“前端”能匹配到“前端开发”、“高级前端”等内容。

谁更灵活?谁更省事?

不过这里有个小插曲,我在选择库的时候对比了Elasticsearch和Lunr.js。前者功能强大,适合大规模分布式系统,但对于我们的项目来说太重了。

最后选择了Lunr.js,主要是因为它:

  • 体积小,压缩后只有几KB。
  • 不需要额外的服务端支持,纯客户端运行。
  • API设计简单,学习成本低。

当然,也有缺点,比如数据量超过一定规模(比如几万条记录)时,内存占用会比较高。但对于目前几百条文档的小项目来说完全够用了。

踩坑提醒:这三点一定注意

虽然最终效果不错,但过程中还是遇到了几个坑,分享给大家避免重蹈覆辙:

  • 第一个坑是中文分词的问题。Lunr.js默认只支持英文分词,如果你要处理中文,需要额外引入一个插件,比如lunr-languages中的中文支持包。
  • 第二个坑是性能问题。如果数据量较大,建议不要一次性加载所有数据,而是通过分页或懒加载的方式逐步构建索引。
  • 第三个坑是高亮显示。Lunr.js只负责搜索,不会帮你自动高亮关键词,这部分逻辑需要自己实现。可以用正则替换来完成:
function highlightText(text, keyword) {
  const regex = new RegExp((${keyword}), 'gi');
  return text.replace(regex, '<span class="highlight">$1</span>');
}

记得给highlight加个样式:

.highlight {
  background-color: yellow;
}

以上是我踩坑后的总结

总体来说,这次全文搜索的开发经历让我学到不少东西。从最初的天真的想法,到不断试错,再到找到合适的工具解决问题,整个过程虽然曲折,但也挺有意思的。

如果你有更好的方案,或者对Lunr.js有更多使用经验,欢迎评论区交流!后续我还会继续分享这类实战经验,希望能帮到更多人。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论