赞
踩
前面在 Python 连接 es 的操作中,有过介绍如何使用 Python 代码连接 es 以及对 es 数据进行增删改查。
这一篇笔记介绍一下如何为 es 的 索引 index 定义一个 model,像 Django 里的 model 一样使用 es。
因为本篇笔记要介绍的内容是直接嵌入在 Django 系统使用,所以本篇笔记直接归属于 Django 笔记系列。
本篇笔记目录如下:
首先我们要定义一下 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
,然后再定义相应的字段。
在 BlogEs 中,我们这里将大部分常用的字段都定义上了,包括 Keyword,Text,Integer, Date等。
其中,对于 tag_line
字段,这里将其定义为 Text,那么所存储的文本内容会被分词之后存储,而我们同时定义它的子类型为 Keyword,则说明同时会将其文本作为一个整体存储,字段可以通过 tag_line__keyword
的方式搜索。
我们还为 tag_line 增加了一个 analyzer 参数,它的值是我们前面在 es 笔记中安装的中文分词插件的一种分词模式,表示的是可以对存储的文本进行重复分词。
这里对中文分词模式做一下简单的介绍,我们安装的分词插件有两种模式,一种 ik_smart,一种是 ik_max_word:
这种模式的分词是将文本只拆分一次,假设要分词的文本是 “一个苹果”,那么分词的结果就是,“一个” 和 “苹果”。
ik_max_word 的作用是将文本按照语义进行可能的重复分词,比如文本是 “一个苹果”,那么分词的结果就是 “一个”,“一”,“个”,“苹果”。
我们在每个 es 模型下都要定义一个 Index,其中的属性这里介绍两个,一个是 name,一个是 using。
name 表示的是索引名称
using 表示的是使用的 es 链接,es 的链接定义我们前面在 settings.py 里有定义,可以指定 using 的名称,这里不对 using 赋值的话默认取值为 default
什么时候用到 Keyword,什么时候用 Text 呢,这里再赘述一下
选取哪种类型主要取决于我们字段的业务属性
一些需要用于整体搜索的字段可以使用 Keyword 类型,姓名,邮箱、标签等
大段文字的、不会被整体搜索的、需要搜索某些关键词的字段可以用 Text 字段,比如博客标题,正文内容等
在首次使用每个 es 模型前,我们都需要对模型进行初始化的操作,其含义就是将索引各字段对应的 mapping 写入 es 中,这里我们通过 python3 manage.py shell
来完成这个操作:
from blog.es_models import BlogEs
BlogEs.init()
初始化之后,我们可以在 kibana 里看到对应的 es 索引。
接下来我们尝试对模型的数据进行增删改查等操作。
创建数据的方式很简单,我们引入该 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 的笔记四的批量创建数据的方式。
查询数据可以分为两种,一种是按照 _id 参数进行查询,比如 get() 和 mget(),一种是根据其他字段进行查询。
我们可以使用 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
如果我们已知多条 _id 的值,我们通过 mget() 方法来一次性获取多条数据,传入的值是一个列表
id_list = [25, 78]
BlogEs.mget(id_list)
# [BlogEs(index='blog', id='25'), None]
如果在这个列表里有不存在于 es 的数据,那么对应返回的数据则是 None
通过 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)
我们修改的 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 的方式而言。
对于单条数据,我们可以直接使用 delete() 方法:
blog = BlogEs.get(id=25)
blog.delete()
也可以使用 query().delete() 的方式:
s = BlogEs.search()
query = s.query(ES_Q({"term": {"blog_id": 25}}))
query.delete()
在 Python 里,常用字段有 Keyword,Text,Date,Integer,Boolean,Float 等,和 es 中字段相同,但是如果我们想存储一个相同元素类型的列表字段如何操作呢?
比如我们想存储一个列表字段,里面的元素都是 Integer,假设 BlogEs 里存储一个 id_list,里面都是整数,应该如何定义和操作呢?
答案是直接操作。
因为 es 里并没有列表
这个类型的字段,所以我们如果要为一个字段赋值为列表,可以直接定义元素类型为目标类型,比如整型,字符串等,但是列表元素必须一致,然后操作的时候按照列表类型来操作即可。
以下是 BlogEs 的定义,省去了其他字段:
class BlogEs(Document):
id_list = Integer()
class Index:
name = "blog"
创建时定义 id_list:
blog_es = BlogEs()
blog_es.meta.id = 10
blog_es.id_list = [1, 2, 3]
blog_es.save()
修改 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()
查询 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()
嵌套的类型是 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() 操作。
接下来我们创建几条数据,嵌套的字段 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()
嵌套数据的查询也是使用 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()
删除和修改和之前的操作一样,对于 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)
每个 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")
使用 es_model 对 es 进行排序、计数、指定字段返回和直接使用 Python 的方式无异,下面介绍一下示例。
如果我们想对 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")
这里我们指定 char_count 和 name 字段返回:
query = query.source("char_count", "name")
排序和指定字段返回我们也可以将参数传入 extra(),然后进行操作,比如按照 char_count 字段正序排列,name 字段倒序,以及只返回 char_count 和 name 字段
query = query.extra(
sort=[
{"char_count": {"order": "asc"}},
{"name": {"order": "desc"}}
],
_source=["char_count", "name"]
)
也可以在 extra() 中通过 from 和 size 实现分页操作:
query = query.extra(
**{
"from": 2,
"size": 3
}
)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。