转载整理-Python-ElasticSearch,python对ES进行写入、更新、删除、搜索

py-es官方文档:https://elasticsearch-py.readthedocs.io/en/master/api.html

es官方中文:http://doc.codingdict.com/elasticsearch/1/

es官方英文:https://www.elastic.co/guide/cn/elasticsearch/guide/current/_most_important_queries.html

转载:

  1. Python-ElasticSearch,python对ES进行写入、更新、删除、搜索

  2. Elasticsearch学习(三)————元数据(_index、_type、_id、_score、_source)

  3. ES基本查询总结

参考:

  1. ES 22 - Elasticsearch中如何进行日期(数值)范围查询
  2. 上面那个format 有问题

[TOC]

数据理解

元数据

==概念:==元数据(Metadata),又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。元数据算是一种电子式目录,为了达到编制目录的目的,必须在描述并收藏数据的内容或特色,进而达成协助数据检索的目的。元数据是关于数据的组织、数据域及其关系的信息,简言之,元数据就是关于数据的数据。

1.1. _index

  1. 代表一个document存储在哪个index中。
  2. 类似的数据(document)放在同一个index,不同类型的数据放在不同的index。
  3. 索引名必须是小写,不能用下划线(_)开头,不能包含逗号。

1.2. _type

  1. 代表document属于index下 的哪个类别(type)。
  2. 一个index通常包含多个type。
  3. type名称可以大写或小写,不能用下划线(_)开头,不能包含逗号。

1.3. _id

  1. 代表document的唯一标识,与index和type一起确定一个唯一的document。
  2. 可以手动指定,也可es自动生成。手动指定:PUT /index/type/id 自动生成: PUT /index/type/ 生成base64编码的20长度的字符串ID,分布式集群下不可能重复。

1.4. _score

  1. 相关度分数:匹配程度,分数越高越相关。

1.5. _source

  1. 代表一个document,是一个json对象({json}),包含一个实例对象

python 常用es方法

python-es 常用 增删改 方法

创建,删除 index索引 (类似于数据库)

1
2
3
4
5
6
7
8
9
#创建索引,索引的名字是my-index,如果已经存在了,就返回个400,
#这个索引可以现在创建,也可以在后面插入数据的时候再临时创建
es.indices.create(index='my-index',ignore)
#但是我在使用中发现好像没有ignore这个参数了
# ignore 400 cause by IndexAlreadyExistsException when creating an index
es.indices.create(index='my-index', ignore=400)

# ignore 404 and 400
es.indices.delete(index='my-index', ignore=[400, 404])

插入数据

index 为 my-index, doc_type 为 test-type,id 为 01,其他内容用body

1
2
3
#插入数据,(这里省略插入其他两条数据,后面用)
es.index(index="my-index",doc_type="test-type",id='01',body={"any":"data01","timestamp":datetime.now()})

get获取数据

1
2
3
4
#查询数据,两种get and search
#get获取
res = es.get(index="my-index", doc_type="test-type", id=01)
es.get(index='indexName', doc_type='typeName', id='idValue')

删除数据

1
2
3
delete:删除指定index、type、id的文档

es.delete(index='indexName', doc_type='typeName', id='idValue')

条件删除

1
2
3
4
5
6
7
8
delete_by_query:删除满足条件的所有数据,查询条件必须符合DLS格式

query = {'query': {'match': {'sex': 'famale'}}}# 删除性别为女性的所有文档

query = {'query': {'range': {'age': {'lt': 11}}}}# 删除年龄小于11的所有文档

es.delete_by_query(index='indexName', body=query, doc_type='typeName')

条件更新

1
update_by_query:更新满足条件的所有数据,写法同上删除和查询

