Elasticsearch实现文档标签管理

1.需求描述

使用python操作elasticsearch实现一个文档标签库的增删改查,需求描述如下:

  1. 一个文档有多个标签,并且允许标签的增删改查
  2. 可以查询文档内容和标签信息,并且允许标签的复杂查询
  3. 标签查询逻辑:多个标签之间使用 AND 逻辑,单个标签的多个值使用 OR 逻辑

2.需求分析

  1. Elasticsearch的索引设计:文档需要有内容字段和一个标签字段。标签应该存储为数组类型,这样每个文档可以有多个标签。在Elasticsearch中,正确的映射设置很重要,比如使用"keyword"类型来确保标签可以被正确索引,方便精确匹配和聚合操作。

  2. 基本的CRUD操作:对于创建文档,需要定义索引结构,然后插入文档数据,包含内容和标签。这里需要使用Elasticsearch的Python客户端库,比如elasticsearch-py。然后,对于查询,需要根据ID获取文档,或者根据标签进行过滤。更新操作需要允许添加或删除标签,可以使用脚本更新,比如Painless脚本。删除文档则比较简单,根据ID删除即可。

  3. 复杂查询部分:复杂查询可能包括多个标签的组合查询,比如同时包含多个标签,或者任意一个标签,或者排除某些标签。此外,可能还需要支持通配符查询,比如查找以某个前缀开头的标签。这里需要用到Elasticsearch的复合查询,比如bool查询中的must(AND)、should(OR)、must_not(NOT)等条件。通配符查询可以使用wildcard查询来实现。

  4. 分页和排序:这在查询时是常见的需求。比如使用from和size参数进行分页,或者按某个字段排序。

  5. 代码的结构:需要一个Elasticsearch操作类,封装初始化连接、创建索引、增删改查等方法。在创建索引时,确保标签字段的正确映射。在添加文档时,传入内容和标签列表。更新标签时,使用脚本追加或删除标签,注意处理并发问题,可能需要使用版本控制或者重试机制。

  6. 复杂查询函数:需要构建一个灵活的查询体,根据传入的参数动态添加条件。比如允许用户传入多个必须存在的标签、应该存在的标签、排除的标签等。通配符查询可能需要单独处理,或者整合到标签条件中。

3.代码实现

以下是一个使用Python操作Elasticsearch实现文档标签库增删改查的详细方案,包含复杂标签查询功能:

python 复制代码
from elasticsearch import Elasticsearch
from elasticsearch.helpers import bulk

class ElasticsearchTagLibrary:
    def __init__(self, host='localhost', port=9200):
        self.es = Elasticsearch([{'host': host, 'port': port}])
        self.index_name = "documents"
        self._create_index()

    def _create_index(self):
        """创建索引并定义映射"""
        if not self.es.indices.exists(index=self.index_name):
            mapping = {
                "mappings": {
                    "properties": {
                        "title": {"type": "text"},
                        "content": {"type": "text"},
                        "tags": {
                            "type": "keyword",  # 使用keyword类型便于精确匹配和聚合
                            "null_value": []    # 处理空值情况
                        },
                        "created_at": {"type": "date"}
                    }
                }
            }
            self.es.indices.create(index=self.index_name, body=mapping)

    # 基本CRUD操作
    def create_document(self, title, content, tags):
        """创建新文档"""
        doc = {
            "title": title,
            "content": content,
            "tags": tags,
            "created_at": "now"
        }
        return self.es.index(index=self.index_name, body=doc)

    def get_document(self, doc_id):
        """获取单个文档"""
        return self.es.get(index=self.index_name, id=doc_id)

    def update_document_content(self, doc_id, content):
        """更新文档内容"""
        return self.es.update(
            index=self.index_name,
            id=doc_id,
            body={"doc": {"content": content}}
        )

    def delete_document(self, doc_id):
        """删除文档"""
        return self.es.delete(index=self.index_name, id=doc_id)

    # 标签操作
    def add_tags(self, doc_id, new_tags):
        """添加新标签"""
        script = {
            "source": """
                if (ctx._source.tags == null) {
                    ctx._source.tags = new_tags
                } else {
                    ctx._source.tags.addAll(new_tags)
                }
            """,
            "params": {"new_tags": new_tags}
        }
        return self.es.update(
            index=self.index_name,
            id=doc_id,
            body={"script": script}
        )

    def remove_tags(self, doc_id, tags_to_remove):
        """删除指定标签"""
        script = {
            "source": """
                if (ctx._source.tags != null) {
                    ctx._source.tags.removeAll(tags_to_remove)
                }
            """,
            "params": {"tags_to_remove": tags_to_remove}
        }
        return self.es.update(
            index=self.index_name,
            id=doc_id,
            body={"script": script}
        )

    # 复杂查询方法
    def search_documents(self, query=None, tags=None, 
                         must_tags=None, should_tags=None, 
                         exclude_tags=None, wildcard_tag=None,
                         size=10, page=0):
        """
        复杂文档查询
        :param query: 全文搜索内容的关键词
        :param tags: 必须包含所有指定标签
        :param must_tags: 必须包含所有指定标签(同tags)
        :param should_tags: 至少包含其中一个标签
        :param exclude_tags: 不包含任何指定标签
        :param wildcard_tag: 通配符标签查询(如: "dev*")
        :return: 匹配的文档结果
        """
        query_body = {"bool": {"must": []}}

        # 全文搜索条件
        if query:
            query_body["bool"]["must"].append({
                "multi_match": {
                    "query": query,
                    "fields": ["title^2", "content"]
                }
            })

        # 标签条件处理
        tags_filter = []
        
        # 必须包含所有指定标签
        if tags or must_tags:
            target_tags = tags or must_tags
            tags_filter.append({"terms": {"tags": target_tags}})

        # 应该包含至少一个标签
        if should_tags:
            tags_filter.append({
                "bool": {
                    "should": [
                        {"term": {"tags": tag}} for tag in should_tags
                    ],
                    "minimum_should_match": 1
                }
            })

        # 排除标签
        if exclude_tags:
            query_body["bool"]["must_not"] = [
                {"terms": {"tags": exclude_tags}}
            ]

        # 通配符查询
        if wildcard_tag:
            tags_filter.append({
                "wildcard": {"tags": wildcard_tag}
            })

        # 组合标签过滤条件
        if tags_filter:
            if len(tags_filter) > 1:
                query_body["bool"]["must"].append({"bool": {"must": tags_filter}})
            else:
                query_body["bool"]["must"].append(tags_filter[0])

        # 执行查询
        return self.es.search(
            index=self.index_name,
            body={
                "query": query_body,
                "from": page*size,
                "size": size,
                "sort": [{"created_at": {"order": "desc"}}]
            }
        )

    # 批量操作
    def bulk_create(self, documents):
        """批量创建文档"""
        actions = [
            {
                "_index": self.index_name,
                "_source": {
                    "title": doc["title"],
                    "content": doc["content"],
                    "tags": doc.get("tags", []),
                    "created_at": "now"
                }
            }
            for doc in documents
        ]
        return bulk(self.es, actions)

