Elasticsearch_dsl(python)的搜尋|查詢|聚合操作例項
阿新 • • 發佈:2019-01-02
# 相關匯入
import time
from elasticsearch import Elasticsearch
from elasticsearch_dsl import Search
- 1
- 2
- 3
- 4
# 建立相關例項 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")
- 1
- 2
- 3
- 4
# 新增索引,其中index和doc_type自己指定一個值即可,id可以指定,如果沒有指定elasticsearch會隨機分配,body即為索引的內容
dict_1 = {"any": "test", "timepstamp": "ddd"}
es.index(index="bank", doc_type="account", id="qwe", body=dict_1)
- 1
- 2
- 3
# 通過id獲取特定文件,各引數的意義同上
res = es.get(index="bank", doc_type="account", id=1)
- 1
- 2
# 根據欄位查詢,可以多個查詢條件疊加,hightlight可以指定高亮,但是我的沒有出現高亮,對資料處理沒啥用不去深究 # res_2 = s.query("match", gender="F").query("match", age="32").highlight("age").execute()
- 1
- 2
# 用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()
- 1
- 2
- 3
- 4
- 5
- 6
# 搜尋,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)
- 1
- 2
- 3
- 4
- 5
# 直接執行Search()會預設搜尋所有資料
# res_5 = s.execute()
- 1
- 2
- 3
# 排除
s.exclude()
- 1
- 2
# 刪除索引
es.indices.delete(index="test-index", ignore=[400, 404])
- 1
- 2
# 查詢,match指定操作方法,country="all",指定查詢值,country為要查詢的值所在的field,
s = s.query("match", country="all")
# execute()為執行以上操作
res = s.execute()
# to_dict可以把結果轉化成字典
print(res.to_dict())
# 下面的過濾聚合等操作也是同樣,不再贅述
- 1
- 2
- 3
# 過濾,在此為範圍過濾,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()
- 1
- 2
# 聚合,聚合可以放在查詢,過濾等操作的後面疊加,需要加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
- 2
- 3
- 4
- 5
高階設定
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個。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
2.設定mapping,資料型別的對映
需要指出,elasticsearch中一旦建立好索引之後,mapping就不可以再更改,如果想更改mapping,比如原來的資料是str或text型別,
現在想改成int或者ip型別,此時只有一個辦法,就是刪除原來的索引,重新新建一個索引。
官方解釋如下:類似一個數據庫,顯然你不能更改一個已經建好的資料表的欄位型別,沒毛病。
- 1
- 2
- 3
- 4
- 5
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
- 3
- 4
- 5
- 6
效能優化
1.過濾比查詢要快,因為過濾不需要計算相關性分數,相關性分數的計算也會浪費很多時間;
2.不得不提一下range過濾,這個方法能不用就不要用,相當消耗時間,我去除這個方法之後時間快了一半不止;
3.對資料進行分類,分成幾個大類,分別建立其索引,這樣速度也會快很多;
4.精簡資料,不需要的資料直接捨棄
- 1
- 2
- 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.')
- 1
bug2
elasticsearch.exceptions.RequestError: TransportError(400, 'search_phase_execution_exception', 'Expected numeric type on field [country.keyword], but got [keyword]')
- 1
當執行聚合的運算操作,如求和,最大最小,平均值等的時候,你會發現你用了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=)檢視所有欄位的型別
- 1
- 2
- 3
bug3
elasticsearch.exceptions.RequestError: TransportError(400, 'search_phase_execution_exception', 'failed to create query
- 1
按照以上過濾和查詢兩個似乎的解釋可以解決,
似乎的原因就是es索引的方式,大小寫轉換等(原因已經找到)