批量写入、删除、更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
doc = [
{"index": {}},
{'name': 'jackaaa', 'age': 2000, 'sex': 'female', 'address': u'北京'},
{"index": {}},
{'name': 'jackbbb', 'age': 3000, 'sex': 'male', 'address': u'上海'},
{"index": {}},
{'name': 'jackccc', 'age': 4000, 'sex': 'female', 'address': u'广州'},
{"index": {}},
{'name': 'jackddd', 'age': 1000, 'sex': 'male', 'address': u'深圳'},
]
doc = [
{'index': {'_index': 'indexName', '_type': 'typeName', '_id': 'idValue'}}
{'name': 'jack', 'sex': 'male', 'age': 10 }
{'delete': {'_index': 'indexName', '_type': 'typeName', '_id': 'idValue'}}
{"create": {'_index' : 'indexName', "_type" : 'typeName', '_id': 'idValue'}}
{'name': 'lucy', 'sex': 'female', 'age': 20 }
{'update': {'_index': 'indexName', '_type': 'typeName', '_id': 'idValue'}}
{'doc': {'age': '100'}}
]
es.bulk(index='indexName', doc_type='typeName', body=doc)

#批量更新也可以采用如下的方式进行json拼装,最后写入
for line in list:
action = {
"_index": self.index_name,
"_type": self.index_type,
"_id": i, #_id 也可以默认生成,不赋值
"_source": {
"date": line['date'],
"source": line['source'].decode('utf8'),
"link": line['link'],
"keyword": line['keyword'].decode('utf8'),
"title": line['title'].decode('utf8')}
}
i += 1
ACTIONS.append(action)
success, _ = bulk(self.es, ACTIONS, index=self.index_name, raise_on_error=True)

python-es 常用查询方法

match_all 查询所有

1
2
3
4
5
6
7
8
9
10
es.search(index="my_index",doc_type="test_type")


# 或者
body = {
"query":{
"match_all":{}
}
}
es.search(index="my_index",doc_type="test_type",body=body)

term与terms 等于 匹配

term 单一匹配关键词
1
2
3
4
5
6
7
8
9
body = {
"query":{
"term":{
"name":"python"
}
}
}
# 查询name="python"的所有数据
es.search(index="my_index",doc_type="test_type",body=body)
terms 匹配多个关键词
1
2
3
4
5
6
7
8
9
10
11
body = {
"query":{
"terms":{
"name":[
"python","android"
]
}
}
}
# 搜索出name="python"或name="android"的所有数据
es.search(index="my_index",doc_type="test_type",body=body)

match与multi_match 包含匹配

match 包含 类似于 contain
1
2
3
4
5
6
7
8
9
10
# match:匹配name包含python关键字的数据
body = {
"query":{
"match":{
"name":"python"
}
}
}
# 查询name包含python关键字的数据
es.search(index="my_index",doc_type="test_type",body=body)
multi_match 多个字段包含 类似于多个contain
1
2
3
4
5
6
7
8
9
10
11
# multi_match:在name和addr里匹配包含深圳关键字的数据
body = {
"query":{
"multi_match":{
"query":"深圳",
"fields":["name","addr"]
}
}
}
# 查询name和addr包含"深圳"关键字的数据
es.search(index="my_index",doc_type="test_type",body=body)

ids

ids 搜索指定id的数据
1
2
3
4
5
6
7
8
9
10
11
12
body = {
"query":{
"ids":{
"type":"test_type",
"values":[
"1","2"
]
}
}
}
# 搜索出id为1或2d的所有数据
es.search(index="my_index",doc_type="test_type",body=body)

复合查询bool

bool有3类查询关系,must(都满足),should(其中一个满足),must_not(都不满足)

must(都满足)
should(其中一个满足)
must_not(都不满足)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
body = {
"query":{
"bool":{
"must":[
{
"term":{
"name":"python"
}
},
{
"term":{
"age":18
}
}
]
}
}
}
# 获取name="python"并且age=18的所有数据
es.search(index="my_index",doc_type="test_type",body=body)

切片式查询

可以用来分页

利用 from 和 size 来 控制数据

1
2
3
4
5
6
7
8
9
body = {
"query":{
"match_all":{}
}
"from":2 # 从第二条数据开始
"size":4 # 获取4条数据
}
# 从第2条数据开始,获取4条数据
es.search(index="my_index",doc_type="test_type",body=body)

范围查询

range 利用 gte或者lte 查询范围

