前言
在上一篇《MongoDB Atlas Local Docker 快速部署》中,我们成功在本地搭建了 MongoDB Atlas 环境。本文将在此基础上,实现一个适合中文场景的全文检索方案。
MongoDB Atlas Search 内置了基于 Apache Lucene 的 BM25 算法,但其默认分析器对中文支持不佳(会按字符分词)。本文介绍一种巧妙的解决方案:使用 jieba 在应用层完成中文分词,然后利用 Atlas Search 的 whitespace analyzer 和 BM25 算法完成检索。
技术方案概述
我们的方案分为两个层次:
应用层(Python):
- 使用 jieba 对中文文本进行分词
- 将分词结果用空格连接,存储到 MongoDB 的专用字段
数据库层(MongoDB Atlas Search):
- 在分词字段上创建搜索索引(使用 whitespace analyzer)
- Atlas Search 自动构建倒排索引
- 利用内置的 BM25 算法进行相关性排序
数据结构示例:
1 2 3 4 5 6
| { "_id": ObjectId("..."), "title": "机器学习入门教程", "content": "本文介绍机器学习的基础知识...", "content_segmented": "本文 介绍 机器 学习 的 基础知识 ..." }
|
倒排索引原理
当我们在 content_segmented 字段上创建 Atlas Search 索引时,MongoDB 会构建倒排索引(Inverted Index):
1 2 3 4
| 词项(Term) → 文档列表(Document IDs) "机器" → [doc1, doc3, doc5] "学习" → [doc1, doc2, doc5] "基础知识" → [doc1, doc4]
|
这样查询时可以快速定位包含特定词的文档,而不需要扫描全部数据。
全文检索流程
- 查询分词:用户输入”机器学习” → jieba 分词 → [“机器”, “学习”]
- 倒排查找:在倒排索引中查找包含这些词的文档
- BM25 评分:根据词频(TF)、逆文档频率(IDF)、文档长度等计算相关性分数
- 结果排序:按 BM25 分数降序返回最相关的文档
索引设计说明
你可以根据需求创建多个索引:
- 单字段索引:只对
content_segmented 建索引
- 多字段索引:同时对
title_segmented 和 content_segmented 建索引,可设置不同权重
本文以单字段索引为例进行演示。
环境准备
基于上一篇的 Docker 环境,安装必要的 Python 依赖:
1
| uv pip install jieba pymongo
|
步骤一:使用 jieba 分词并存储数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import jieba from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017/") db = client["search_demo"] collection = db["articles"]
documents = [ { "title": "机器学习入门教程", "content": "机器学习是人工智能的一个分支,它让计算机能够从数据中学习规律。" }, { "title": "深度学习实战", "content": "深度学习是机器学习的子领域,使用神经网络进行特征学习。" }, { "title": "自然语言处理技术", "content": "自然语言处理让计算机能够理解和生成人类语言。" } ]
for doc in documents: content_segmented = " ".join(jieba.cut(doc["content"]))
collection.insert_one({ "title": doc["title"], "content": doc["content"], "content_segmented": content_segmented })
print("数据插入完成!")
|
分词结果示例:
1 2
| 原文: "机器学习是人工智能的一个分支" 分词: "机器 学习 是 人工智能 的 一个 分支"
|
步骤二:创建 Atlas Search 索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| mappings = { "dynamic": False, "fields": { "content_segmented": { "type": "string", "analyzer": "lucene.whitespace" } } }
result = db.command( "createSearchIndexes", "articles", indexes=[{ "name": "content_search_index", "definition": { "mappings": mappings } }] )
print(f"索引创建成功: {result}")
|
索引配置说明:
dynamic: False:只索引明确指定的字段,提高效率
lucene.whitespace:按空格分词,完美适配 jieba 的分词结果
- 索引构建后,MongoDB 会自动生成倒排索引,支持 BM25 检索
查看索引状态:
1 2 3 4 5 6 7 8 9
| search_indexes = list(collection.list_search_indexes())
print(f"Atlas Search 索引数量: {len(search_indexes)}") print("\n索引详情:") for idx in search_indexes: print(f"- 索引名称: {idx['name']}") print(f" 状态: {idx.get('status', 'N/A')}") print(f" 类型: {idx.get('type', 'search')}")
|
输出示例:
1 2 3 4 5 6
| Atlas Search 索引数量: 1
索引详情: - 索引名称: content_search_index 状态: READY 类型: search
|
注意: 索引状态为 READY 时才能正常使用,初次创建可能需要等待几秒到几分钟。
步骤三:执行 BM25 全文检索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| def search_articles(query_text): query_segmented = " ".join(jieba.cut(query_text)) print(f"查询分词结果: {query_segmented}")
pipeline = [ { "$search": { "index": "content_search_index", "text": { "query": query_segmented, "path": "content_segmented" } } }, { "$project": { "title": 1, "content": 1, "score": {"$meta": "searchScore"} } }, { "$limit": 10 } ]
results = collection.aggregate(pipeline)
print("\n搜索结果:") for i, doc in enumerate(results, 1): print(f"{i}. [分数: {doc['score']:.4f}] {doc['title']}") print(f" {doc['content']}\n")
search_articles("机器学习")
|
查询结果示例:
1 2 3 4 5 6 7 8
| 查询分词结果: 机器 学习
搜索结果: 1. [分数: 0.5458] 深度学习实战 深度学习是机器学习的子领域,使用神经网络进行特征学习。
2. [分数: 0.4716] 机器学习入门教程 机器学习是人工智能的一个分支,它让计算机能够从数据中学习规律。
|
BM25 评分说明:
- 分数越高表示相关性越强
- 评分考虑了词频(TF)、逆文档频率(IDF)和文档长度归一化
- Atlas Search 自动使用 BM25 算法,无需额外配置
完整示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| import jieba from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017/") db = client["search_demo"] collection = db["articles"]
def insert_documents(): documents = [ { "title": "机器学习入门教程", "content": "机器学习是人工智能的一个分支,它让计算机能够从数据中学习规律。" }, { "title": "深度学习实战", "content": "深度学习是机器学习的子领域,使用神经网络进行特征学习。" }, { "title": "自然语言处理技术", "content": "自然语言处理让计算机能够理解和生成人类语言。" } ]
for doc in documents: content_segmented = " ".join(jieba.cut(doc["content"])) collection.insert_one({ "title": doc["title"], "content": doc["content"], "content_segmented": content_segmented })
print("数据插入完成!")
def create_search_index(): mappings = { "dynamic": False, "fields": { "content_segmented": { "type": "string", "analyzer": "lucene.whitespace" } } }
result = db.command( "createSearchIndexes", "articles", indexes=[{ "name": "content_search_index", "definition": { "mappings": mappings } }] )
print(f"索引创建成功: {result}")
def search_articles(query_text): query_segmented = " ".join(jieba.cut(query_text)) print(f"查询分词结果: {query_segmented}")
pipeline = [ { "$search": { "index": "content_search_index", "text": { "query": query_segmented, "path": "content_segmented" } } }, { "$project": { "title": 1, "content": 1, "score": {"$meta": "searchScore"} } }, { "$limit": 10 } ]
results = collection.aggregate(pipeline)
print("\n搜索结果:") for i, doc in enumerate(results, 1): print(f"{i}. [分数: {doc['score']:.4f}] {doc['title']}") print(f" {doc['content']}\n")
if __name__ == "__main__": insert_documents()
create_search_index()
search_articles("机器学习")
|
扩展阅读
相关文章:
- 上一篇:MongoDB Atlas Local Docker 快速部署