Popular blog tags

 

在有搜索引擎之前,我们查文档常使用顺序匹配。我们需要在文档中顺序扫描,找到完全匹配的子句。

有的情况精确匹配比搜索引擎的查找有优势,比如这样的内容”chinese:1388838245“,如果用户输入”883“希望搜到这则内容,在常规的情况下是搜不到的。

这是因为在有了搜索引擎后,我们对查询语句做的处理就不一样了。我们通常会先分词,然后查找对应的词条索引,最后得到评分由高到低的文档列表。上面的例句在常规的分词情况下,没有也不可能有”883“这个词条,因此搜索不到这则内容。

我一度以为没法实现完全匹配了,直到一个硬需求的出现。花了一天时间,把完全匹配用搜索引擎的思维整理出来。

 

简要描述实现思路,字段按一字一词的形式分词,再利用短语查询来搜索

 

ES中,可以实现一字一词的的分词器是NGram。

Ngram分词的官方文档地址: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-ngram-tokenizer.html

它其实是一个上下文相连续字符的分词工具,可以看官方文档中的例子。当我们将它 min_gram 和 max_gram 都设为1时,它会按一字一词的形式分词。比如“[email protected]”,分词的结果是["s" , "h" , "i" , "n" , "y" , "k" , "e" , "@" , "1" , "8" , "9" , "." , "c" , "n" ]。

 

 

  1.  
    <pre name="code" class="javascript">/index_name/
  2.  
    {
  3.  
    "settings": {
  4.  
    "analysis": {
  5.  
    "analyzer": {
  6.  
    "charSplit": {
  7.  
    "type": "custom",
  8.  
    "tokenizer": "ngram_tokenizer"
  9.  
    }
  10.  
    },
  11.  
    "tokenizer": {
  12.  
    "ngram_tokenizer": {
  13.  
    "type": "nGram",
  14.  
    "min_gram": "1",
  15.  
    "max_gram": "1",
  16.  
    "token_chars": [
  17.  
    "letter",
  18.  
    "digit",
  19.  
    "punctuation"
  20.  
    ]
  21.  
    }
  22.  
    }
  23.  
    }
  24.  
    }
  25.  
    }
 
 
以上语句中,构建了一个名为“charSplit”的分析器。它使用一个名为“ngram_tokenizer”的Ngram分词器。

 

可以用如下语句测试charSplit分析器,可以看到一字一词的效果:

 

  1.  
    curl -POST http://IP:port/{index_name}/_analyze?pretty&analyzer=charSplit
  2.  
    "测试语句"
 

 

把这个分析器在mapping里用起来:

 

  1.  
    ...
  2.  
    "sender": {
  3.  
    "type": "string",
  4.  
    "store": "yes",
  5.  
    "analyzer": "charSplit",
  6.  
    "fields": {
  7.  
    "raw": {
  8.  
    "type": "string",
  9.  
    "index": "not_analyzed"
  10.  
    }
  11.  
    },
  12.  
    ...
 


 

 

接下来就可以用match_phrase来实现完全匹配查询

 

  1.  
    /{index_name}/{type_name}/_search
  2.  
    {
  3.  
    "query": {
  4.  
    "multi_match": {
  5.  
    "query": "@189.cn",
  6.  
    "type": "phrase", //type指定为phrase
  7.  
    "slop": 0, //slop指定每个相邻词之间允许相隔多远。此处设置为0,以实现完全匹配。
  8.  
    "fields": [
  9.  
    "sender"
  10.  
    ],
  11.  
    "analyzer": "charSplit", //分析器指定为charSplit
  12.  
    "max_expansions": 1
  13.  
    }
  14.  
    },
  15.  
    "highlight": { //测试高亮是否正常
  16.  
    "pre_tags": [
  17.  
    "<b>"
  18.  
    ],
  19.  
    "post_tags": [
  20.  
    "</b>"
  21.  
    ],
  22.  
    "fragment_size": 100,
  23.  
    "number_of_fragments": 2,
  24.  
    "require_field_match": true,
  25.  
    "fields": {
  26.  
    "sender": {}
  27.  
    }
  28.  
    }
  29.  
    }
 
phrase查询原始的作用是用来做短语查询,它有一个重要的特点:有顺序。我们利用了它匹配的有序性,限制slop为0,则可实现完全匹配查询。

 

以上语句返回的结果是:

 

  1.  
    {
  2.  
     
  3.  
    "took": 18,
  4.  
    "timed_out": false,
  5.  
    "_shards": {
  6.  
    "total": 9,
  7.  
    "successful": 9,
  8.  
    "failed": 0
  9.  
    },
  10.  
    "hits": {
  11.  
    "total": 1,
  12.  
    "max_score": 0.40239456,
  13.  
    "hits": [
  14.  
    {
  15.  
    "_index": "index_name",
  16.  
    "_type": "type_name",
  17.  
    "_id": "AU9OLIGOZN4dLecgyoKp",
  18.  
    "_score": 0.40239456,
  19.  
    "_source": {
  20.  
    "sender": "18977314000 <[email protected]>, 李X <[email protected]>, 秦X <[email protected]>, 刘X <[email protected]>"
  21.  
    },
  22.  
    "highlight": {
  23.  
    "sender": [
  24.  
    "18977314000 <18977314000<b>@</b><b>1</b><b>8</b><b>9</b><b>.</b><b>c</b><b>n</b>>, 李X <18977314000<b>@</b><b>1</b><b>8</b><b>9</b><b>.</b><b>c</b><b>n</b>>, 秦纯X <18977314000<b>@</b><b>1</b><b>8</b><b>9</b><b>.</b><b>c</b><b>n</b>>, 刘X <189773140"
  25.  
    ]
  26.  
    }
  27.  
    }
  28.  
    ]
  29.  
    }
  30.  
     
  31.  
    }
 

 

到此,就实现了完全匹配查询。实际环境中用NGram做一字一词分析器的时候会更细致一些,比如有一些字符需要用stop word过滤掉。这些细节可以根据实际需要在构造分析器时添加filter实现,在此不做赘述。