如果是查询时间 ,还要用到format ,对日期进行格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
body = {
"query":{
"range":{
"age":{
"gte":18, # >=18
"lte":30 # <=30
},
"post_date": {
"gte": "2/1/2018",
"lte": "2019",
"format": "dd/MM/yyyy||yyyy"
}
}
}
}
# 查询18<=age<=30的所有数据
es.search(index="my_index",doc_type="test_type",body=body)

前缀查询

prefix (查询前缀为xx的数据)
1
2
3
4
5
6
7
8
9
body = {
"query":{
"prefix":{
"name":"p"
}
}
}
# 查询前缀为"赵"的所有数据
es.search(index="my_index",doc_type="test_type",body=body)

通配符查询 类似like

wildcard (查询name以*id为后缀的所有数据)
1
2
3
4
5
6
7
8
9
body = {
"query":{
"wildcard":{
"name":"*id"
}
}
}
# 查询name以id为后缀的所有数据
es.search(index="my_index",doc_type="test_type",body=body)

排序 对结果进行排序

sort
1
2
3
4
5
6
7
8
9
10
body = {
"query":{
"match_all":{}
}
"sort":{
"age":{ # 根据age字段升序排序
"order":"asc" # asc升序,desc降序
}
}
}

响应过滤 filter_path (获取指定字段)

filter_path
1
2
3
4
5
6
# 只需要获取_id数据,多个条件用逗号隔开
es.search(index="my_index",doc_type="test_type",filter_path=["hits.hits._id"])

# 获取所有数据
es.search(index="my_index",doc_type="test_type",filter_path=["hits.hits._*"])

执行查询并获取该查询的匹配数 count

count
1
2
# 获取数据量
es.count(index="my_index",doc_type="test_type")

度量类聚合 (最小值 最大值)

获取最小值 min
1
2
3
4
5
6
7
8
9
10
11
12
13
14
body = {
"query":{
"match_all":{}
},
"aggs":{ # 聚合查询
"min_age":{ # 最小值的key
"min":{ # 最小
"field":"age" # 查询"age"的最小值
}
}
}
}
# 搜索所有数据,并获取age最小的值
es.search(index="my_index",doc_type="test_type",body=body)
获取最大值 max
1
2
3
4
5
6
7
8
9
10
11
12
13
14
body = {
"query":{
"match_all":{}
},
"aggs":{ # 聚合查询
"max_age":{ # 最大值的key
"max":{ # 最大
"field":"age" # 查询"age"的最大值
}
}
}
}
# 搜索所有数据,并获取age最大的值
es.search(index="my_index",doc_type="test_type",body=body)
获取和 sum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
body = {
"query":{
"match_all":{}
},
"aggs":{ # 聚合查询
"sum_age":{ # 和的key
"sum":{ # 和
"field":"age" # 获取所有age的和
}
}
}
}
# 搜索所有数据,并获取所有age的和
es.search(index="my_index",doc_type="test_type",body=body)
获取平均值 sum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
body = {
"query":{
"match_all":{}
},
"aggs":{ # 聚合查询
"avg_age":{ # 平均值的key
"sum":{ # 平均值
"field":"age" # 获取所有age的平均值
}
}
}
}
# 搜索所有数据,获取所有age的平均值
es.search(index="my_index",doc_type="test_type",body=body)

es 和 mysql数据对比结构图



Elasticsearch中当我们设置Mapping(分词器、字段类型)完毕后,就可以按照设定的方式导入数据。

有了数据后,我们就需要对数据进行检索操作。根据实际开发需要,往往我们需要支持包含但不限于以下类型的检索:
1)精确匹配,类似mysql中的 “=”操作;
2)模糊匹配,类似mysql中的”like %关键词% “查询操作;
3)前缀匹配;
4)通配符匹配;
5)正则表达式匹配;
6)跨索引匹配;
7)提升精读匹配。

细数一下,我们的痛点在于:
1)ES究竟支持哪些检索操作?
2)如何实现ES精确值检索、指定索引检索、全文检索?

这些就是本文着重参考ES最新官方文档,针对ES5.X版本探讨的内容。