# 使用示例
if __name__ == "__main__":
    # 初始化客户端
    es_lib = ElasticsearchTagLibrary()

    # 创建测试文档
    doc1 = es_lib.create_document(
        title="Python编程指南",
        content="Python编程的最佳实践...",
        tags=["programming", "python", "development"]
    )
    doc_id = doc1["_id"]

    # 添加新标签
    es_lib.add_tags(doc_id, ["backend", "tutorial"])

    # 删除标签
    es_lib.remove_tags(doc_id, ["development"])

    # 复杂查询示例:查找包含python标签且包含programming或backend标签,排除test标签
    results = es_lib.search_documents(
        must_tags=["python"],
        should_tags=["programming", "backend"],
        exclude_tags=["test"],
        size=5
    )
    print("查询结果:", results["hits"]["hits"])

    # 通配符查询:查找以"dev*"开头的标签
    wildcard_results = es_lib.search_documents(wildcard_tag="dev*")
    print("通配符查询结果:", wildcard_results["hits"]["hits"])

功能说明

  1. 索引设计

    • 使用keyword类型存储标签,确保精确匹配
    • 支持空标签处理(null_value)
    • 包含创建时间用于排序
  2. 核心功能

    • 文档的创建、读取、更新、删除
    • 标签的动态添加和移除
    • 批量文档创建
  3. 复杂查询支持

    • 多标签组合查询(AND/OR/NOT)
    • 全文搜索(标题和内容)
    • 通配符标签查询
    • 分页和排序支持
  4. 扩展功能

    • 相关性搜索(标题权重更高)
    • 自动处理空标签字段
    • 批量操作支持

典型使用场景

  1. 简单标签查询
python 复制代码
# 查找包含"python"和"web"标签的文档
results = es_lib.search_documents(must_tags=["python", "web"])
  1. 组合查询
python 复制代码
# 包含"python"且(包含"web"或"data")但不包含"legacy"
results = es_lib.search_documents(
    must_tags=["python"],
    should_tags=["web", "data"],
    exclude_tags=["legacy"]
)
  1. 通配符查询
python 复制代码
# 查找标签以"dev"开头的文档
results = es_lib.search_documents(wildcard_tag="dev*")
  1. 全文搜索+标签过滤
python 复制代码
# 搜索包含"最佳实践"且带有"python"标签的文档
results = es_lib.search_documents(
    query="最佳实践",
    must_tags=["python"]
)

注意事项

  1. 确保Elasticsearch服务已启动并正确配置
  2. 根据数据量调整分片数量和索引配置
  3. 重要操作(如删除)建议添加版本控制
  4. 生产环境需要处理并发冲突(retry_on_conflict参数)
  5. 对于大量标签的场景,建议定期清理无效标签

可以通过扩展search_documents方法添加更多高级功能:

  • 范围查询(按时间过滤)
  • 聚合统计(标签云)
  • 同义词搜索
  • 搜索结果高亮显示

参考文档

利用 Elasticsearch 实现高效标签(Tag)匹配

相关推荐
绝无仅有35 分钟前
使用 Docker 安装 Elastic Stack 并重置本地密码
后端·面试·github
老A技术联盟1 小时前
聊一聊消息中间件的后起之秀-pulsar及其实践
后端
隐-梵1 小时前
Android studio前沿开发--利用socket服务器连接AI实现前后端交互(全站首发思路)
android·服务器·人工智能·后端·websocket·android studio·交互
uhakadotcom1 小时前
Langflow:零基础快速上手AI流程可视化开发工具详解与实战案例
后端·面试·github
bobz9651 小时前
strongswan ipsec 端口使用
后端
陈哥聊测试1 小时前
这款自研底层框架,你说不定已经用上了
前端·后端·开源
一只叫煤球的猫1 小时前
分布式-跨服务事务一致性的常见解决方案
java·分布式·后端
扣丁梦想家1 小时前
Spring Boot 实现 Excel 导出功能(支持前端下载 + 文件流)
spring boot·后端·excel
调试人生的显微镜1 小时前
flutter ios 自定义ios插件
后端
仰望星空的打工人2 小时前
windows11家庭版安装docker
后端