Django筆記四十一之Django中使用es

原文鏈接:Django筆記四十一之Django中使用es

前面在 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 筆記系列。

本篇筆記目錄如下:

  1. es_model 示例及配置介紹
  2. 數(shù)據(jù)的增刪改查
  3. 字段列表操作
  4. 嵌套類(lèi)型操作
  5. 類(lèi)函數(shù)
  6. 排序、取字段等操作

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
    }
)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容