0、检索概览

检索子句的行为取决于查询应用于过滤(filter)上下文还是查询/分析(query)上下文。

过滤上下文——对应于结构化检索

1
1)核心回答的问题是:“这个文档是否符合这个查询条款?”  2)答案是简单的是或否,不计算分数。  3)过滤器上下文主要用于过滤结构化数据。类似于Mysql中判定某个字段是否存在: 

例如:
a. 时间戳字段:是否属于2015年或2016年?
b. 状态字段:是否设置为“已发布”?

经常使用的过滤器将被Elasticsearch自动缓存,以加快性能

分析上下文——对应于全文检索
1)核心回答了“本文档与此查询子句是否匹配?”的问题。

2)除了决定文档是否匹配之外,查询子句还会计算一个_score,表示文档与其他文档的匹配程度。

综合应用场景如下:

1
GET /_search{  "query": {  "bool": {  "must": [  { "match": { "title": "Search" }},  { "match": { "content": "Elasticsearch" }}  ],  "filter": [  { "term": { "status": "published" }},  { "range": { "publish_date": { "gte": "2015-01-01" }}}  ]  }  }}

以上检索,title中包含”Search”并且content中包含 “Elasticsearch”,status中精确匹配”published”,并且publish_date 大于“2015-01-01”的全部信息。

以下,以“脑图”的形式直观展示检索分类。


其中,3-7根据我随着我开发深入再做更新。

以下内容的原文需要参考ES官方文档(随着版本变化,后续会有更新)

1、结构化检索

针对字段类型: 日期、时间、数字类型,以及精确的文本匹配。
结构化检索特点:
* 1)结构化查询,我们得到的结果 总是 非是即否,要么存于集合之中,要么存在集合之外。
* 2)结构化查询不关心文件的相关度或评分;它简单的对文档包括或排除处理。

1.1 精确值查找

1.1.1 单个精确值查找(term query)

term 查询会查找我们指定的精确值。term 查询是简单的,它接受一个字段名以及我们希望查找的数值。

想要类似mysql中如下sql语句的查询操作:

SELECT document FROM products WHERE price = 20;
DSL写法:

1
GET /my_store/products/_search{  "query" : {  "term" : {  "price" : 20  }  }}

当进行精确值查找时, 我们会使用过滤器(filters)。过滤器很重要,因为它们执行速度非常快,不会计算相关度(直接跳过了整个评分阶段)而且很容易被缓存。如下: 使用 constant_score 查询以非评分模式来执行 term 查询并以一作为统一评分。

1
GET /my_store/products/_search{  "query" : {  "constant_score" : {  "filter" : {  "term" : {  "price" : 20  }  }  }  }}

注意:5.xES中,对于字符串类型,要进行精确值匹配。需要讲类型设置为text和keyword两种类型。mapping设置如下:

