前面在 Python 連接 es 的操作中,有過(guò)介紹如何使用 Python 代碼連接 es 以及對(duì) es 數(shù)據(jù)進(jìn)行增刪改查。
這一篇筆記介紹一下如何為 es 的 索引 index 定義一個(gè) model,像 Django 里的 model 一樣使用 es。
因?yàn)楸酒P記要介紹的內(nèi)容是直接嵌入在 Django 系統(tǒng)使用,所以本篇筆記直接歸屬于 Django 筆記系列。
本篇筆記目錄如下:
- es_model 示例及配置介紹
- 數(shù)據(jù)的增刪改查
- 字段列表操作
- 嵌套類(lèi)型操作
- 類(lèi)函數(shù)
- 排序、取字段等操作
1、es_model 示例及配置介紹
es 連接配置
首先我們要定義一下 es 的連接配置,這個(gè)在之前 Python 連接 es 的操作中有過(guò)介紹。
因?yàn)槲覀兊?es 放在 Django 系統(tǒng)里,所以在系統(tǒng)啟動(dòng)的時(shí)候就要加載,因此我們一般將其配置在 settings.py 中,示例如下:
# hunter/settings.py
from elasticsearch_dsl import connections
connections.configure(
default={"hosts": "localhost:9200"},
)
模型示例
我們?cè)?blog application 下建立一個(gè) es_models.py 文件用于存儲(chǔ)我們的 es 索引模型:
# blog/es_models.py
from elasticsearch_dsl import Document, InnerDoc, Keyword, Text, Date, Integer, Float, Boolean
class BlogEs(Document):
name = Keyword()
tag_line = Text(fields={"keyword": Keyword()}, analyzer="ik_max_word")
char_count = Integer()
is_published = Boolean()
pub_datetime = Date()
blog_id = Integer()
id = Integer()
class Index:
name = "blog"
using = "private"
文件頂部引入的 Keyword,Text,Integer 等都是我們之前在介紹 es 的時(shí)候在 Python 里對(duì)應(yīng)的數(shù)據(jù)類(lèi)型。
Document
我們?cè)诮⒚恳粋€(gè)索引模型的時(shí)候都要繼承 Document,然后再定義相應(yīng)的字段。
在 BlogEs 中,我們這里將大部分常用的字段都定義上了,包括 Keyword,Text,Integer, Date等。
其中,對(duì)于 tag_line 字段,這里將其定義為 Text,那么所存儲(chǔ)的文本內(nèi)容會(huì)被分詞之后存儲(chǔ),而我們同時(shí)定義它的子類(lèi)型為 Keyword,則說(shuō)明同時(shí)會(huì)將其文本作為一個(gè)整體存儲(chǔ),字段可以通過(guò) tag_line__keyword 的方式搜索。
分詞模式
我們還為 tag_line 增加了一個(gè) analyzer 參數(shù),它的值是我們前面在 es 筆記中安裝的中文分詞插件的一種分詞模式,表示的是可以對(duì)存儲(chǔ)的文本進(jìn)行重復(fù)分詞。
這里對(duì)中文分詞模式做一下簡(jiǎn)單的介紹,我們安裝的分詞插件有兩種模式,一種 ik_smart,一種是 ik_max_word:
ik_smart
這種模式的分詞是將文本只拆分一次,假設(shè)要分詞的文本是 "一個(gè)蘋(píng)果",那么分詞的結(jié)果就是,"一個(gè)" 和 "蘋(píng)果"。
ik_max_word
ik_max_word 的作用是將文本按照語(yǔ)義進(jìn)行可能的重復(fù)分詞,比如文本是 "一個(gè)蘋(píng)果",那么分詞的結(jié)果就是 "一個(gè)","一","個(gè)","蘋(píng)果"。
Index
我們?cè)诿總€(gè) es 模型下都要定義一個(gè) Index,其中的屬性這里介紹兩個(gè),一個(gè)是 name,一個(gè)是 using。
name 表示的是索引名稱
using 表示的是使用的 es 鏈接,es 的鏈接定義我們前面在 settings.py 里有定義,可以指定 using 的名稱,這里不對(duì) using 賦值的話默認(rèn)取值為 default
keyword 和 text
什么時(shí)候用到 Keyword,什么時(shí)候用 Text 呢,這里再贅述一下
選取哪種類(lèi)型主要取決于我們字段的業(yè)務(wù)屬性
一些需要用于整體搜索的字段可以使用 Keyword 類(lèi)型,姓名,郵箱、標(biāo)簽等
大段文字的、不會(huì)被整體搜索的、需要搜索某些關(guān)鍵詞的字段可以用 Text 字段,比如博客標(biāo)題,正文內(nèi)容等
模型初始化
在首次使用每個(gè) es 模型前,我們都需要對(duì)模型進(jìn)行初始化的操作,其含義就是將索引各字段對(duì)應(yīng)的 mapping 寫(xiě)入 es 中,這里我們通過(guò) python3 manage.py shell 來(lái)完成這個(gè)操作:
from blog.es_models import BlogEs
BlogEs.init()
初始化之后,我們可以在 kibana 里看到對(duì)應(yīng)的 es 索引。
接下來(lái)我們嘗試對(duì)模型的數(shù)據(jù)進(jìn)行增刪改查等操作。
2、數(shù)據(jù)的增刪改查
1.創(chuàng)建數(shù)據(jù)
單條創(chuàng)建數(shù)據(jù)
創(chuàng)建數(shù)據(jù)的方式很簡(jiǎn)單,我們引入該 BlogEs,對(duì)其實(shí)例化后,對(duì)字段進(jìn)行挨個(gè)賦值,然后進(jìn)行 save() 操作即可完成對(duì)一條數(shù)據(jù)的創(chuàng)建。
示例如下:
from blog.es_models import BlogEs
blog_es = BlogEs(
name="如何學(xué)好Django",
tag_line="這是一條tag_line",
)
blog_es.char_count = 98
blog_es.is_published = True
blog_es.pub_datetime = "2023-02-11 12:56:46"
blog_es.blog_id = 25
blog_es.meta.id = 25
blog_es.id = 78
blog_es.save()
這里我們指定了 meta.id,指定的是這條數(shù)據(jù)的 _id 字段,后面我們通過(guò) get() 方法獲取數(shù)據(jù)的時(shí)候,所使用到的就是這個(gè)字段。
如果不指定 meta.id,那么 es 會(huì)自動(dòng)為我們給該字段賦值,上面我們創(chuàng)建了數(shù)據(jù)之后,在 kibana 中查詢結(jié)果如下:
{
"_index" : "blog",
"_type" : "_doc",
"_id" : "25",
"_score" : 1.0,
"_source" : {
"name" : "如何學(xué)好Django",
"tag_line" : "這是一條tag_line",
"char_count" : 98,
"is_published" : true,
"pub_datetime" : "2023-02-11T12:56:46",
"blog_id" : 25,
"id" : 78
}
}
至此,我們單條數(shù)據(jù)即創(chuàng)建完畢。
批量創(chuàng)建數(shù)據(jù)
那么如何批量創(chuàng)建數(shù)據(jù)呢,貌似這里的官方文檔并沒(méi)有直接提供批量創(chuàng)建的方法,但是不要緊,我們可以使用 Python 連接 es 的筆記四的批量創(chuàng)建數(shù)據(jù)的方式。
2.查詢數(shù)據(jù)
查詢數(shù)據(jù)可以分為兩種,一種是按照 _id 參數(shù)進(jìn)行查詢,比如 get() 和 mget(),一種是根據(jù)其他字段進(jìn)行查詢。
get()
我們可以使用 get() 方法獲取單條數(shù)據(jù),這個(gè)就和 Django 的 model 的 get() 方式一樣。
但是 get() 方法只能使用 id 參數(shù)進(jìn)行查詢,不接受其他字段,比如我們 BlogEs 里定義的 name,char_count 這些字段在這個(gè)方法里都不支持
而且,這里的 id,指的是我們上面展示的這條數(shù)據(jù)的 _id 字段,并非_source 里面我們可以自定義的 id 字段。
比如我們上面在 _source 里手動(dòng)定義了 id 字段的值為 78,我們?nèi)カ@取數(shù)據(jù) id=78:
BlogEs.get(id=78)
上面這條會(huì)報(bào)錯(cuò),而我們?nèi)カ@取寫(xiě)入的 id=25:
BlogEs.get(id=25)
則可以返回?cái)?shù)據(jù),因?yàn)檫@里的 id 參數(shù)指定的是 meta.id
在這里如果我們獲取不存在的 _id 字段,則會(huì)報(bào)錯(cuò),為了防止這種情況,我們可以在 get() 方法里加上 ignore=404 來(lái)忽略這種報(bào)錯(cuò),如果不存在對(duì)應(yīng)條件的數(shù)據(jù),則返回 None:
BlogEs.get(id=22, ignore=404)
因?yàn)椴淮嬖?_id=22 的數(shù)據(jù),所以返回的數(shù)據(jù)就是 None
mget()
如果我們已知多條 _id 的值,我們通過(guò) mget() 方法來(lái)一次性獲取多條數(shù)據(jù),傳入的值是一個(gè)列表
id_list = [25, 78]
BlogEs.mget(id_list)
# [BlogEs(index='blog', id='25'), None]
如果在這個(gè)列表里有不存在于 es 的數(shù)據(jù),那么對(duì)應(yīng)返回的數(shù)據(jù)則是 None
query()
通過(guò) es_model 使用 query 的方式和使用 Python 直接進(jìn)行 es 的方式差不多,都是使用 query() 方法,示例如下:
from elasticsearch_dsl import Q as ES_Q
from blog.es_models import BlogEs
s = BlogEs.search()
query = s.query(ES_Q({"term": {"name": "如何學(xué)好Django"}}))
result = query.execute()
print(result)
# <Response: [BlogEs(index='blog', id='25')]>
或者使用 doc_type() 方法:
from elasticsearch_dsl import Search
s = Search()
s = s.doc_type(BlogEs)
query = s.query(ES_Q({"term": {"blog_id": 25}}))
result = query.execute()
print(result)
3.修改數(shù)據(jù)
我們修改的 es 數(shù)據(jù)來(lái)源可以是 get() 或者 query() 的方式
blog = BlogEs.get(id=25)
blog.name = "get修改"
blog.save()
s = BlogEs.search()
query = s.query(ES_Q({"term": {"blog_id": 25}}))
result = query.execute()
blog = result[0]
blog.name = "query修改"
blog.save()
使用 es_model 對(duì)數(shù)據(jù)進(jìn)行修改有一個(gè)很方便的地方就是可以直接對(duì)數(shù)據(jù)進(jìn)行 save 操作,相比 Python 連接 es 的方式而言。
4.刪除數(shù)據(jù)
對(duì)于單條數(shù)據(jù),我們可以直接使用 delete() 方法:
blog = BlogEs.get(id=25)
blog.delete()
也可以使用 query().delete() 的方式:
s = BlogEs.search()
query = s.query(ES_Q({"term": {"blog_id": 25}}))
query.delete()
3、字段列表操作
在 Python 里,常用字段有 Keyword,Text,Date,Integer,Boolean,F(xiàn)loat 等,和 es 中字段相同,但是如果我們想存儲(chǔ)一個(gè)相同元素類(lèi)型的列表字段如何操作呢?
比如我們想存儲(chǔ)一個(gè)列表字段,里面的元素都是 Integer,假設(shè) BlogEs 里存儲(chǔ)一個(gè) id_list,里面都是整數(shù),應(yīng)該如何定義和操作呢?
答案是直接操作。
因?yàn)?es 里并沒(méi)有列表這個(gè)類(lèi)型的字段,所以我們?nèi)绻獮橐粋€(gè)字段賦值為列表,可以直接定義元素類(lèi)型為目標(biāo)類(lèi)型,比如整型,字符串等,但是列表元素必須一致,然后操作的時(shí)候按照列表類(lèi)型來(lái)操作即可。
以下是 BlogEs 的定義,省去了其他字段:
class BlogEs(Document):
id_list = Integer()
class Index:
name = "blog"
1.創(chuàng)建列表字段
創(chuàng)建時(shí)定義 id_list:
blog_es = BlogEs()
blog_es.meta.id = 10
blog_es.id_list = [1, 2, 3]
blog_es.save()
2.修改列表字段
修改 id_list,修改時(shí)可以直接重定義,也可以 append 添加,只要我們?cè)诙x字段時(shí)用的列表,那么在修改時(shí)可以直接對(duì)其進(jìn)行列表操作:
blog_es = BlogEs.get(id=10)
blog_es.id_list = [1,4, 5] # 直接重新定義
blog_es.id_list.append(8) # 原數(shù)組添加元素
blog_es.id_list.append(9)
blog_es.save()
3.查詢列表字段
查詢 id_list 中元素
現(xiàn)在我們創(chuàng)建兩條數(shù)據(jù),之后的查詢都基于這兩條數(shù)據(jù)
blog_es = BlogEs()
blog_es.meta.id = 50
blog_es.id_list = [1, 2, 3]
blog_es.save()
blog_es_2 = BlogEs()
blog_es_2.meta.id = 50
blog_es_2.id_list = [1, 4, 5, 8, 9]
blog_es_2.save()
如果我們想查詢 id_list 中包含了 1 的數(shù)據(jù),可以如下操作:
s = BlogEs.search()
condition = ES_Q({"term": {"id_list": 1}})
query = s.query(condition)
result = query.execute()
如果想查詢 id_list 中包含了 1 或者 8 的數(shù)據(jù),任意包含其中一個(gè)元素即可,那么可以如下操作:
s = BlogEs.search()
condition = ES_Q({"terms": {"id_list": [1, 8]}})
query = s.query(condition)
result = query.execute()
如果想查詢包含了 1 且 包含了 8 的數(shù)據(jù),可以如下操作:
s = BlogEs.search()
condition = ES_Q({"term": {"id_list": 1}}) & ES_Q({"term": {"id_list": 8}})
query = s.query(condition)
result = query.execute()
4、嵌套類(lèi)型操作
嵌套的類(lèi)型是 Nested,前面我們介紹的數(shù)據(jù)存儲(chǔ)方式都是簡(jiǎn)單的 key-value 的形式,嵌套的話,可以理解成是一個(gè)字段作為 key,它的 value 則又是一個(gè) key-value。
以下是一個(gè)示例:
# blog/es_models.py
from elasticsearch_dsl import Document, InnerDoc, Keyword, Text, Date, Boolean, Nested
class Comment(InnerDoc):
author = Text()
content = Text()
class Post(Document):
title = Text()
created_at = Date()
published = Boolean()
comments = Nested(Comment)
class Index:
name = "post"
在這里,我們用 Nested() 作為嵌套字段的類(lèi)型,其中,我們通過(guò)定義 Comment 作為嵌套的對(duì)象
注意:嵌套的 Comment 繼承自 InnerDoc,且不需要進(jìn)行 init() 操作。
1. 嵌套數(shù)據(jù)的創(chuàng)建
接下來(lái)我們創(chuàng)建幾條數(shù)據(jù),嵌套的字段 comments 為列表類(lèi)型,保存多個(gè) Comment 數(shù)據(jù)
先初始化 Post:
from blog.es_models import Post
Post.init()
創(chuàng)建兩條數(shù)據(jù):
from blog.es_models import Post, Comment
comment_list = [
Comment(author="張三", content="這是評(píng)論1"),
Comment(author="李四", content="這是評(píng)論2"),
]
post = Post(
title="post_title",
published=1,
comments=comment_list
)
post.save()
comment_list_2 = [
Comment(author="張三", content="這是評(píng)論3"),
Comment(author="王五", content="這是評(píng)論4"),
]
post_2 = Post(
title="post_title_2",
published=1,
comments=comment_list_2
)
post_2.save()
2. 嵌套數(shù)據(jù)的查詢
嵌套數(shù)據(jù)的查詢也是使用 elasticsearch_dsl.Q,但是使用方式略有不同,他需要使用到 path 參數(shù),然后指出我們查詢的字段路徑
比如我們想查詢 comment 下 author 字段值為 author_1 的數(shù)據(jù),查詢示例如下:
from elasticsearch_dsl import Q as ES_Q
s = Post.search()
condition = ES_Q("nested", path="comments", query=ES_Q("term", comments__author="張三"))
query = s.query(condition)
result = query.execute()
3. 嵌套數(shù)據(jù)的修改和刪除
刪除和修改和之前的操作一樣,對(duì)于 comments 字段的內(nèi)容進(jìn)行修改后 save() 操作即可
這里我們演示示例如下:
# 獲取某個(gè) meta.id 的數(shù)據(jù)
# 然后打印出 comments 字段值
# 之后進(jìn)行修改,保存操作
post = Post.get(id="yebzsYYSls5E4GzFd_WA")
print(post.comments)
post.comments = [Comment(author="孫悟空", content="孫悟空的評(píng)論")]
post.save()
# 獲取某個(gè) meta.id 的數(shù)據(jù)
# 打印當(dāng)前值
# 然后置空做刪除處理
post = Post.get(id="yebzsYYSls5E4GzFd_WA")
print(post.comments)
post.comments = []
post.save()
# 查看置空 comments 字段后的數(shù)據(jù)情況
post = Post.get(id="yebzsYYSls5E4GzFd_WA")
print(post.comments)
5、類(lèi)函數(shù)
每個(gè) es_model 和 Django 里的 model 一樣,可以自定義函數(shù)來(lái)操作,比如我們想創(chuàng)建一條 Title 數(shù)據(jù),參數(shù)直接傳入,可以如下操作
先定義我們的 model 然后重新進(jìn)行 init() 操作:
from elasticsearch_dsl import Document, Text, Date, Boolean
from django.utils import timezone
class Title(Document):
title = Text()
created_at = Date()
published = Boolean()
class Index:
name = "title"
def create(self, title="", created_at=timezone.now(), published=True):
self.title = title
self.created_at = created_at
self.published = published
self.save()
創(chuàng)建數(shù)據(jù):
from blog.es_models import Title
Title.init()
Title().create(title="this is a title")
6、排序、取字段等操作
使用 es_model 對(duì) es 進(jìn)行排序、計(jì)數(shù)、指定字段返回和直接使用 Python 的方式無(wú)異,下面介紹一下示例。
1. 排序 sort()
如果我們想對(duì) char_count 字段進(jìn)行排列操作,可以直接使用 sort()
這里我們復(fù)用前面的 search() 操作:
s = BlogEs.search()
condition = ES_Q()
query = s.query(condition)
按照 char_count 倒序:
query = query.sort("-char_count")
按照 char_count 正序:
query = query.sort("char_count")
多字段排序,按照 char_count 和 name 字段排序:
query = query.sort("-char_count", "name")
2.指定字段返回 source()
這里我們指定 char_count 和 name 字段返回:
query = query.source("char_count", "name")
3.extra()
排序和指定字段返回我們也可以將參數(shù)傳入 extra(),然后進(jìn)行操作,比如按照 char_count 字段正序排列,name 字段倒序,以及只返回 char_count 和 name 字段
query = query.extra(
sort=[
{"char_count": {"order": "asc"}},
{"name": {"order": "desc"}}
],
_source=["char_count", "name"]
)
4.分頁(yè)操作
也可以在 extra() 中通過(guò) from 和 size 實(shí)現(xiàn)分頁(yè)操作:
query = query.extra(
**{
"from": 2,
"size": 3
}
)