elasticsearch補全功能之只補全篩選後的部分資料context suggester
官方文件https://www.elastic.co/guide/en/elasticsearch/reference/5.0/suggester-context.html
下面所有演示基於elasticsearch5.x和Python3.x
最近專案使用elasticsearch的補全功能時,需要對於所有文章(article)的作者名字(author)的搜尋做補全,文章的mapping大致如下
ARTICLE = { 'properties': { 'id': { 'type': 'integer', 'index': 'not_analyzed', }, 'author': { 'type': 'text', }, 'author_completion': { 'type': 'completion', }, 'removed': { 'type': 'boolean', } } } MAPPINGS = { 'mappings': { 'article': ARTICLE, } }
現在的需求是,針對於下架狀態removed為True的不做補全提示。
作為演示先插入部分資料,程式碼如下
#!/usr/bin/env python # -*- coding: utf-8 -*- from elasticsearch.helpers import bulk from elasticsearch import Elasticsearch ES_HOSTS = [{'host': 'localhost', 'port': 9200}, ] ES = Elasticsearch(hosts=ES_HOSTS) INDEX = 'test_article' TYPE = 'article' ARTICLE = { 'properties': { 'id': { 'type': 'integer', 'index': 'not_analyzed', }, 'author': { 'type': 'text', }, 'author_completion': { 'type': 'completion', }, 'removed': { 'type': 'boolean', } } } MAPPINGS = { 'mappings': { 'article': ARTICLE, } } def create_index(): """ 插入資料前建立對應的index """ ES.indices.delete(index=INDEX, ignore=404) ES.indices.create(index=INDEX, body=MAPPINGS) def insert_data(): """ 新增測試資料 :return: """ test_datas = [ { 'id': 1, 'author': 'tom', 'author_completion': 'tom', 'removed': False }, { 'id': 2, 'author': 'tom_cat', 'author_completion': 'tom_cat', 'removed': True }, { 'id': 3, 'author': 'kitty', 'author_completion': 'kitty', 'removed': False }, { 'id': 4, 'author': 'tomato', 'author_completion': 'tomato', 'removed': False }, ] bulk_data = [] for data in test_datas: action = { '_index': INDEX, '_type': TYPE, '_id': data.get('id'), '_source': data } bulk_data.append(action) success, failed = bulk(client=ES, actions=bulk_data, stats_only=True) print('success', success, 'failed', failed) if __name__ == '__main__': create_index() insert_data()
成功插入4條測試資料,下面測試獲取作者名稱補全建議,程式碼如下
def get_suggestions(keywords): body = { # 'size': 0, # 這裡是不返回相關搜尋結果的欄位,如author,id等,作為測試這裡返回 '_source': 'suggest', 'suggest': { 'author_prefix_suggest': { 'prefix': keywords, 'completion': { 'field': 'author_completion', 'size': 10, } } }, # 對於下架資料,我單純的以為加上下面的篩選就行了 'query': { 'term': { 'removed': False } } } suggest_data = ES.search(index=INDEX, doc_type=TYPE, body=body) return suggest_data if __name__ == '__main__': # create_index() # insert_data() suggestions = get_suggestions('t') print(suggestions) """ suggestions = { 'took': 0, 'timed_out': False, '_shards': { 'total': 5, 'successful': 5, 'skipped': 0, 'failed': 0 }, 'hits': { 'total': 3, 'max_score': 0.6931472, 'hits': [ {'_index': 'test_article', '_type': 'article', '_id': '4', '_score': 0.6931472, '_source': {}}, {'_index': 'test_article', '_type': 'article', '_id': '1', '_score': 0.2876821, '_source': {}}, {'_index': 'test_article', '_type': 'article', '_id': '3', '_score': 0.2876821, '_source': {}}]}, 'suggest': { 'author_prefix_suggest': [{'text': 't', 'offset': 0, 'length': 1, 'options': [ {'text': 'tom', '_index': 'test_article', '_type': 'article', '_id': '1', '_score': 1.0, '_source': {}}, {'text': 'tom_cat', '_index': 'test_article', '_type': 'article', '_id': '2', '_score': 1.0, '_source': {}}, {'text': 'tomato', '_index': 'test_article', '_type': 'article', '_id': '4', '_score': 1.0, '_source': {}}]}] } } """
發現,removed為True的tom_cat赫然在列,明明加了
'query': { 'term': { 'removed': False } }
卻沒有起作用,難道elasticsearch不支援這種需求!?怎麼可能……
查閱文件發現解決方法為https://www.elastic.co/guide/en/elasticsearch/reference/5.0/suggester-context.html
找到問題所在,首先改造mapping,並重新錄入測試資料如下
#!/usr/bin/env python # -*- coding: utf-8 -*- from elasticsearch.helpers import bulk from elasticsearch import Elasticsearch ES_HOSTS = [{'host': 'localhost', 'port': 9200}, ] ES = Elasticsearch(hosts=ES_HOSTS) INDEX = 'test_article' TYPE = 'article' ARTICLE = { 'properties': { 'id': { 'type': 'integer', 'index': 'not_analyzed' }, 'author': { 'type': 'text', }, 'author_completion': { 'type': 'completion', 'contexts': [ # 這裡是關鍵所在 { 'name': 'removed_tab', 'type': 'category', 'path': 'removed' } ] }, 'removed': { 'type': 'boolean', } } } MAPPINGS = { 'mappings': { 'article': ARTICLE, } } def create_index(): """ 插入資料前建立對應的index """ ES.indices.delete(index=INDEX, ignore=404) ES.indices.create(index=INDEX, body=MAPPINGS) def insert_data(): """ 新增測試資料 :return: """ test_datas = [ { 'id': 1, 'author': 'tom', 'author_completion': 'tom', 'removed': False }, { 'id': 2, 'author': 'tom_cat', 'author_completion': 'tom_cat', 'removed': True }, { 'id': 3, 'author': 'kitty', 'author_completion': 'kitty', 'removed': False }, { 'id': 4, 'author': 'tomato', 'author_completion': 'tomato', 'removed': False }, ] bulk_data = [] for data in test_datas: action = { '_index': INDEX, '_type': TYPE, '_id': data.get('id'), '_source': data } bulk_data.append(action) success, failed = bulk(client=ES, actions=bulk_data, stats_only=True) print('success', success, 'failed', failed) if __name__ == '__main__': create_index() insert_data()
Duang!意想不到的問題出現了
elasticsearch.helpers.BulkIndexError: ('4 document(s) failed to index.', [{'index': {'_index': 'test_article', '_type': 'article', '_id': '1', 'status': 400, 'error': {'type': 'illegal_argument_exception', 'reason': 'Failed to parse context field [removed], only keyword and text fields are accepted'}, 'data': {'id': 1, 'author': 'tom', 'author_completion': 'tom', 'removed': False}}}, {'index': {'_index': 'test_article', '_type': 'article', '_id': '2', 'status': 400, 'error': {'type': 'illegal_argument_exception', 'reason': 'Failed to parse context field [removed], only keyword and text fields are accepted'}, 'data': {'id': 2, 'author': 'tom_cat', 'author_completion': 'tom_cat', 'removed': True}}}, {'index': {'_index': 'test_article', '_type': 'article', '_id': '3', 'status': 400, 'error': {'type': 'illegal_argument_exception', 'reason': 'Failed to parse context field [removed], only keyword and text fields are accepted'}, 'data': {'id': 3, 'author': 'kitty', 'author_completion': 'kitty', 'removed': False}}}, {'index': {'_index': 'test_article', '_type': 'article', '_id': '4', 'status': 400, 'error': {'type': 'illegal_argument_exception', 'reason': 'Failed to parse context field [removed], only keyword and text fields are accepted'}, 'data': {'id': 4, 'author': 'tomato', 'author_completion': 'tomato', 'removed': False}}}])
意思是context只支援keyword和text型別,而上面removed型別為boolean,好吧,再改造mapping,將mapping的removed改為keyword型別……
#!/usr/bin/env python # -*- coding: utf-8 -*- from elasticsearch.helpers import bulk from elasticsearch import Elasticsearch ES_HOSTS = [{'host': 'localhost', 'port': 9200}, ] ES = Elasticsearch(hosts=ES_HOSTS) INDEX = 'test_article' TYPE = 'article' ARTICLE = { 'properties': { 'id': { 'type': 'integer', 'index': 'not_analyzed' }, 'author': { 'type': 'text', }, 'author_completion': { 'type': 'completion', 'contexts': [ # 這裡是關鍵所在 { 'name': 'removed_tab', 'type': 'category', 'path': 'removed' } ] }, 'removed': { 'type': 'keyword', } } } MAPPINGS = { 'mappings': { 'article': ARTICLE, } } def create_index(): """ 插入資料前建立對應的index """ ES.indices.delete(index=INDEX, ignore=404) ES.indices.create(index=INDEX, body=MAPPINGS) def insert_data(): """ 新增測試資料 :return: """ test_datas = [ { 'id': 1, 'author': 'tom', 'author_completion': 'tom', 'removed': 'False' }, { 'id': 2, 'author': 'tom_cat', 'author_completion': 'tom_cat', 'removed': 'True' }, { 'id': 3, 'author': 'kitty', 'author_completion': 'kitty', 'removed': 'False' }, { 'id': 4, 'author': 'tomato', 'author_completion': 'tomato', 'removed': 'False' }, ] bulk_data = [] for data in test_datas: action = { '_index': INDEX, '_type': TYPE, '_id': data.get('id'), '_source': data } bulk_data.append(action) success, failed = bulk(client=ES, actions=bulk_data, stats_only=True) print('success', success, 'failed', failed) if __name__ == '__main__': create_index() insert_data()
mission success。看看錶結構ok
接下來就是獲取補全建議
def get_suggestions(keywords): body = { 'size': 0, '_source': 'suggest', 'suggest': { 'author_prefix_suggest': { 'prefix': keywords, 'completion': { 'field': 'author_completion', 'size': 10, 'contexts': { 'removed_tab': ['False', ] # 篩選removed為'False'的補全 } } } }, } suggest_data = ES.search(index=INDEX, doc_type=TYPE, body=body) return suggest_data if __name__ == '__main__': # create_index() # insert_data() suggestions = get_suggestions('t') print(suggestions) """ suggestions = { 'took': 0, 'timed_out': False, '_shards': { 'total': 5, 'successful': 5, 'skipped': 0, 'failed': 0 }, 'hits': { 'total': 0, 'max_score': 0.0, 'hits': [] }, 'suggest': { 'author_prefix_suggest': [ {'text': 't', 'offset': 0, 'length': 1, 'options': [ {'text': 'tom', '_index': 'test_article', '_type': 'article', '_id': '1', '_score': 1.0, '_source': {}, 'contexts': {'removed_tab': ['False']}}, {'text': 'tomato', '_index': 'test_article', '_type': 'article', '_id': '4', '_score': 1.0, '_source': {}, 'contexts': {'removed_tab': ['False']}}]}]}} """
發現,removed為'True'的tom_cat被篩選掉了,大功告成!