1
POST testindex/testtype/_mapping{   "testtype ":{  "properties":{  "title":{  "type":"text",  "analyzer":"ik_max_word",  "search_analyzer":"ik_max_word",  "fields":{  "keyword":{  "type":"keyword"  }  }  }}}

精确值java api jest使用方法:
searchSourceBuilder.query(QueryBuilders.termQuery(“text.keyword”, “来自新华社的报道”));

1.1.2 布尔过滤器

一个 bool 过滤器由三部分组成:

1
{   "bool" : {      "must" :     [],      "should" :   [],      "must_not" : [],      "filter":    []   }}

must ——所有的语句都 必须(must) 匹配,与 AND 等价。
must_not ——所有的语句都 不能(must not) 匹配,与 NOT 等价。
should ——至少有一个语句要匹配,与 OR 等价。
filter——必须匹配,运行在非评分&过滤模式。
就这么简单! 当我们需要多个过滤器时,只须将它们置入 bool 过滤器的不同部分即可。

举例:

1
GET /my_store/products/_search{  "query" : {  "filtered" : {  "filter" : {  "bool" : {  "should" : [  { "term" : {"price" : 20}},   { "term" : {"productID" : "XHDK-A-1293-#fJ3"}}   ],  "must_not" : {  "term" : {"price" : 30}   }  }  }  }  }}

1.1.3 多个值精确查找(terms query)

1
{  "terms" : {  "price" : [20, 30]  }}

如上,terms是包含的意思,包含20或者包含30。
如下实现严格意义的精确值检索, tag_count代表必须匹配的次数为1。

1
GET /my_index/my_type/_search{  "query": {  "constant_score" : {  "filter" : {  "bool" : {  "must" : [  { "term" : { "tags" : "search" } },  { "term" : { "tag_count" : 1 } }  ]  }  }  }  }}

1.2 范围检索(range query)

range 查询可同时提供包含(inclusive)和不包含(exclusive)这两种范围表达式,可供组合的选项如下:

1
gt: > 大于(greater than)lt: < 小于(less than)gte: >= 大于或等于(greater than or equal to)lte: <= 小于或等于(less than or equal to

类似Mysql中的范围查询:

1
SELECT documentFROM   productsWHERE  price BETWEEN 20 AND 40

ES中对应的DSL如下:

1
GET /my_store/products/_search{  "query" : {  "constant_score" : {  "filter" : {  "range" : {  "price" : {  "gte" : 20,  "lt" : 40  }  }  }  }  }}

1.3 存在与否检索(exist query)

mysql中,有如下sql:
SELECT tags FROM posts WHERE tags IS NOT NULL;

ES中,exist查询某个字段是否存在:

1
GET /my_index/posts/_search{    "query" : {        "constant_score" : {            "filter" : {                "exists" : { "field" : "tags" }            }        }    }}

若想要exist查询能匹配null类型,需要设置mapping:

1
"user": {  "type": "keyword",  "null_value": "_null_"  }

missing查询在5.x版本已经不存在,改成如下的判定形式:

1
GET /_search{    "query": {        "bool": {            "must_not": {                "exists": {                    "field": "user"                }            }        }    }}

1.4 前缀检索( Prefix Query )

匹配包含 not analyzed 的前缀字符:

1
GET /_search{ "query": {  "prefix" : { "user" : "ki" }  }}

1.5 通配符检索( wildcard query)

匹配具有匹配通配符表达式( (not analyzed )的字段的文档。 支持的通配符:
1),它匹配任何字符序列(包括空字符序列);
2)?,它匹配任何单个字符。
请注意,此查询可能很慢,因为它需要遍历多个术语。
为了防止非常慢的通配符查询,通配符不能以任何一个通配符
或?开头。
举例:

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

1.6 正则表达式检索(regexp query)

正则表达式查询允许您使用正则表达式术语查询。
举例如下:

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

注意: 的匹配会非常慢,你需要使用一个长的前缀,
通常类似.
?+通配符查询的正则检索性能会非常低。

1.7 模糊检索(fuzzy query)

模糊查询查找在模糊度中指定的最大编辑距离内的所有可能的匹配项,然后检查术语字典,以找出在索引中实际存在待检索的关键词。
举例如下:

1
GET /_search{  "query": {  "fuzzy" : { "user" : "ki" }  }}

1.8 类型检索(type query)

举例:

1
GET /my_index/_search{  "query": {  "type" : {  "value" : "xext"  }  }}

已验证,检索索引my_index中,type为xext的全部信息。

1.9 Ids检索(ids query)

返回指定id的全部信息。

1
GET /my_index/_search{  "query": {  "ids" : {  "type" : "xext",  "values" : ["2", "4", "100"]  }  }}

2、全文检索

高级全文查询通常用于在全文本字段(如电子邮件正文)上运行全文查询。他们了解如何对被查询的字段进行分析,并在执行前将每个字段的分析器(或search_analyzer)应用于查询字符串。

2.1 匹配检索(match query)

匹配查询接受文本/数字/日期类型,分析它们,并构造查询。
1)匹配查询的类型为boolean。 这意味着分析所提供的文本,并且分析过程从提供的文本构造一个布尔查询,
可以将运算符标志设置为或以控制布尔子句(默认为或);
2)文本分析取决于mapping中设定的analyzer(中文分词,我们默认选择ik分词器);
3) fuzziness——模糊性允许基于被查询的字段的类型进行模糊匹配;
4)”operator”: “and”——匹配与操作(默认或操作);
5) “minimum_should_match”: “75%”——这让我们可以指定必须匹配的词项数用来表示一个文档是否相关。
举例:

1
GET /_search{    "query": {        "match" : {            "message" : {                "query" : "this is a test",                "operator" : "and"            }        }    }}

2.2 匹配解析检索 match_phrase query

match_phrase查询分析文本,并从分析文本中创建短语查询。
类似 match 查询, match_phrase 查询首先将查询字符串解析成一个词项列表,然后对这些词项进行搜索,但只保留那些包含 全部 搜索词项,且 位置 与搜索词项相同的文档。
举例如下:对于 quick fox 的短语搜索可能不会匹配到任何文档,因为没有文档包含的 quick 词之后紧跟着 fox 。

1
GET /my_index/my_type/_search{  "query": {  "match_phrase": {  "title": "quick brown fox"  }  }}

2.3 匹配解析前缀检索(match_phrase_prefix)

用户已经渐渐习惯在输完查询内容之前,就能为他们展现搜索结果,这就是所谓的 即时搜索(instant search) 或 输入即搜索(search-as-you-type) 。
不仅用户能在更短的时间内得到搜索结果,我们也能引导用户搜索索引中真实存在的结果。
例如,如果用户输入 johnnie walker bl ,我们希望在它们完成输入搜索条件前就能得到:
Johnnie Walker Black Label 和 Johnnie Walker Blue Label 。

match_phrase_prefix与match_phrase相同,除了它允许文本中最后一个术语的前缀匹配。
举例:

1
GET / _search{    “query”:{        “match_phrase_prefix”:{            “message”:“quick brown f”        }    }}

2.4 多字段匹配检索( multi_match query)

multi_match 查询为能在多个字段上反复执行相同查询提供了一种便捷方式。
默认情况下,查询的类型是 best_fields, 这表示它会为每个字段生成一个 match 查询。
举例1:”fields”: “_title”
——任何与模糊模式正则匹配的字段都会被包括在搜索条件中, 例如可以左侧的方式同时匹配 book_title 、 chapter_title 和 section_title (书名、章名、节名)这三个字段。
举例2: “fields”: [ “
_title”, “chapter_title^2” ]
——可以使用 ^ 字符语法为单个字段提升权重,在字段名称的末尾添加 ^boost , 其中 boost 是一个浮点数。
举例3:”fields”: [ “first_name”, “last_name” ],
“operator”: “and”
——两个字段必须都包含。

1
GET /_search{  "query": {  "multi_match" : {  "query": "this is a test",  "fields": [ "subject", "message" ]  }  }}

2.5 字符串检索(query_string)

一个使用查询解析器解析其内容的查询。
query_string查询提供了以简明的简写语法执行多匹配查询 multi_match queries ,布尔查询 bool queries ,提升得分 boosting ,模糊匹配 fuzzy matching ,通配符 wildcards ,正则表达式 regexp 和范围查询 range queries 的方式。
支持参数达10几种。

1
GET /_search{  "query": {  "query_string" : {  "default_field" : "content",  "query" : "this AND that OR thus"  }  }}

2.6 简化字符串检索(simple_query_string)

一个使用SimpleQueryParser解析其上下文的查询。 与常规query_string查询不同,simple_query_string查询永远不会抛出异常,并丢弃查询的无效部分。
举例:

1
GET /_search{    "query": {        "simple_query_string" : {            "fields" : ["content"],            "query" : "foo bar -baz"        }    }}

支持的操作如下:
1)+表示AND操作
2)| 表示OR操作
3)- 否定操作
4)*在术语结束时表示前缀查询
5)(和)表示优先

3、小结

有的博友可能会问,这和ES官网API有什么区别。
仔细对比你会发现,ES的中文文档是根据2.X版本翻译的,ES的英文文档一个版本是没有更新到5.X版本,另一个已经更新