上一篇博客写到ElasticSearch有中文分词检索的能力,但如果仅仅就这个就完全没办法体现ElasticSearch的强大了,ElasticSearch还能支持短语搜索,近似搜索,搜索推荐,搜索纠正等搜索引擎高级特性。可以极大地方便用户,极大地提高用户体验。站内垂直搜索几乎在所有互联网产品中都有运用,往往首页最显眼处都有一个搜索框,如天猫,京东,拼多多,当当网,美团,饿了么,优酷,爱奇艺,哔哩哔哩,QQ音乐,网易云音乐,携程,途牛等等等等,不出意外的话应该都是用ElasticSearch或者solr来实现的。
ES不错的教学视频:
es会把每一个分词器分好的词建立倒排索引,存储每一个包含该词语的id等信息,个人猜测分词的列表还会建立Hash表或者B树,因为词语的数据量一大,达到千万级亿级,性能无法保证,如果这样检索单个词语的时候时间复杂度则为O(1)或者O(log(M,N)),当然纯属个人臆测啦,并没有找到相关资料。
es会对搜索关键字进行分词,然后到倒排索引中去匹配,但如果用户希望根据输入的短语匹配,比如就是要搜索‘是一个靓仔’一定连一起的,跟数据库的LIKE关键字效果一样,而不是分词之后只要有【是,一个,靓仔】其中一个就OK,这个时候需要用到短语搜索了;短语搜索也支持任意两个词设置一个最大的距离,只要满足词语词之间不要超过这个距离都可以搜索出来,比如包含【是不是 一个鲜肉 靓仔】需要搜索出来,那么距离就要设置为2了,这种可以称之为近似搜索。
原理:因为文档分词后建立倒排索引时会记录该词在文档中的position,匹配到之后再比较一下position的差值即可,如果必须连在一起,那么差值不能超过1。es也可以使用prefix,wildcard,regexp来检索,但是这些是不能利用es索引的。
-- 建立映射
PUT /parse_search
{
"mappings": {
"properties": {
"con_parse": {
"type": "text"
},
"con_notana": {
"type": "text",
"fields": {
"raw": {
"type": "text",
"index": false
}
}
}
}
}
}
-- 插入数据
PUT /parse_search/_doc/1
{
"con_parse":"凌章是一个靓仔",
"con_notana":"凌章是一个靓仔"
}
PUT /parse_search/_doc/2
{
"con_parse":"ling is a good looking boy",
"con_notana":"ling is a good looking boy"
}
可以搜到,不符合要求
GET /parse_search/_search
{
"query": {
"match": {
"con_parse": "a good"
}
}
}
可以搜到,不符合要求
GET /parse_search/_search
{
"query": {
"match": {
"con_parse": "is good"
}
}
}
搜不到,不符合要求
GET /parse_search/_search
{
"query": {
"term": {
"con_parse": {
"value": "a good"
}
}
}
}
搜不到,不符合要求
GET /parse_search/_search
{
"query": {
"term": {
"con_parse": {
"value": "a good"
}
}
}
}
搜不到,符合要求
GET /parse_search/_search
{
"query": {
"match_phrase": {
"con_parse": "is good"
}
}
}
可以搜到,符合要求
GET /parse_search/_search
{
"query": {
"match_phrase": {
"con_parse": "a good"
}
}
}
可以搜到,间隔一个词,符合要求
GET /parse_search/_search
{
"query": {
"match_phrase": {
"con_parse": {
"query": "is good",
"slop": 2
}
}
}
}
搜不到,不符合要求
GET /parse_search/_search
{
"query": {
"match_phrase": {
"con_parse": {
"query": "is goo",
"slop": 2
}
}
}
}
可以搜到,符合要求
GET /parse_search/_search
{
"query": {
"match_phrase_prefix": {
"con_parse": {
"query": "is goo",
"slop": 2
}
}
}
}
有些时候,用户去做搜索是,未必记得全名,这个时候我们需要给用户一些推荐词列表,唤起用户的记忆,友好的帮助用户搜索;又有些时候,用户会有意或无意把词语拼错,比如靓仔拼成靓崽,good拼成god,像笔者这种相当爱国、过四级还差两分、又要经常搜一些代码资料的人自然是无可避免(拿他真没办法),所以搜索纠正对于es来说也是必不可少。
原理:es的实现使用的N-gram算法模型,我查了一下,他是自然语言处理NLP的一种模型,说起来有一点AI的意味了。使用fuzzy也可以完成检索,但是不能利用es索引。
--- 搜索推荐操作
PUT /ngram_search
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "my_tokenizer"
}
},
"tokenizer": {
"my_tokenizer": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 30,
"token_chars": [
"letter",
"digit"
]
}
}
}
},"mappings": {
"properties": {
"con_ngram":{
"type": "text",
"analyzer": "my_analyzer"
}
}
}
}
-- 测试
POST ngram_search/_analyze
{
"analyzer": "my_analyzer",
"text": "ling is a good looking boy"
}
PUT /parse_search/_doc/1
{
"con_parse":"凌章是一个靓仔"
}
PUT /parse_search/_doc/2
{
"con_parse":"ling is a good looking boy"
}
--- 搜索纠正操作
PUT /autoc_search
{
"settings": {
"analysis": {
"filter": {
"autocomplete_filter" : {
"type" : "edge_ngram",
"min_gram" : 1,
"max_gram" : 20
}
},
"analyzer": {
"autocomplete" : {
"type" : "custom",
"tokenizer" : "standard",
"filter" : [
"lowercase",
"autocomplete_filter"
]
}
}
}
}
, "mappings": {
"properties": {
"con_ngram":{
"type": "text",
"analyzer": "autocomplete"
}
}
}
}
-- 查看分词
POST autoc_search/_analyze
{
"analyzer": "autocomplete",
"text": "ling is a good looking boy"
}
PUT /autoc_search/_doc/1
{
"con_ngram":"凌章是一个靓仔"
}
PUT /autoc_search/_doc/2
{
"con_ngram":"ling is a good looking boy"
}
-- 可以查到,搜索纠正
GET /autoc_search/_search
{
"query": {
"match_phrase": {
"con_ngram":{
"query": "is a god"
}
}
}
}
-- 不可以查到,隔了一个
GET /autoc_search/_search
{
"query": {
"match_phrase": {
"con_ngram":{
"query": "is god"
}
}
}
}
--可以查到,搜索纠正
GET /autoc_search/_search
{
"query": {
"match_phrase": {
"con_ngram":{
"query": "is god",
"slop": 1
}
}
}
}
-- 搜索不到
GET /autoc_search/_search
{
"query": {
"match_phrase_prefix": {
"con_ngram":{
"query": "is god"
}
}
}
}
-- 可以搜到
GET /autoc_search/_search
{
"query": {
"match_phrase_prefix": {
"con_ngram":{
"query": "is god",
"slop": 1
}
}
}
}
PostgreSQL也支持N-gram,所以再提一笔,需要添加pg_trgm扩展,使用gist_trgm后对左右匹配都可以利用索引了,也可对正则表达式利用索引,可以实现近似搜索,但是搜索推荐和搜索纠正貌似还是不行。
-- 创建扩展
create extension pg_trgm;
create table pg_ngram(
id serial,
con text,
con_btree text,
con_gin text,
con_gist text
);
-- 索引
create index k_btree on pg_ngram using btree(con_btree);
create index k_gin on pg_ngram using gin(con_gin gin_trgm_ops);
create index k_gist on pg_ngram using gist(con_gist gist_trgm_ops);
-- 查询
explain select * from pg_ngram where con like '%god looking%';
explain select * from pg_ngram where con_btree like '%god looking%';
explain select * from pg_ngram where con_gin like '%good looking%';
explain select * from pg_ngram where con_gist like '%good looking%';
explain select * from pg_ngram where con_gist ~ 'good looking';
explain select * from pg_ngram where con_gist ~ 'good looking[^。]';
--查看分解
select show_trgm(con_gin) from pg_ngram;
select show_trgm(con_gist) from pg_ngram;
select similarity(con_gin,'god looking') from pg_ngram;
select similarity(con_gist,'god looking') from pg_ngram;