×

Elasticsearch_dsl(python)的搜索|查询|聚合操作实例

穆琪 穆琪 发表于2018-08-08 02:21:22 浏览792 评论0

抢沙发发表评论

# 相关导入 import time from elasticsearch import Elasticsearch from elasticsearch_dsl import Search

# 创建相关实例
es = Elasticsearch()
# using参数是指定Elasticsearch实例对象,index指定索引,可以缩小范围,index接受一个列表作为多个索引,且也可以用正则表示符合某种规则的索引都可以被索引,如index=["bank", "banner", "country"]又如index=["b*"]后者可以同时索引所有以b开头的索引,search中同样可以指定具体doc-type
s = Search(using=es, index="time_appid_placementid_country")

# 添加索引,其中index和doc_type自己指定一个值即可,id可以指定,如果没有指定elasticsearch会随机分配,body即为索引的内容
dict_1 = {"any": "test", "timepstamp": "ddd"}
es.index(index="bank", doc_type="account", id="qwe", body=dict_1)

# 通过id获取特定文档,各参数的意义同上
res = es.get(index="bank", doc_type="account", id=1)

# 根据字段查询,可以多个查询条件叠加,hightlight可以指定高亮,但是我的没有出现高亮,对数据处理没啥用不去深究
# res_2 = s.query("match", gender="F").query("match", age="32").highlight("age").execute()

# 用Q()对象查询多个对象,在多个字段中,fields是一个列表,可以存放多个field,query为所要查询的值,如果要查询多个值可以用空格隔开(似乎查询的时候Q对象只接受同种类型的数据,如果文本和数字混杂在一块就会报错,建立查询语句出错,有待考察,如query="Amber 11"就会失败,fields也是一样,另外query可以接受单个数字的查询,如果是多个同样会报相同的错误)

# Q()第一个参数是查询方法,具体用法及其他方法可以参考elasticsearch的官方文档

q = Q("multi_match", query="Amber Hattie", fields=["firstname"])
res_3 = s.query(q).execute()

# 搜索,q是指定搜索内容,可以看到空格对q查询结果没有影响,size指定个数,from_指定起始位置,q用空格隔开可以多个查询也可以限定返回结果的字段,filter_path可以指定需要显示的数据,如本例中显示在最后的结果中的只有_id和_type

res_3 = es.search(index="bank", q="Holmes", size=1, from_=1)
res_4 = es.search(index="bank", q=" 39225    5686 ", size=1000, filter_path=['hits.hits._id', 'hits.hits._type'])
res_4 = es.search(index="bank", q=" 39225    5686 ", size=1000)


# 直接执行Search()会默认搜索所有数据
# res_5 = s.execute()

# 排除
s.exclude()

# 删除索引
es.indices.delete(index="test-index", ignore=[400, 404])

# 查询,match指定操作方法,country="all",指定查询值,country为要查询的值所在的field,
s = s.query("match", country="all")
# execute()为执行以上操作
res = s.execute()
# to_dict可以把结果转化成字典
print(res.to_dict())

# 下面的过滤聚合等操作也是同样,不再赘述

# 过滤,在此为范围过滤,range是方法,timestamp是所要查询的field的名字,gte意为大于等于,lt意为小于,根据需要设定即可(似乎过滤只能接受数字形式的内容,如果是文本就会返回空)
# 关于term和match的区别,term是精确匹配,match会模糊化,会进行分词,返回匹配度分数,(term查询字符串之接受小写字母,如果有大写会返回空即没有命中,match则是不区分大小写都可以进行查询,返回结果也一样)

实例1:范围查询
s = s.filter("range", timestamp={"gte": 0, "lt": time.time()}).query("match", country="in")
实例2:普通过滤
res_3 = s.filter("terms", balance_num=["39225", "5686"]).execute()

# 聚合,聚合可以放在查询,过滤等操作的后面叠加,需要加aggs,bucket即为分组,其中第一个参数是分组的名字,自己指定即可,第二个参数是方法,第三个是指定的field,metric也是同样,metric的方法有sum、avg、max、min等等,但是需要指出的是有两个方法可以一次性返回这些值,stats和extended_stats,后者还可以返回方差等值,很方便,此过程可能会出现一些错误,具体见本文最后相关bug

# 实例1
s.aggs.bucket("per_country", "terms", field="timestamp").metric("sum_click", "stats", field="click").metric("sum_request", "stats", field="request")

# 实例2
s.aggs.bucket("per_age", "terms", field="click.keyword").metric("sum_click", "stats", field="click")

# 实例3
s.aggs.metric("sum_age", "extended_stats", field="impression")

# 实例4
s.aggs.bucket("per_age", "terms", field="country.keyword")
# 最后依然是要execute,此处注意s.aggs......的操作不能用变量接收(如res=s.aggs......的操作就是错误的),聚合的结果会在res中显示

# 实例5
a = A("range", field="account_number", ranges=[{"to": 10}, {"from": 11, "to": 21}])
# 此聚合是根据区间进行聚合
res = s.execute()

高级设置

1.设置分片数量

s = Index("your_index", using=your_elasticsearch)
s.settings(number_of_shards=20)
s.save()
注:查到较早的资料显示,官方建议每个node分配的shards最好不要超过3个,shards根据node来进行分配,过多的shards会带来I/O压力,
同样不利于查询速度,但是目前的elasticsearch如果没有指定shards数量则会默认分配5个。
2.设置mapping,数据类型的映射

