0x00 测试数据

{
  "took": 5,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 7,
    "max_score": 1,
    "hits": [
      {
        "_index": "test",
        "_type": "test",
        "_id": "123",
        "_score": 1,
        "_source": {
          "content": "1.2.3.12.23.34"
        }
      },
      {
        "_index": "test",
        "_type": "test",
        "_id": "21",
        "_score": 1,
        "_source": {
          "content": "beijing"
        }
      },
      {
        "_index": "test",
        "_type": "test",
        "_id": "2122",
        "_score": 1,
        "_source": {
          "content": "360.cn"
        }
      },
      {
        "_index": "test",
        "_type": "test",
        "_id": "21322",
        "_score": 1,
        "_source": {
          "content": "360"
        }
      },
      {
        "_index": "test",
        "_type": "test",
        "_id": "21222",
        "_score": 1,
        "_source": {
          "content": "ab.ab"
        }
      },
      {
        "_index": "test",
        "_type": "test",
        "_id": "213422",
        "_score": 1,
        "_source": {
          "content": "ab"
        }
      },
      {
        "_index": "test",
        "_type": "test",
        "_id": "212",
        "_score": 1,
        "_source": {
          "content": "360.cn/?1=1"
        }
      }
    ]
  }
}

0x01 Elasticsearch 基本结构

Elasticsearch 是一个分布式的搜索和分析引擎,可以用于全文检索、结构化检索和分析,并能将这三者结合起来。

Elasticsearch 存储储存的数据,相当于一个个文档。其中有三个必须的元数据元素:

_index(索引)
    文档位置
_type(类别)
    文档表示对象类别
_id
    文档唯一标识

一般插入数据时如果不指定_id将会分配一个随机字符串当作文档的_id.

一般我们想查看储存结构和数据元素可通过_mapping方式,可以查看完整映射。

λ curl -XGET "http://192.168.188.133:9200/test/_mapping/test?pretty"
{
  "test" : {
    "mappings" : {
      "test" : {
        "properties" : {
          "content" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          }
        }
      }
    }
  }
}

从映射中可以看到index=test,type=test中有个properties。

而我们插入数据中的 field = content的内容首先有个type为text.

那么现在我们来解释一下Elasticsearch中文档类型。

具体可查看elasticsearch5.5 Document

这边简单的介绍几种常用的:

Numeric datatypes
    long, integer, short, byte, double, float, half_float, scaled_float
Date datatype
    date
Boolean datatype
    boolean
Binary datatype
    binary
Range datatypes
    integer_range, float_range, long_range, double_range, date_range
string
    text and keyword

着重说明一下string类型。

string类型在elasticsearch5.0版本后拆分成text和keyword两种类型。在查询时,根据选择text或者keyword的不同,查询的处理方式也不同。如果将数据当作text进行查找的话,es内置分词器会将text按照分词器规则进行分词后进行匹配,但是如果使用keyword的话则会当成字符串进行匹配。使用text还是keyword需要看需求进行选择,如果一个field的数据量过大,使用keyword的话将会极大的降低效率。

0x02 Elasticsearch DSL 查询

如果你只是需要应用,那么这里将会有大部分问题的解决办法。

match 查询

GET /_search
{
    "query": {
        "match" : {
            "content" : "360.cn"
        }
    }
}

查询结果:

{
  "took": 11,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 0.52354836,
    "hits": [
      {
        "_index": "test",
        "_type": "test",
        "_id": "21322",
        "_score": 0.52354836,
        "_source": {
          "content": "360"
        }
      },
      {
        "_index": "test",
        "_type": "test",
        "_id": "2122",
        "_score": 0.34148216,
        "_source": {
          "content": "360.cn"
        }
      },
      {
        "_index": "test",
        "_type": "test",
        "_id": "212",
        "_score": 0.2876821,
        "_source": {
          "content": "360.cn/?1=1"
        }
      }
    ]
  }
}

match查询会将你所输入的查询字符按照既定的分词器拆分后在和指定的field进行匹配,如果field的类型是text,则会将filed内的内容也拆分,匹配后返回结果。match查询实质上是一种boolean型查询,它支持 and 或者 or 操作符,但是一般默认是or操作符。即,若查询为:

GET /_search
{
    "query": {
        "match" : {
            "content" : "test search"
        }
    }
}

match返回结果中会包含匹配的test或者searc的field。如展示的查询,就是将360.cn按照默认分词分为360 和 cn 两个部分,进行查询。

GET /_search
{
    "query": {
        "match" : {
            "content" : {
                "query" : "test search"
                "operator" : "and"
            }
        }
    }
}

这样查询的话则会返回同时包含test和search的部分。

前面说到match查询是将所输入和查询目标分别分词后查询,这样查询以后会返回一个score即匹配度。匹配度越高也就是在text中这些词汇出现率越高。

cutoff_frequency这个参数即可以根据出现频率高低进行划,绝对输出内容

match_phrase 短语查询

match_phrase 查询会依旧会将查询语句进行分词,但是他会根据位置关系进行匹配,如:

GET /_search
{
    "query": {
        "match_phrase" : {
            "message" : "360.cn"
        }
    }
}

