本文首發於公眾號:Hunter後端
前面在 Python 連線 es 的操作中,有過介紹如何使用 Python 程式碼連線 es 以及對 es 資料進行增刪改查。
這一篇筆記介紹一下如何為 es 的 索引 index 定義一個 model,像 Django 裡的 model 一樣使用 es。
因為本篇筆記要介紹的內容是直接嵌入在 Django 系統使用,所以本篇筆記直接歸屬於 Django 筆記系列。
本篇筆記目錄如下:
- es_model 示例及配置介紹
- 資料的增刪改查
- 欄位列表操作
- 巢狀型別操作
- 類函式
- 排序、取欄位等操作
1、es_model 示例及配置介紹
es 連線配置
首先我們要定義一下 es 的連線配置,這個在之前 Python 連線 es 的操作中有過介紹。
因為我們的 es 放在 Django 系統裡,所以在系統啟動的時候就要載入,因此我們一般將其配置在 settings.py 中,示例如下:
# hunter/settings.py
from elasticsearch_dsl import connections
connections.configure(
default={"hosts": "localhost:9200"},
)
模型示例
我們在 blog application 下建立一個 es_models.py 檔案用於儲存我們的 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 的時候在 Python 裡對應的資料型別。
Document
我們在建立每一個索引模型的時候都要繼承 Document
,然後再定義相應的欄位。
在 BlogEs 中,我們這裡將大部分常用的欄位都定義上了,包括 Keyword,Text,Integer, Date等。
其中,對於 tag_line
欄位,這裡將其定義為 Text,那麼所儲存的文字內容會被分詞之後儲存,而我們同時定義它的子型別為 Keyword,則說明同時會將其文字作為一個整體儲存,欄位可以透過 tag_line__keyword
的方式搜尋。
分詞模式
我們還為 tag_line 增加了一個 analyzer 引數,它的值是我們前面在 es 筆記中安裝的中文分詞外掛的一種分詞模式,表示的是可以對儲存的文字進行重複分詞。
這裡對中文分詞模式做一下簡單的介紹,我們安裝的分詞外掛有兩種模式,一種 ik_smart,一種是 ik_max_word:
ik_smart
這種模式的分詞是將文字只拆分一次,假設要分詞的文字是 "一個蘋果",那麼分詞的結果就是,"一個" 和 "蘋果"。
ik_max_word
ik_max_word 的作用是將文字按照語義進行可能的重複分詞,比如文字是 "一個蘋果",那麼分詞的結果就是 "一個","一","個","蘋果"。
Index
我們在每個 es 模型下都要定義一個 Index,其中的屬性這裡介紹兩個,一個是 name,一個是 using。
name 表示的是索引名稱
using 表示的是使用的 es 連結,es 的連結定義我們前面在 settings.py 裡有定義,可以指定 using 的名稱,這裡不對 using 賦值的話預設取值為 default
keyword 和 text
什麼時候用到 Keyword,什麼時候用 Text 呢,這裡再贅述一下
選取哪種型別主要取決於我們欄位的業務屬性
一些需要用於整體搜尋的欄位可以使用 Keyword 型別,姓名,郵箱、標籤等
大段文字的、不會被整體搜尋的、需要搜尋某些關鍵詞的欄位可以用 Text 欄位,比如部落格標題,正文內容等
模型初始化
在首次使用每個 es 模型前,我們都需要對模型進行初始化的操作,其含義就是將索引各欄位對應的 mapping 寫入 es 中,這裡我們透過 python3 manage.py shell
來完成這個操作:
from blog.es_models import BlogEs
BlogEs.init()
初始化之後,我們可以在 kibana 裡看到對應的 es 索引。
接下來我們嘗試對模型的資料進行增刪改查等操作。
2、資料的增刪改查
1.建立資料
單條建立資料
建立資料的方式很簡單,我們引入該 BlogEs,對其例項化後,對欄位進行挨個賦值,然後進行 save() 操作即可完成對一條資料的建立。
示例如下:
from blog.es_models import BlogEs
blog_es = BlogEs(
name="如何學好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,指定的是這條資料的 _id 欄位,後面我們透過 get() 方法獲取資料的時候,所使用到的就是這個欄位。
如果不指定 meta.id,那麼 es 會自動為我們給該欄位賦值,上面我們建立了資料之後,在 kibana 中查詢結果如下:
{
"_index" : "blog",
"_type" : "_doc",
"_id" : "25",
"_score" : 1.0,
"_source" : {
"name" : "如何學好Django",
"tag_line" : "這是一條tag_line",
"char_count" : 98,
"is_published" : true,
"pub_datetime" : "2023-02-11T12:56:46",
"blog_id" : 25,
"id" : 78
}
}
至此,我們單條資料即建立完畢。
批次建立資料
那麼如何批次建立資料呢,貌似這裡的官方文件並沒有直接提供批次建立的方法,但是不要緊,我們可以使用 Python 連線 es 的筆記四的批次建立資料的方式。
2.查詢資料
查詢資料可以分為兩種,一種是按照 _id 引數進行查詢,比如 get() 和 mget(),一種是根據其他欄位進行查詢。
get()
我們可以使用 get() 方法獲取單條資料,這個就和 Django 的 model 的 get() 方式一樣。
但是 get() 方法只能使用 id 引數進行查詢,不接受其他欄位,比如我們 BlogEs 裡定義的 name,char_count 這些欄位在這個方法裡都不支援
而且,這裡的 id,指的是我們上面展示的這條資料的 _id 欄位,並非_source 裡面我們可以自定義的 id 欄位。
比如我們上面在 _source 裡手動定義了 id 欄位的值為 78,我們去獲取資料 id=78:
BlogEs.get(id=78)
上面這條會報錯,而我們去獲取寫入的 id=25:
BlogEs.get(id=25)
則可以返回資料,因為這裡的 id 引數指定的是 meta.id
在這裡如果我們獲取不存在的 _id 欄位,則會報錯,為了防止這種情況,我們可以在 get() 方法里加上 ignore=404 來忽略這種報錯,如果不存在對應條件的資料,則返回 None:
BlogEs.get(id=22, ignore=404)
因為不存在 _id=22 的資料,所以返回的資料就是 None
mget()
如果我們已知多條 _id 的值,我們透過 mget() 方法來一次性獲取多條資料,傳入的值是一個列表
id_list = [25, 78]
BlogEs.mget(id_list)
# [BlogEs(index='blog', id='25'), None]
如果在這個列表裡有不存在於 es 的資料,那麼對應返回的資料則是 None
query()
透過 es_model 使用 query 的方式和使用 Python 直接進行 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": "如何學好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.修改資料
我們修改的 es 資料來源可以是 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 對資料進行修改有一個很方便的地方就是可以直接對資料進行 save 操作,相比 Python 連線 es 的方式而言。
4.刪除資料
對於單條資料,我們可以直接使用 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,Float 等,和 es 中欄位相同,但是如果我們想儲存一個相同元素型別的列表欄位如何操作呢?
比如我們想儲存一個列表欄位,裡面的元素都是 Integer,假設 BlogEs 裡儲存一個 id_list,裡面都是整數,應該如何定義和操作呢?
答案是直接操作。
因為 es 裡並沒有列表
這個型別的欄位,所以我們如果要為一個欄位賦值為列表,可以直接定義元素型別為目標型別,比如整型,字串等,但是列表元素必須一致,然後操作的時候按照列表型別來操作即可。
以下是 BlogEs 的定義,省去了其他欄位:
class BlogEs(Document):
id_list = Integer()
class Index:
name = "blog"
1.建立列表欄位
建立時定義 id_list:
blog_es = BlogEs()
blog_es.meta.id = 10
blog_es.id_list = [1, 2, 3]
blog_es.save()
2.修改列表欄位
修改 id_list,修改時可以直接重定義,也可以 append 新增,只要我們在定義欄位時用的列表,那麼在修改時可以直接對其進行列表操作:
blog_es = BlogEs.get(id=10)
blog_es.id_list = [1,4, 5] # 直接重新定義
blog_es.id_list.append(8) # 原陣列新增元素
blog_es.id_list.append(9)
blog_es.save()
3.查詢列表欄位
查詢 id_list 中元素
現在我們建立兩條資料,之後的查詢都基於這兩條資料
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 的資料,可以如下操作:
s = BlogEs.search()
condition = ES_Q({"term": {"id_list": 1}})
query = s.query(condition)
result = query.execute()
如果想查詢 id_list 中包含了 1 或者 8 的資料,任意包含其中一個元素即可,那麼可以如下操作:
s = BlogEs.search()
condition = ES_Q({"terms": {"id_list": [1, 8]}})
query = s.query(condition)
result = query.execute()
如果想查詢包含了 1 且 包含了 8 的資料,可以如下操作:
s = BlogEs.search()
condition = ES_Q({"term": {"id_list": 1}}) & ES_Q({"term": {"id_list": 8}})
query = s.query(condition)
result = query.execute()
4、巢狀型別操作
巢狀的型別是 Nested,前面我們介紹的資料儲存方式都是簡單的 key-value 的形式,巢狀的話,可以理解成是一個欄位作為 key,它的 value 則又是一個 key-value。
以下是一個示例:
# 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() 作為巢狀欄位的型別,其中,我們透過定義 Comment 作為巢狀的物件
注意:巢狀的 Comment 繼承自 InnerDoc,且不需要進行 init() 操作。
1. 巢狀資料的建立
接下來我們建立幾條資料,巢狀的欄位 comments 為列表型別,儲存多個 Comment 資料
先初始化 Post:
from blog.es_models import Post
Post.init()
建立兩條資料:
from blog.es_models import Post, Comment
comment_list = [
Comment(author="張三", content="這是評論1"),
Comment(author="李四", content="這是評論2"),
]
post = Post(
title="post_title",
published=1,
comments=comment_list
)
post.save()
comment_list_2 = [
Comment(author="張三", content="這是評論3"),
Comment(author="王五", content="這是評論4"),
]
post_2 = Post(
title="post_title_2",
published=1,
comments=comment_list_2
)
post_2.save()
2. 巢狀資料的查詢
巢狀資料的查詢也是使用 elasticsearch_dsl.Q,但是使用方式略有不同,他需要使用到 path 引數,然後指出我們查詢的欄位路徑
比如我們想查詢 comment 下 author 欄位值為 author_1 的資料,查詢示例如下:
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. 巢狀資料的修改和刪除
刪除和修改和之前的操作一樣,對於 comments 欄位的內容進行修改後 save() 操作即可
這裡我們演示示例如下:
# 獲取某個 meta.id 的資料
# 然後列印出 comments 欄位值
# 之後進行修改,儲存操作
post = Post.get(id="yebzsYYSls5E4GzFd_WA")
print(post.comments)
post.comments = [Comment(author="孫悟空", content="孫悟空的評論")]
post.save()
# 獲取某個 meta.id 的資料
# 列印當前值
# 然後置空做刪除處理
post = Post.get(id="yebzsYYSls5E4GzFd_WA")
print(post.comments)
post.comments = []
post.save()
# 檢視置空 comments 欄位後的資料情況
post = Post.get(id="yebzsYYSls5E4GzFd_WA")
print(post.comments)
5、類函式
每個 es_model 和 Django 裡的 model 一樣,可以自定義函式來操作,比如我們想建立一條 Title 資料,引數直接傳入,可以如下操作
先定義我們的 model 然後重新進行 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()
建立資料:
from blog.es_models import Title
Title.init()
Title().create(title="this is a title")
6、排序、取欄位等操作
使用 es_model 對 es 進行排序、計數、指定欄位返回和直接使用 Python 的方式無異,下面介紹一下示例。
1. 排序 sort()
如果我們想對 char_count 欄位進行排列操作,可以直接使用 sort()
這裡我們複用前面的 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()
排序和指定欄位返回我們也可以將引數傳入 extra(),然後進行操作,比如按照 char_count 欄位正序排列,name 欄位倒序,以及只返回 char_count 和 name 欄位
query = query.extra(
sort=[
{"char_count": {"order": "asc"}},
{"name": {"order": "desc"}}
],
_source=["char_count", "name"]
)
4.分頁操作
也可以在 extra() 中透過 from 和 size 實現分頁操作:
query = query.extra(
**{
"from": 2,
"size": 3
}
)
如果想獲取更多相關文章,可掃碼關注閱讀: