使用requests发请求操作Elasticsearch【二】

本文为博主原创,未经授权,严禁转载及使用。
本文链接:https://blog.csdn.net/zyooooxie/article/details/118367832

前面刚刚分享 使用requests发请求操作Elasticsearch【一】 ,继续分享下。

【实际这篇博客推迟发布N个月】

个人博客:https://blog.csdn.net/zyooooxie

【以下所有内容仅为个人项目经历,如有不同,纯属正常】

Document APIs

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/docs.html

"""
@blog: https://blog.csdn.net/zyooooxie
@qq: 153132336
@email: zyooooxie@gmail.com
"""

import json
import random
import time
import string
import copy
import requests

from requests_toolbelt.utils import dump
from requests import api
from XXX.common_es import gl_es_auth, gl_es_host_new
from XXX.common_functions import one_choice_true_false

from user_log import Log

# 一个文档不仅仅包含它的数据 ,也包含 元数据(有关 文档的信息)。 三个必须的元数据元素如下:
# _index    文档在哪存放
# _type 文档表示的对象类别
# _id   文档唯一标识

gl_index = 'ABC-data'
gl_type = '_doc'

gl_int = random.randint(1, 999)
gl_id = 'test' + str(time.time())

def es_send_request(request_method: str, request_url: str, data_dict: dict,
                    auth: tuple = gl_es_auth, **kwargs) -> requests.Response:
    """

    :param request_method:
    :param request_url:
    :param data_dict:
    :param auth:
    :param kwargs:
    :return:
    """
    assert hasattr(api, request_method) is True

    res = requests.request(method=request_method, url=request_url, json=data_dict, auth=auth, **kwargs)
    Log.info(dump.dump_all(res).decode('utf-8'))

    res.close()
    Log.info('********')

    return res


def test_index_doc(document_id):
    """

    :param document_id:
    :return:
    """
    Log.info(document_id)

    # 提供自定义的 _id 值
    # PUT /{index}/{type}/{id}

    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id])
    data_dict = {'test': gl_int * 1, 'text': {'content': document_id + '是id'}}

    res = es_send_request('put', url, data_dict, gl_es_auth)
    assert res.json().get('_id') == document_id

    test_get_doc(document_id)

    # 在 Elasticsearch 中每个文档都有一个版本号。当每次对文档进行修改时(包括删除), _version 的值会递增。
    Log.info(res.json().get('_version'))

    Log.info('********')

    # 让 index API 自动生成
    # POST /{index}/{type}/

    url = '/'.join([gl_es_host_new, gl_index, gl_type])

    data_ = {'test': gl_int * 10000, 'text': {'content': 'id是es自动生成的'}}

    res = es_send_request('post', url, data_)
    assert res.json().get('result') == 'created'

    # _id 是 Elasticsearch 自动生成的
    id_ = res.json().get('_id')
    Log.info(res.json().get('_id'))

    test_get_doc(id_)


def test_create_doc(document_id):
    """

    :param document_id:
    :return:
    """
    # 当我们索引一个文档,怎么确认我们正在创建一个完全新的文档,而不是覆盖现有的呢?
    # 如果已经有自己的 _id ,那么我们必须告诉 Elasticsearch ,只有在相同的 _index 、 _type 和 _id 不存在时,才接受我们的索引请求

    # 1.使用 op_type
    # PUT  /{index}/{type}/{id}?op_type=create

    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id + '?op_type=create'])
    data_dict = {'test': str(gl_int)}
    res = es_send_request('put', url, data_dict)
    Log.info(res.status_code)

    Log.info('********')

    new_id_ = document_id if one_choice_true_false else document_id + gl_id
    Log.info(new_id_)

    # 2.在 URL 末端使用 /_create
    # PUT /{index}/{type}/{id}/_create
    # POST /{index}/{type}/{id}/_create

    url = '/'.join([gl_es_host_new, gl_index, gl_type, new_id_, '_create'])
    res = es_send_request('put', url, data_dict)
    Log.info(res.status_code)

    res = es_send_request('post', url, data_dict)
    Log.info(res.status_code)

    # 如果具有相同的 _index 、 _type 和 _id 的文档已经存在,Elasticsearch 将会返回 409 Conflict 响应码
    # 如果创建新文档的请求成功执行,Elasticsearch 会返回  201 Created 的 HTTP 响应码。


def test_get_doc(document_id: str):
    """

    :param document_id:
    :return:
    """
    # 执行 一个 HTTP GET 请求并指定文档的地址——索引库、类型和ID
    # GET /{index}/{type}/{id}

    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id])
    res = es_send_request('get', url, {})

    Log.info('********')

    # 加上 pretty 参数,这将会调用 Elasticsearch 的 pretty-print 功能,该功能 使得 JSON 响应体更加可读    【调试使用】
    # GET /{index}/{type}/{id}?pretty

    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id + '?pretty'])
    # res = es_send_request('get', url, {})

    Log.info(res.json())
    found_ = res.json().get('found')
    assert res.status_code == (404 if found_ is False else 200)
    # GET 请求的响应体包括 {"found": true} ,这证实了文档已经被找到。
    # 如果我们请求一个不存在的文档,我们仍旧会得到一个 JSON 响应体,但是 found 将会是 false 。
    # 此外, HTTP 响应码将会是 404 Not Found ,而不是 200 OK 。

    return res


def test_get_doc2(document_id: str):

    """
    
    :param document_id: 
    :return: 
    """
    # 默认情况下, GET 请求会返回整个文档,这个文档正如存储在 _source 字段中的一样。但是也许你只对其中的 title 字段感兴趣。
    # 单个字段能用 _source 参数请求得到,多个字段也能使用逗号分隔的列表来指定。

    # GET /{index}/{type}/{id}?_source=title,text
    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id + '?_source={}'.format('seq')])
    es_send_request('get', url, {})

    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id + '?_source={}'.format('seq,text')])
    es_send_request('get', url, {})

    # _source_includes
    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id + '?_source_includes={}'.format('seq')])
    es_send_request('get', url, {})

    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id + '?_source_includes={}'.format('seq,text')])
    es_send_request('get', url, {})

    Log.info('********')

    # _source_excludes
    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id + '?_source_excludes={}'.format('seq')])
    es_send_request('get', url, {})

    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id + '?_source_excludes={}'.format('seq,text')])
    es_send_request('get', url, {})

    Log.info('********')

    # 只想得到 _source 字段,不需要任何元数据,你能使用 _source
    # GET /{index}/{type}/{id}/_source
    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id, '_source'])
    es_send_request('get', url, {})


def test_doc_exists(document_id: str):

    """
    
    :param document_id: 
    :return: 
    """
    # 如果只想检查一个文档是否存在--根本不想关心内容,那么用 HEAD 方法来代替 GET 方法。
    # HEAD 请求没有返回体,只返回一个 HTTP 请求报头
    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id])

    res = es_send_request('head', url, {})
    Log.info(res.status_code)

    # 如果文档存在, Elasticsearch 将返回一个 200 ok 的状态码
    # 若文档不存在, Elasticsearch 将返回一个 404 Not Found 的状态码


def test_update_doc(document_id: str):
    """
    更新整个文档
    :param document_id:
    :return:
    """
    res = test_get_doc(document_id)
    _version = res.json().get('_version')
    Log.info(_version)

    # 更新整个文档
    # PUT /{index}/{type}/{id}

    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id])
    data_dict = {'abc': gl_int}
    res = es_send_request('put', url, data_dict, auth=gl_es_auth)
    Log.info(res.status_code)

    # 【若此id存在】    status_code是200
    # 【若此id不存在】  创建  status_code是201

    assert res.status_code == 200 if _version is not None else 201
    assert res.json().get('_version') == (_version if _version is not None else 0) + 1

    # 在内部,Elasticsearch 已将旧文档标记为已删除,并增加一个全新的文档。 尽管你不能再对旧版本的文档进行访问,但它并不会立即消失。当继续索引更多的数据,Elasticsearch 会在后台清理这些已删除文档。
    test_get_doc(document_id)


def test_partial_updates(document_id):
    """
    文档的部分更新
    :param document_id:
    :return:
    """
    res = test_get_doc(document_id)
    _version = res.json().get('_version')
    Log.info(_version)

    # 文档的部分更新
    # 作为 doc 的参数, 它只是与现有的文档进行合并。对象被合并到一起,覆盖现有的字段,增加新的字段

    # POST /{index}/{type}/{id}/_update
    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id, '_update'])

    data = {"doc": {random.choice(string.ascii_letters): 1, "test": str(gl_int)}}  # 已有字段要保证 字段值类型一致
    res = es_send_request('post', url, data)
    Log.info(res.status_code)

    assert res.status_code == 200 if _version is not None else 404

    Log.info('********')

    # POST /{index}/_update/{id}        没有 {type}
    url = '/'.join([gl_es_host_new, gl_index, '_update', document_id])
    data = {"doc": {random.choice(string.ascii_letters): 1, "test": str(gl_int)}}  # 已有字段要保证 字段值类型一致

    res = es_send_request('post', url, data)
    Log.info(res.status_code)

    assert res.status_code == 200 if _version is not None else 404

    # 【若此id存在】   status_code是200
    # 【若此id不存在】 不做更新 + 不创建  status_code是404

    test_get_doc(document_id)


def test_bulk(document_id):
    """
    
    :param document_id: 
    :return: 
    """
    # https://www.elastic.co/guide/en/elasticsearch/reference/7.17/docs-bulk.html#docs-bulk-api-desc
    # https://www.elastic.co/guide/cn/elasticsearch/guide/current/bulk.html

    test_get_doc(document_id)

    # bulk API 允许在单个步骤中进行多次 create 、 index 、 update 或 delete 请求。

    # bulk 与其他的请求体格式稍有不同,格式:

    # { action: { metadata }}\n
    # { request body        }\n
    # { action: { metadata }}\n
    # { request body        }\n
    # ...

    # 有2个要点:    每行一定要以换行符(\n)结尾,包括最后一行;     这些行不能包含未转义的换行符。

    # action/metadata行 指定 哪一个文档 做 什么操作 。

    # action 必须是以下选项之一:
    # create 如果文档不存在,那么就创建它。
    # index 创建一个新文档或者替换一个现有的文档。
    # update 部分更新一个文档。
    # delete 删除一个文档。

    # metadata 应该指定被索引、创建、更新或者删除的文档的 _index 、 _type 和 _id 。

    # request body 行由文档的 _source 本身组成 文档包含的字段和值。
    # 它是 index 和 create 操作所必需的。
    # 它也是 update 操作所必需的,应该包含你传递给 update API 的相同请求体: doc 、 upsert 、 script 等等;
    # delete 操作不需要 request body 行。

    metadata = {'_index': gl_index, '_type': gl_type, '_id': document_id}

    # POST /_bulk
    url = '/'.join([gl_es_host_new, '_bulk'])

    bulk_action_body(metadata=metadata, url=url)

    test_get_doc(document_id)

    Log.info('********')
    Log.info('********')
    Log.info('********')

    # 不重复指定Index和Type
    # 在 bulk 请求的 URL 中接收默认的 /_index 或者 /_index/_type

    document_id_ = document_id + '_id'
    test_get_doc(document_id_)

    metadata_new = {'_id': document_id_}

    # POST /{index}/{type}/_bulk
    url_new = '/'.join([gl_es_host_new, gl_index, gl_type, '_bulk'])
    bulk_action_body(metadata=metadata_new, url=url_new)

    test_get_doc(document_id_)


def bulk_action_body(metadata: dict, url: str):
    """
    
    :param metadata: 
    :param url: 
    :return: 
    """
    # 整个批量请求都需要由接收到请求的节点加载到内存中,因此该请求越大,其他请求所能获得的内存就越少。 批量请求的大小有一个最佳值,
    Log.info(metadata)

    random_int = random.randint(-999, -1)
    id_old = metadata.get('_id')
    Log.info(f'{random_int}, {id_old}')

    Log.info('********')

    create = {'create': metadata.copy()}
    c_body = {'test': random_int * 1, 'text': {'content': f'id是{id_old}'}}
    create_ele = create, c_body, id_old

    Log.info(metadata)
    Log.info(create_ele)

    Log.info('********')

    # 不指定id(将会自动生成一个ID)
    metadata.pop('_id')

    index_1 = {'index': metadata.copy()}
    i_body_1 = {'test': random_int * 100, 'text': {'content': 'id是es生成的'}}
    index_ele_1 = index_1, i_body_1, None

    Log.info(metadata)
    Log.info(index_ele_1)

    # 指定id
    id_new = id_old + random.choice(string.ascii_letters)
    metadata.update(_id=id_new)

    index_2 = {'index': metadata.copy()}
    i_body_2 = {'test': random_int * 10000, 'text': {'content': f'id是{id_new},多了个字母'}}
    index_ele_2 = index_2, i_body_2, id_new

    Log.info(metadata)
    Log.info(index_ele_2)

    Log.info('********')

    update = {'update': metadata.copy()}
    u_body = {'doc': {'test': random_int * 0.01, 'new': 'update的新值'}}
    update_ele = update, u_body, id_new

    Log.info(metadata)
    Log.info(update_ele)

    Log.info('********')

    delete = {'delete': metadata.copy()}
    d_body = None  # delete操作 不需要 request body 行。
    delete_ele = delete, d_body, id_new

    Log.info(metadata)
    Log.info(delete_ele)

    Log.info('********')

    # 调转 delete_ele 的位置
    # a_b_id = create_ele, index_ele_1, index_ele_2, update_ele, delete_ele
    a_b_id = create_ele, index_ele_1, index_ele_2, delete_ele, update_ele

    all_data = str()

    for action_, body_, id_ in a_b_id:
        # 每个单独 bulk

        if body_:
            request_data = '{}\n{}\n'.format(json.dumps(action_), json.dumps(body_))

        else:
            request_data = '{}\n'.format(json.dumps(action_))

        Log.info(request_data)

        bulk_send_request(req_data=request_data, req_url=url)

        if id_:
            test_get_doc(id_)

        all_data += request_data

    else:
        # 全部 bulk

        Log.info(all_data)

        # 每个子请求都是独立执行,因此某个子请求的失败不会对其他子请求的成功与否造成影响。
        # 如果其中任何子请求失败,最顶层的 error 标志被设置为 true ,并且在相应的请求报告出错误明细

        bulk_send_request(req_data=all_data, req_url=url)

        test_get_doc(id_old)
        test_get_doc(id_new)


def bulk_send_request(req_data: str, req_url: str):
    """
    
    :param req_data: 
    :param req_url: 
    :return: 
    """
    
    # When sending NDJSON data to the _bulk endpoint, use a Content-Type header of application/json or application/x-ndjson.

    h = {'Content-Type': 'application/json;charset=utf-8'}

    res = requests.post(req_url, data=req_data, auth=gl_es_auth, headers=h)

    Log.info(dump.dump_all(res).decode('utf-8'))

    time.sleep(1)

    Log.error('🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀')


def test_delete_doc(document_id: str):
    """

    :param document_id:
    :return:
    """
    # DELETE /{index}/{type}/{id}
    url = '/'.join([gl_es_host_new, gl_index, gl_type, document_id])

    res = es_send_request('delete', url, {})

    # 如果找到该文档,Elasticsearch 将要返回一个 200 ok 的 HTTP 响应码
    # 如果文档没有找到,我们将得到 404 Not Found 的响应码
    Log.info(res.status_code)


def create_source(docs_list: list):
    """

    :param docs_list:
    :return:
    """

    Log.info(docs_list)

    # 如果你想检索一个或者多个特定的字段,那么你可以通过 _source 参数来指定这些字段的名字:

    # By default, the _source field is returned for every document (if stored). Use the _source and _source_include or source_exclude attributes to filter what fields are returned for a particular document.
    # You can include the _source, _source_includes, and _source_excludes query parameters in the request URI to specify the defaults to use when there are no per-document instructions.
    dl_copy = copy.deepcopy(docs_list)

    for dl in dl_copy:
        # dl.update(_source=random.sample(['abc', 'test', 'text'], k=2)) if random.getrandbits(1) else dl.update(
        #     _source='abc')

        abc = random.getrandbits(2)

        if not abc:
            dl.update(_source=random.sample(['abc0', 'msgType', 'text'], k=2))

        elif abc == 1:
            dl.update(_source='seq')

        elif abc == 2:
            dl.update(_source={'include': ['abc2', 'text', 'msgTime']})

        else:
            dl.update(_source={'include': ['abc3', 'text', 'msgId'], 'exclude': ['text.content']})

    Log.info(dl_copy)

    return dl_copy


def test_multi_get(document_id_list: list):
    """
    取回多个文档
    :param document_id_list:
    :return:
    """

    # mget API 要求有一个 docs 数组作为参数,每个元素包含需要检索文档的元数据, 包括 _index 、 _type 和 _id 。

    # GET /_mget
    url1 = '/'.join([gl_es_host_new, '_mget'])

    docs_1 = [{'_index': gl_index, '_type': gl_type, '_id': di} for di in document_id_list]

    data1 = {'docs': docs_1}
    data1_ = {'docs': create_source(docs_1)}

    Log.info('********')

    # 如果想检索的数据都在相同的 _index 中(甚至相同的 _type 中),则可以在 URL 中指定默认的 /_index 或者默认的 /_index/_type

    # GET /{index}/_mget
    url2 = '/'.join([gl_es_host_new, gl_index, '_mget'])

    docs_2 = [{'_id': di, '_type': gl_type} for di in document_id_list]

    data2 = {'docs': docs_2}
    data2_ = {'docs': create_source(docs_2)}

    # GET /{index}/{type}/_mget

    url3 = '/'.join([gl_es_host_new, gl_index, gl_type, '_mget'])
    docs_3 = [{'_id': di} for di in document_id_list]

    data3 = {'docs': docs_3}
    data3_ = {'docs': create_source(docs_3)}

    Log.info('********')

    # 所有文档的 _index 和 _type 都是相同的,你可以只传一个 ids 数组,而不是整个 docs 数组
    data3_new = {'ids': document_id_list}

    Log.info('********')

    for u, d in zip((url1, url1, url2, url2, url3, url3, url3),
                    (data1, data1_, data2, data2_, data3, data3_, data3_new)):
        # 对于每一个在请求中指定的文档,这个数组中都包含有一个对应的响应,且顺序与请求中的顺序相同。
        # 其中的每一个响应都和使用单个 get request 请求所得到的响应体相同
        es_send_request('get', u, d)

        time.sleep(1)


def test_count():
    """
    
    :return: 
    """
    # POST /{index}/{type}/_count
    url = '/'.join([gl_es_host_new, gl_index, gl_type, '_count'])

    es_send_request('post', url, {})


def test_mapping():
    """
    
    :return: 
    """
    # 通过 /_mapping ,我们可以查看 Elasticsearch 在一个或多个索引中的一个或多个类型的映射

    # GET /{index}/_mapping
    url = '/'.join([gl_es_host_new, gl_index, '_mapping'])
    es_send_request('get', url, {})

    # GET /{index}/_mapping/{type}
    url = '/'.join([gl_es_host_new, gl_index, '_mapping', gl_type])
    es_send_request('get', url, {}, params={'include_type_name': 'true'})


if __name__ == '__main__':
    # 映射(Mapping)   描述数据在每个字段内如何存储
    # 分析(Analysis)  全文是如何处理使之可以被搜索的
    # 领域特定查询语言(Query DSL)   Elasticsearch 中强大灵活的查询语言

    Log.info(gl_int)
    Log.info(gl_id)

本文链接:https://blog.csdn.net/zyooooxie/article/details/118367832

个人博客 https://blog.csdn.net/zyooooxie

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值