查询结果:

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1.0541058,
    "hits": [
      {
        "_index": "test",
        "_type": "test",
        "_id": "2122",
        "_score": 1.0541058,
        "_source": {
          "content": "360.cn"
        }
      },
      {
        "_index": "test",
        "_type": "test",
        "_id": "212",
        "_score": 0.5753642,
        "_source": {
          "content": "360.cn/?1=1"
        }
      }
    ]
  }
}

虽然他查询也是将查询内容和查询目标(text)进行分词以后查询,但是他会根据位置是否相连判断是否是短语进行匹配,然后输出结果。

match_phrase有个参数是 analyzer (分词规则),使用这个可以指定指定分词器。如果想了解分词器可以继续往下看,会有详细说明,如果只是想满足需求,可以看keyword不需要看分词器

match_phrase_prefix 前缀查询

match_phrase_prefix 和 match_phrase 一样,唯一的区别就是从分词后的字符前缀开始比较

multi_match 多数查询

multi_match 可以同时匹配多个 field,如:

GET /_search
{
  "query": {
    "multi_match" : {
      "query":    "Will Smith",
      "fields": [ "title", "*_name" ]
    }
  }
}

同时有个小技巧,如果想在两个field内匹配不同内容,因为match实质上是boolean查询,故可以这样添加条件:

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "content": "360"
          }
        },
        {
          "match": {
            "content": "cn"
          }
        }
      ]
    }
  }
}

这样可以同时匹配不同field,不同内容的文档。

query_string

URL Search中q参数查询也就是使用这个方法,在此不做赘述。附链接,可查看语法:

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html

term

term查询是精确的查询特定的词汇在倒排索引中。

首先解释一下倒排索引,es在查询时会将查询的field通过分词器处理后加入倒排索引。

查询方式如下:

GET _search
{
  "query": {
    "term" : {
        "content" : "360.cn"
     }
  }
}

看到这里的应该很好奇既然这样term和match的区别在哪里,官方已经给出解释很明确了:

String fields can be of type text (treated as full text, like the body of an email), or keyword (treated as exact values, like an email address or a zip code). Exact values (like numbers, dates, and keywords) have the exact value specified in the field added to the inverted index in order to make them searchable.

However, text fields are analyzed. This means that their values are first passed through an analyzer to produce a list of terms, which are then added to the inverted index.

There are many ways to analyze text: the default standard analyzer drops most punctuation, breaks up text into individual words, and lower cases them. For instance, the standard analyzer would turn the string “Quick Brown Fox!” into the terms [quick, brown, fox].

This analysis process makes it possible to search for individual words within a big block of full text.

The term query looks for the exact term in the field’s inverted index — it doesn’t know anything about the field’s analyzer. This makes it useful for looking up values in keyword fields, or in numeric or date fields. When querying full text

其中最重要的是:

主要说的是一个string类型的field可能是一个text也可能是一个keyword。当text被处理的时候,text会被分词器分析,这就意味着他们的数据第一次被处理不是查询而是分词器,它产生一系列的小的词汇加入倒叙索引,如“Quick Brown Fox!”在standard分词器处理会会变成[quick, brown, fox],那么在差欻性能的时候将不会匹配整个文本,而是在倒序索引中匹配这些小的词汇。
term不会将查询词汇进行分词进行匹配,而match则会将查询词汇分词后进行匹配。

官方还给了几个例子:

PUT my_index
{
  "mappings": {
    "my_type": {
      "properties": {
        "full_text": {
          "type":  "text"    //1
        },
        "exact_value": {
          "type":  "keyword"    //2
        }
      }
    }
  }
}

PUT my_index/my_type/1
{
  "full_text":   "Quick Foxes!",    //3
  "exact_value": "Quick Foxes!"   //4
}

1The full_text field is of type text and will be analyzed.

2.The exact_value field is of type keyword and will NOT be analyzed.

3.The full_text inverted index will contain the terms: [quick, foxes].

4.The exact_value inverted index will contain the exact term: [Quick Foxes!].

term和match的比较

GET my_index/my_type/_search
{
  "query": {
    "term": {
      "exact_value": "Quick Foxes!" 
    }
  }
}
//1

GET my_index/my_type/_search
{
  "query": {
    "term": {
      "full_text": "Quick Foxes!" 
    }
  }
}
//2

GET my_index/my_type/_search
{
  "query": {
    "term": {
      "full_text": "foxes" 
    }
  }
}
//3

GET my_index/my_type/_search
{
  "query": {
    "match": {
      "full_text": "Quick Foxes!" 
    }
  }
}
//4

1.This query matches because the exact_value field contains the exact term Quick Foxes!.

2.This query does not match, because the full_text field only contains the terms quick and foxes. It does not contain the exact term Quick Foxes!.

3.A term query for the term foxes matches the full_text field.

4.This match query on the full_text field first analyzes the query string, then looks for documents containing quick or foxes or both.

range 范围查询

参数如下:

gt: > 大于
lt: < 小于
gte: >= 大于或等于
lte: <= 小于或等于

示例:

GET _search
{
    "query": {
        "range" : {
            "age" : {
                "gte" : 10,
                "lte" : 20,
            }
        }
    }
}