需要指出,elasticsearch中一旦建立好索引之后,mapping就不可以再更改,如果想更改mapping,比如原来的数据是str或text类型,
现在想改成int或者ip类型,此时只有一个办法,就是删除原来的索引,重新新建一个索引。
官方解释如下:类似一个数据库,显然你不能更改一个已经建好的数据表的字段类型,没毛病。
3.es索引的方式

可能有时候你会发现一个问题,用term或terms进行查询文本信息的时候竟然没有返回任何数据,例如:需要查询elasticsearch-dsl,Elasticseach,
elasticsearch client这类文本信息,结果都是查不到的,原因不在于term方法,而是因为es建立索引的方式,
es默认建立索引的时候会是analyzed,这个设置会对写入es的数据进行分词并全部转换成小写,这样做是为了方便进行全文搜索,
如elasticsearch-dsl存储的时候是elasticsearch和dsl分开的,但有时候我们需要进行精确查询,这个守候需要设置索引为not_analyzed,
此设置下不会进行分词处理,但显然不利于全文搜索,
查到有人的解决方法是,设置两个存储相同内容的field,一个设置analyzed,另一个设置not_analyzed,
一个用来精确查询,另一个用来全文搜索。但是也可以为每个字段设置两个index,一个是text类型,一个keyword类型。
另外如果对数据内容没有太大要求的时候,可以再写入es之前对数据进行处理,过滤掉“-“、空格等非单词字符,
如Elasticsearch-dsl client直接存储成Elasticsearchdslclient

在elasticsearch-dsl中没有找到设置not_analyzed的接口,原因是在elasticsearch-dsl只需要设置类型为keyword即可,
在此还有一个高级功能,一个field也可以设置多个类型,代码如下:

    es = Elasticsearch(hosts=es_hosts)
    m = Mapping(your_type)
    m.field("country", "keyword")
    m.field("province", "keyword")
    m.field("phone_brand", "keyword", field={"use_for_search": Text() **1**})
    m.field("phone_model", "keyword")
    m.save(es_index, using=es)

    i = Index(_es_index, using=es)
    print(i.get_mapping())

**1**处的类型需要从elasticsearch_dsl导入,其他类型可以自行查找。
多个类型怎么用呢?指定field的同时指定类型即可,
例如我想对phone_brand进行全文搜索,这个时候就不希望精确查询了,phone_brand换成phone_brand.use_for_search即可,前面的例子中keyword就是这个原因。

性能优化

1.过滤比查询要快,因为过滤不需要计算相关性分数,相关性分数的计算也会浪费很多时间;
2.不得不提一下range过滤,这个方法能不用就不要用,相当消耗时间,我去除这个方法之后时间快了一半不止;
3.对数据进行分类,分成几个大类,分别建立其索引,这样速度也会快很多;
4.精简数据,不需要的数据直接舍弃

那些遇到的bug

bug1
elasticsearch.exceptions.RequestError: TransportError(400, 'search_phase_execution_exception', 'Fielddata is disabled on text fields by default. Set fielddata=true on [country] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead.')
解决:关于fielddata可以自行搜索,默认为text类型的数据不能进行聚合操作,会报出以上错误,有三种解决方法,方法一:如上面实例2中,在相关field中用keyword即可,简单解决,方法二:如错误日志中所示,设置fielddata=true,方法三:官方文档中所示,附上连接https://www.elastic.co/guide/en/elasticsearch/reference/current/fielddata.html#_fielddata_is_disabled_on_literal_text_literal_fields_by_default,方法二和方法三本人没有去尝试,感兴趣的可以自行尝试 bug2
elasticsearch.exceptions.RequestError: TransportError(400, 'search_phase_execution_exception', 'Expected numeric type on field [country.keyword], but got [keyword]')
当执行聚合的运算操作,如求和,最大最小,平均值等的时候,你会发现你用了keyword仍然会报错,错误日志如上,可以看到意思是,希望获得数值,但显然传递给elasticsearch的不是数值类型,原因是向elasticsearch中写入数据的时候,错误对应的field的值类型不是可以进行数学运算的类型,解决方法就是,向elasticsearch中添加索引的时候,相关field值的类型用可以参加数学运算的类型,如int,float等类型,也可以后续更改字段类型,但是不建议这样做,elasticsearch理论上认为字段的mapping一旦被设定就不可更改,更好和更简单的方法就是在写入elasticsearch的时候就指定好类型,具体可以通过以下方法查看字段类型:
from elasticsearch.client import IndicesClient(elastic search_dsl 中同样有以下两个方法,from elastivsearch_dsl import Index)
i = IndicesClient(es)
res_1 = i.get_field_mapping(index="time_appid_placementid_country", doc_type="my-type", fields="country")
# 通过i.get_field_mapping可以查看某个具体字段的类型,text类型的不能参加聚合,但是可以通过以上的keyword解决这个问题

# 亦或通过get_mapping(index=, doc_type=)查看所有字段的类型
bug3
elasticsearch.exceptions.RequestError: TransportError(400, 'search_phase_execution_exception', 'failed to create query
按照以上过滤和查询两个似乎的解释可以解决, 似乎的原因就是es索引的方式,大小写转换等(原因已经找到)