需要采用ES实现 "全文搜索" 👉 没有错 需求就这4个字
想要 中文"分词"搜索 , 同时也需要像"like匹配"那样,不要随意召回大量数据,希望数据精确的匹配上
暂时不要求拼音检索
1.ik分词
通常我们做中文搜索的时候,会选用ik分词器进行ik分词检索,最基础的检索方案如下
-
1.第一步 创建索引
jsonPUT test_ik
-
2.第二步 配置映射信息
- 一般采用ik_max_word来处理写入文档时的分词,搜索时使用ik_smart
jsonPUT test_a/_mappings { "dynamic": "runtime", "properties": { "ik": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" } }
-
3.第三步 检查索引并填充数据
- 验证下是否正确
jsonGET test_a
- 造数据
jsonPOST test_a/_doc { "ik": "用户名信息张三李四王五击破完全没考虑" } POST test_a/_doc { "ik": "之前在 DSL 中一次问卷调查中,收集到如下几个和搜索类型相关的问题。" } POST test_a/_doc { "ik": "本文继续缩小范围,把重心缩小为最常用的:精准匹配检索、全文检索、组合检索三种类型。精准匹配检索和全文检索的本质区别:精准匹配把检索的整个文本不做分词处理,当前一个串整体理。而全文检索需要分词处理,对分词后的每个词单独检索然后大bool组合检索。" }
-
4.第四步 检索验证
- 1.使用多字段全文检索方案
json{ "query": { "bool": { "should": [ { "multi_match": { "query": "缩小范围" } } ] } }, "highlight": { "fields": { "*": { "pre_tags": [ "<em>" ], "post_tags": [ "</em>" ] } } } }
结果:
json{ "took": 1039, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 1, "relation": "eq" }, "max_score": 3.5733945, "hits": [ { "_index": "test_a", "_type": "_doc", "_id": "Sx9Z744BYpIYyAE61FNi", "_score": 3.5733945, "_source": { "ik": "本文继续缩小范围,把重心缩小为最常用的:精准匹配检索、全文检索、组合检索三种类型。精准匹配检索和全文检索的本质区别:精准匹配把检索的整个文本不做分词处理,当前一个串整体理。而全文检索需要分词处理,对分词后的每个词单独检索然后大bool组合检索。" }, "highlight": { "ik": [ "本文继续<em>缩小</em><em>范围</em>,把重心<em>缩小</em>为最常用的:精准匹配检索、全文检索、组合检索三种类型。精准匹配检索和全文检索的本质区别:精准匹配把检索的整个文本不做分词处理,当前一个串整体理。" ] } } ] } }
结果可以看到 检索到了ik字段的信息
这样看 ik分词检索似乎真的达到了我们需要的效果。
但是有时候会出现一个情况 ik词库中并没有这个词,你搜索的句子可能是文章中的某一段,但ik召回了匹配更多词的记录。我们希望记录更匹配的排到最前面,然而ik的召回变得并没有那么准确。
这个时候我想到 通过合并simple string query 和 ik 并设置最低分限制避免ssq召回评分为0的数据。 我通过bool来合并
json
{
"query": {
"bool": {
"should": [
{
"multi_match": {
"query": "缩小范围"
}
},
{
"simple_query_string": {
"query": "缩小范围"
}
}
]
}
},
"highlight": {
"fields": {
"*": {
"pre_tags": [
"<em>"
],
"post_tags": [
"</em>"
]
}
}
}
}
似乎是成功了...
但是这个时候 出现了情况,对方过来和你说:"我想要空格分词 然后精确的检索这个内容",我不想召回这么多无关数据。
那么我这个时候第一个想到的就是 match_phrase
, 类似于sql的like,那么使用这个方案似乎必须舍弃ik分词,这是最简单的方式将需求处理掉。
Ngram方案
在实现过程中发现了网上有其他的方案,似乎是使用 ngram
来作替代, 那么开始尝试。
1.创建索引
http
DELETE /testngram
json
PUT /testngram
{
"settings": {
"index": {
"max_ngram_diff": 10
},
"analysis": {
"analyzer": {
"ngram_analyzer": {
"tokenizer": "ngram_tokenizer",
"filter": [
"lowercase"
]
}
},
"tokenizer": {
"ngram_tokenizer": {
"type": "ngram",
"min_gram": 1,
"max_gram": 10,
"token_chars": [
"letter",
"digit"
]
}
},
"normalizer": {
"custom_normalizer": {
"type": "custom",
"filter": [
"lowercase",
"asciifolding"
]
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "ngram_analyzer"
},
"key": {
"type": "keyword",
"normalizer": "custom_normalizer"
}
}
}
}
2.插入数据
json
POST /testngram/doc/1
{
"name": "Apple iPhone 13",
"key": "hello"
}
POST /testngram/doc/2
{
"name": "Apple iPhone 14",
"key": "HEllo"
}
POST /testngram/doc/3
{
"name": "据记者了,郑州文化馆、河南省大河文化艺术中心自2016年以来,通过组织第十一届、第十二届中国郑州国际少林武术节书画展,通过书画展文化艺术搭台,是认真贯彻习中央文艺工作座谈会重要讲话精神,响应文化部开展深入生活、扎根人民主题实践活动。以作品的真善美陶冶人类崇高之襟怀品格,树立中华民族的文化自信,用写意精神推动社会文学艺术的繁荣发展。",
"key": "worlD"
}
POST /testngram/doc/4
{
"name": "恭喜发财 红包拿来"
}
POST testngram/_analyze
{
"analyzer": "ngram_analyzer",
"text": "据记者了,郑州文化馆、河南省大河文化艺术中心自2016年以来,通过组织第十一届、第十二届中国郑州国际少林武术节书画展,通过书画展文化艺术搭台,是认真贯彻习中央文艺工作座谈会重要讲话精神,响应文化部开展深入生活、扎根人民主题实践活动。以作品的真善美陶冶人类崇高之襟怀品格,树立中华民族的文化自信,用写意精神推动社会文学艺术的繁荣发展。"
}
3.执行查询
json
GET /testngram/_search
{
"query": {
"multi_match": {
"query": "hello 郑州文 来,通",
"analyzer": "whitespace"
}
}
}
也可以作为一个方案。
但以上的几种方案于我个人都并不满意,并不能够达到自己想要的效果。
在我目前的学习水平看来中文分词与like匹配似乎是有冲突的,并不能做到二者兼得。
以上就是我在实践过程中对ES的简单使用,如果有更好的处理方案,希望能够分享建议!