正则查询wildcard 和 regexp

使用范例:

GET /_search
{
    "query": {
        "wildcard" : {
            "user" : "ki*y"
        }
    }
}

GET /_search
{
    "query": {
        "regexp":{
            "name.first": "s.*y"
        }
    }
}

特别注意:wildcard和regexp在查询时若field的类型为text,则是对倒叙索引中的内容进行匹配。如:

GET /_search
{
    "query": {
        "regexp":{
            "content": "3[0-9]0\.cn"
        }
    }
}

而field为content的内容为:

360.cn 666

则倒叙索引中只会有['360','cn','666'],而正则使用也只会对['360','cn','666']中的每一个分别进行匹配。

wildcard 使用标准的 shell 通配符查询: ? 匹配任意字符, * 匹配 0 或多个字符。

regexp 可以使用我们平时使用的正则表达式,其中regexp有个flags参数,他是继承至Lucene。

可用的有:

ALL (default), ANYSTRING, COMPLEMENT, EMPTY, INTERSECTION, INTERVAL, or NONE.

意义可通过 http://lucene.apache.org/core/4_9_0/core/org/apache/lucene/util/automaton/RegExp.html 查询.

keyword 类型

keyword 类型是当这个field被查询时,会当成一个字符串而不是一个text进行分词,如果查询field的内容大小没有很大并且想通过正则进行精确查询,可以通过将text转换为keyword进行查询,方式如下:

{
  "query": {
    "match" : {
      "content.keyword":"360"
    }
  }
}

即在原field后加个.keyword即可。

特别说明

当查找类型转换为keyword以后,term或者match等这中需要完全匹配的处理结果将会和把field当作text时候处理的结果差距特别大。因为当field变为keyword进行查询后keyword不分词,则match将查找内容分词后不能和整个field完全匹配,就不会返回结果,term同理。

0x02分词器介绍

如果只需要应用,那么前面所说的应该满足基本需求了,如果想更优雅的使用可以接着了解下分词器。

官方文档5.5介绍如下:

https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer-anatomy.html

默认就有的分词器有:

Standard
Simple
Whitespace
Stop
keyword
pattern
language
Fingerprint
Custom

每个分词器的特性官方都有写,链接如下:

https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html

当想对每种分词器进行测试时可以使用如下方法:

POST _analyze
{
  "analyzer": "whitespace",
  "text":     "The quick brown fox."
}

analyzer 分词器名称
text 进行测试内容

如我使用 whitespace 分词器(按照空格将text内的内容分为一个个term加入倒叙索引),对'360.cn 666'进行测试:

POST _analyze
{
  "analyzer": "whitespace",
  "text":     "360.cn 666"
}

返回内容为:

{
  "tokens" : [
    {
      "token" : "360.cn",
      "start_offset" : 0,
      "end_offset" : 6,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "666",
      "start_offset" : 7,
      "end_offset" : 10,
      "type" : "word",
      "position" : 1
    }
  ]
}

将会分为两部分['360.cn','666']并表明开始和借宿点在text的下标。

挑选好分词器后,可以通过如下命令创建指定index的分词器:

curl -XPUT 'localhost:9200/bac?pretty' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "abc": {
      "properties": {
        "content": {
          "type":     "text",
          "analyzer": "standard",
          "fields": {
            "keyword": {
              "type":     "keyword",
              "ignore_above":"256"
            }
          }
        }
      }
    }
  }
}

创建完成后即可看到mapping中已经有分词器了:

{
  "abc" : {
    "mappings" : {
      "abc" : {
        "properties" : {
          "content" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            },
            "analyzer" : "standard"
          }
        }
      }
    }
  }
}

如果想使用停用词之类的自己建立一个新规则的分词器可以使用setting:

PUT my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "std_english": {
          "type":      "standard",
          "stopwords": "_english_"
        }
      }
    }
  }
}

这样会建立一个名为 std_englist 的分词器。在通过上述方式设定即可。

然而我并没有找到可以修改已经建立index的分词器的方法,如果各位师父们有知识请教我一下qwq

0x03 一些tips

First

在查询时可以先使用_mapping查看整个index的映射关系,field的类型,这样更好确定怎么查,查哪些。如:

{
  "test" : {
    "mappings" : {
      "tesst" : {
        "properties" : {
          "content" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          }
        }
      },
      "test" : {
        "properties" : {
          "content" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          }
        }
      }
    }
  }
}

在这里就可以看到,没有指定分词器,则使用的是默认分词器。只有一个field,名字是content。类型是文本,则查询时根据需求选择用text查询还是keyword查询。这样可以更好确定查询语句怎么写。

Second

由于match,match_phrase,term,regexp等都是boolean型查询,则可以通过must,should,boolean等组合进行联合查询,更方便快捷高效。

如:

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "_index": "360"
          }
        },
        {
          "match_all": {}
        },
        {
          "regexp": {
            "content.keyword": ".*360.cn.*"
          }
        }
      ]
    }
  }
}

final

推荐一个chrome插件,如果连看都不想看就直接用这个生成查询就可以了:mirage