MeiliSearch轻量级搜索引擎-食用指南

2022-12-27 14:44:27 浏览数 (1)

MeiliSearch 是一个开源、速度极快且高度相关的搜索引擎。但它不仅仅是通用搜索引擎:MeiliSearch 高度可定制的搜索引擎 API 为您提供了极大的灵活性。例如,您可以修改排名规则、添加自定义排名规则、配置同义词、过滤掉停用词等等。为了提高您的搜索能力,MeiliSearch 允许您设置作为索引的分面过滤器。

背景

因为最近开发文档搜索功能,所以开始接触搜索引擎,目的主要是对比和选型。

搜索引擎有很多,最为常见的是ES,是搜索引擎的主流,但ES太重了,对于小项目而言增加的硬件成本,运维成本。对于小项目来说不太合适。想要分词很好,要么费人,要么费机器。

至于利用关系型数据库(MySQL和PostgreSQL等)自带的全文检索功能,虽然有对应的功能,但性能不佳,很费机器。

综合研究下来,开箱即用的 MeiliSearch, 对于中小型项目来说,MeiliSearch 是一个不错的选择。

使用教程
安装Meilisearch服务
代码语言:javascript复制
# Install Meilisearch

curl -L https://install.meilisearch.com | sh

# Launch Meilisearch

./meilisearch
验证

查看索引列表

代码语言:javascript复制
curl http://127.0.0.1:7700/indexes

// {"results":[],"offset":0,"limit":20,"total":0}

// 因为是从零开始的,所以索引列表应该是空数组
设置访问密码

可选,为了安全还是要设置的

代码语言:javascript复制
./meilisearch --master-key="MASTER_KEY"

// 访问的时候要带上密码

curl http://127.0.0.1:7700/indexes / --header 'Authorization: Bearer xxxx'

// 如果是0.24以下老版本,--header 'X-Meili-API-Key: xxxx'

现在我们可以愉快的尝试meiliSearch的强大功能了

初始化客户端

首先,创建用npm创建新项目:

代码语言:javascript复制
mkdir myMeiliSearch

cd ./myMeiliSearch

npm init -y

然后,安装meilisearch-js 依赖:

代码语言:javascript复制
npm install meilisearch

最终, 创建一个index.js 文件在根目录,在这里输入我们的代码。

代码语言:javascript复制
touch index.js
填充一些demo数据

数据字段如下

代码语言:javascript复制
{

id: 'number',

title: 'string',

genres: 'Array'

}

index.js如下

代码语言:javascript复制
const {MeiliSearch} = require('meilisearch')

const main = async () => {

const client = new MeiliSearch({

host: 'http://127.0.0.1:7700',

apiKey: 'xxxx',

})

// An index is where the documents are stored.

const index = client.index('movies')

const documents = [

{ id: 1, title: 'Carol', genres: ['Romance', 'Drama'] },

{ id: 2, title: 'Wonder Woman', genres: ['Action', 'Adventure'] },

{ id: 3, title: 'Life of Pi', genres: ['Adventure', 'Drama'] },

{ id: 4, title: 'Mad Max: Fury Road', genres: ['Adventure', 'Science Fiction'] },

{ id: 5, title: 'Moana', genres: ['Fantasy', 'Action']},

{ id: 6, title: 'Philadelphia', genres: ['Drama'] },

]

// If the index 'movies' does not exist, Meilisearch creates it when you first add the documents.

let response = await index.addDocuments(documents)

console.log(response) // => { "uid": 0 }

// 打印效果如下

/*EnqueuedTask {

taskUid: 0,

indexUid: 'movies',

status: 'enqueued',

type: 'documentAdditionOrUpdate',

enqueuedAt: 2022-11-02T07:20:47.332Z

}*/

}

main()

诸如文档添加之类的任务总是返回一个唯一的标识符。您可以使用此标识符 taskUid 来检查任务的状态(入队、处理、成功或失败)。

查询

index.js 代码如下

代码语言:javascript复制
const { MeiliSearch } = require('meilisearch')

const main = async () => {

const client = new MeiliSearch({

host: 'http://127.0.0.1:7700',

apiKey: 'xxxx',

})

// An index is where the documents are stored.

const index = client.index('movies')

// Meilisearch is typo-tolerant:

const search = await index.search('philoudelphia')

console.log(search)

// 查询结果如下

/*

{

hits: [ { id: 6, title: 'Philadelphia', genres: [Array] } ],

estimatedTotalHits: 1,

query: 'philoudelphia',

limit: 20,

offset: 0,

processingTimeMs: 4

}

*/

}

同时尝试,文本前缀触发

代码语言:javascript复制
const search = await index.search('phi')

// 查询结果如下

/*

{

hits: [ { id: 6, title: 'Philadelphia', genres: [Array] } ],

estimatedTotalHits: 1,

query: 'phi',

limit: 20,

offset: 0,

processingTimeMs: 0

}

*/

发现依然有效,当如如果是中间的单词。比如Philadelphia的中间部分ladel,这时候有query:ladel,是搜出不来的。

那么 Road 这种明显是分隔的单词(分词)怎么样,我尝试了一些query:Maxquery:Road的时候,都可以。

重点来了,中文支持的如何呢?

我们继续往索引中添加数据,

代码语言:javascript复制
[

{ id: 7, title: '一只狐狸', genres: ['狐狸', '老爷爷'] },

{ id: 8, title: '帅气的猫', genres: ['蓝色的猫', '淘气的老鼠'] },

{ id: 9, title: '喜欢盆子的狗', genres: ['盆子', '狗'] },

{ id: 10, title: '无敌的超人', genres: ['超人', '可路菲菲'] },

{ id: 11, title: '斗破天下', genres: ['萧快乐', '范无敌'] },

{ id: 12, title: '闺蜜之主', genres: ['赵猥琐', '美丽女神'] },

]

然后,我们搜索一下“猫”,不负众望,

代码语言:javascript复制
{

hits: [ { id: 8, title: '帅气的猫', genres: [Array] } ],

estimatedTotalHits: 1,

query: '猫',

limit: 20,

offset: 0,

processingTimeMs: 0

}

无论是搜单个字还是全部,它都可以匹配出来,简直完美。仅这些,使用es实现就要好几天,甚至不能轻易实现。

条件判断

如果你需要条件判断,那么你需要先配置一下

代码语言:javascript复制
// 官网上的ReadMe.md index.updateAttributesForFaceting 是过期的写法

//详情 https://github.com/meilisearch/meilisearch-js/pull/941

await index.updateFilterableAttributes([

'id',

'genres'

])

如果你还想搜索genres字段,你需要

代码语言:javascript复制
await index.updateSearchableAttributes([

'id',

'title',

'genres'

])

查询一下id大于3且genres中包含Adventure的项

代码语言:javascript复制
const search = await index.search(

'Adventure',

{

filter: ['id > 3']

}

)

console.log(search)

/*

{

hits: [ { id: 4, title: 'Mad Max: Fury Road', genres: [Array] } ],

estimatedTotalHits: 1,

query: 'Adventure',

limit: 20,

offset: 0,

processingTimeMs: 0

}

*/

是不是很容易?

附上完整代码
代码语言:javascript复制
const { MeiliSearch } = require('meilisearch')

const main = async () => {

const client = new MeiliSearch({

host: 'http://127.0.0.1:7700',

apiKey: 'xxxx',

})

// An index is where the documents are stored.

const index = client.index('movies')

await index.updateFilterableAttributes([

'id',

'genres'

])

await index.updateSearchableAttributes([

'id',

'title',

'genres'

])

// addDocument(index)

// addDocumentZh(index)

// addDocumentEnZh(index)

// updateDocumentEnZh(index)

// Meilisearch is typo-tolerant:

// const search = await index.search('ww')

const search = await index.search(

'Adventure',

{

filter: ['id > 3']

}

)

console.log(search)

}

async function addDocument(index) {

const documents = [

{ id: 1, title: 'Carol', genres: ['Romance', 'Drama'] },

{ id: 2, title: 'Wonder Woman', genres: ['Action', 'Adventure'] },

{ id: 3, title: 'Life of Pi', genres: ['Adventure', 'Drama'] },

{ id: 4, title: 'Mad Max: Fury Road', genres: ['Adventure', 'Science Fiction'] },

{ id: 5, title: 'Moana', genres: ['Fantasy', 'Action'] },

{ id: 6, title: 'Philadelphia', genres: ['Drama'] },

]

// If the index 'movies' does not exist, Meilisearch creates it when you first add the documents.

let response = await index.addDocuments(documents)

console.log(response) // => { "uid": 0 }

}

async function addDocumentZh(index) {

const documents = [

{ id: 7, title: '一只狐狸', genres: ['狐狸', '老爷爷'] },

{ id: 8, title: '帅气的猫', genres: ['蓝色的猫', '淘气的老鼠'] },

{ id: 9, title: '喜欢盆子的狗', genres: ['盆子', '狗'] },

{ id: 10, title: '无敌的超人', genres: ['超人', '可路菲菲'] },

{ id: 11, title: '斗破天下', genres: ['萧快乐', '范无敌'] },

{ id: 12, title: '闺蜜之主', genres: ['赵猥琐', '美丽女神'] },

]

// If the index 'movies' does not exist, Meilisearch creates it when you first add the documents.

let response = await index.addDocuments(documents)

console.log(response) // => { "uid": 0 }

}

async function addDocumentEnZh(index) {

const documents = [

{ id: 13, title: '一只App狐狸', genres: ['狐狸', '老爷爷'] },

{ id: 14, title: 'Niu帅气的猫', genres: ['蓝色的猫', '淘气的老鼠'] },

{ id: 15, title: '喜欢盆子A的狗', genres: ['盆子', '狗'] },

{ id: 16, title: '无敌的超人', genres: ['超人', '可路菲菲'] },

{ id: 17, title: '斗Kankan破K天下', genres: ['萧快乐', '范无敌'] },

{ id: 18, title: '闺蜜Www之主', genres: ['赵猥琐', '美丽女神'] },

]

// If the index 'movies' does not exist, Meilisearch creates it when you first add the documents.

let response = await index.addDocuments(documents)

console.log(response) // => { "uid": 0 }

}

async function updateDocumentEnZh(index) {

const documents = [

{ id: 13, title: '一只App狐狸', genres: ['狐狸', '老爷爷'] },

{ id: 14, title: 'Niu帅气的猫', genres: ['蓝色的猫', '淘气的老鼠'] },

{ id: 15, title: '喜欢盆子A的狗', genres: ['盆子', '狗'] },

{ id: 16, title: '无敌的超人', genres: ['超人', '可路菲菲'] },

{ id: 17, title: '斗Kankan破K天下', genres: ['萧快乐', '范无敌'] },

{ id: 18, title: '闺蜜Www之主1', genres: ['赵猥琐', '美丽女神'] },

]

// If the index 'movies' does not exist, Meilisearch creates it when you first add the documents.

let response = await index.updateDocuments(documents)

console.log(response) // => { "uid": 0 }

}

main()

0 人点赞