筱团Blog筱团のBlog

MongoDB 实现中文全文搜索

筱团·2022-09-01 10:29·2597 次阅读

MongoDB 实现中文全文搜索

Prerequisite

倒排索引是所有支持全文搜索的数据库的基础。比如 i am iron mani will be soon back,欲查找 be,先查第一句,再查第二局,这是正排;将每个单词提取出来形成一个排序,i {1, 2}am {1}be {2} 形成一个排序,再要搜索 be 的时候,立刻就搜索到了,并且知道对应第二句,这就是倒排。

MongoDB 内置的默认分词器,是按空格分切即可,这是因为英文默认的,那么倒排的最小单位就是单词。如果文档中存在中文,句子直接没有空格,那么倒排的最小单位就是一个字了,解决这个问题也很简单,就是找到最合适的中文分词。

一元分词和二元分词#

所谓一元分词:就是一个字一个字地切分,把字当成词(我是团子 ->
所谓二元分词:就是按两字两个分词(我是团子 -> 我是是团团子

很正常的想到,如果将二元分词,作为中文的倒排最小单位,那么就可以全文检索中文了,那么来写实现吧 ~

编写索引程序#

先来写二元分词生成器,这个很简单

Copy
def bigram_tokenize(word): return ' '.join(word[i:i + 2] for i in range(len(word)) if i + 2 <= len(word)) print(bigram_tokenize('我是团子')) # 我是 是团 团子

再对 MongoDB 建立二元分词索引,采用的方法是:创建 _t 字段,用于储存一个句子的全部二元分词即可
我选择使用 pymongo 实现

Copy
""" # 实际文档 { _id: ObjectId("630d708529bd493430410366"), desc: '切换至聚合模式', method: 'put', uri: '/api/v2/mail_setting', body: '{\n\t"general": {\n\t\t"browse_mode": 1\n\t}\n}' } """ # 编写二元分词索引程序(用 pymongo 实现) from pymongo import MongoClient client = MongoClient('localhost', 27017) db = client["邮件"] collection = db['邮件多选'] def build_fts(): # 在 _t 字段建立全文索引 collection.create_index([('_t', 'text')]) # 在 _t 字段储存 desc 的全部二元分词 for item in collection.find(): collection.update_one({'_id': item['_id']}, {'$set': {'_t': bigram_tokenize(item['desc'])}}) if __name__ == "__main__": build_fts()

二元分词索引#

那么再来看一下,如何实现检索的,不妨在 MongoDB 数据库中查看

Copy
# 获取全部索引 # db.邮件多选.getIndexes() # 在 _t 字段建立全文索引 # db.邮件多选.ensureIndex({_t:"text"}) # 获取集合下全部文档 db.邮件多选.find().pretty() [ { _id: ObjectId("630d708529bd493430410366"), desc: '切换至聚合模式', method: 'put', uri: '/api/v2/mail_setting', body: '{\n\t"general": {\n\t\t"browse_mode": 1\n\t}\n}', _t: '切换 换至 至聚 聚合 合模 模式' }, { _id: ObjectId("630d708529bd493430410367"), desc: '切换至非聚合模式', method: 'put', uri: '/api/v2/mail_setting', body: '{\n\t"general": {\n\t\t"browse_mode": 2\n\t}\n}', _t: '切换 换至 至非 非聚 聚合 合模 模式' }, { _id: ObjectId("630d74dac79d6354805b86c3"), desc: 'all', method: 'put', uri: '/api/v2/mail_setting', body: '{\n\t"general": {\n\t\t"browse_mode": 1\n\t}\n}', params_dict: { mail_id: '1438', mbox: 'INBOX' }, extracts_dict: { result: '$.result' }, validations_dict: { '$.result': 'ok' }, _t: 'al ll' } ] # 检索目标分词 db.邮件多选.find({$text:{$search:"聚合"}}) [ { _id: ObjectId("630d708529bd493430410366"), desc: '切换至聚合模式', method: 'put', uri: '/api/v2/mail_setting', body: '{\n\t"general": {\n\t\t"browse_mode": 1\n\t}\n}', _t: '切换 换至 至聚 聚合 合模 模式' }, { _id: ObjectId("630d708529bd493430410367"), desc: '切换至非聚合模式', method: 'put', uri: '/api/v2/mail_setting', body: '{\n\t"general": {\n\t\t"browse_mode": 2\n\t}\n}', _t: '切换 换至 至非 非聚 聚合 合模 模式' } ]

再用 pymongo 来实现

Copy
from pymongo import MongoClient client = MongoClient('localhost', 27017) db = client["邮件"] collection = db['邮件多选'] def bigram_tokenize(word): return ' '.join(word[i:i + 2] for i in range(len(word)) if i + 2 <= len(word)) # 全文检索 for post in collection.find({'$text': {'$search': f'"{bigram_tokenize("聚合模式")}"'}}): pprint.pprint(post) {'_id': ObjectId('630d708529bd493430410366'), '_t': '切换 换至 至聚 聚合 合模 模式', 'body': '{\n\t"general": {\n\t\t"browse_mode": 1\n\t}\n}', 'desc': '切换至聚合模式', 'method': 'put', 'uri': '/api/v2/mail_setting'} {'_id': ObjectId('630d708529bd493430410367'), '_t': '切换 换至 至非 非聚 聚合 合模 模式', 'body': '{\n\t"general": {\n\t\t"browse_mode": 2\n\t}\n}', 'desc': '切换至非聚合模式', 'method': 'put', 'uri': '/api/v2/mail_setting'}

重点就在于,只用检索其中的文字(如 “切换至聚合模式” 中的 “聚合模式”),就可以检索出来
本质就在于,二元分词之间有空格隔开,所以可以像英语那样搜索得到

初步结果,可以实现中文全文检索,但效率不高,因为一个句子的二元分词占用太多,如果可以缩减分词数量就好了

优化#

结巴中文分词优化,最流行的Python中文分词组件,它有一种搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词

Copy
import jieba import paddle import pprint paddle.enable_static() test = "批量星标(前两封邮件打星标)" jieba.enable_paddle() strs=[test] for str in strs: seg_list = jieba.cut(str,use_paddle=True) # 使用 paddle 模式 print("Paddle Mode: " + '/'.join(list(seg_list))) seg_list = jieba.cut(test, cut_all=True) print("Full Mode: " + "/ ".join(seg_list)) # 全模式 seg_list = jieba.cut(test, cut_all=False) print("Default Mode: " + "/ ".join(seg_list)) # 精确模式 seg_list = jieba.cut(test) # 默认是精确模式 print(", ".join(seg_list)) seg_list = jieba.cut_for_search(test) # 搜索引擎模式 print(", ".join(seg_list)) Paddle Mode: 批量/星标/(/前/两封/邮件/打/星标/) Full Mode: 批量/ 星/ 标/ (/ 前/ 两封/ 邮件/ 打/ 星/ 标/ ) Default Mode: 批量/ 星标/ (/ 前/ 两封/ 邮件/ 打星标/ ) 批量, 星标, (, 前, 两封, 邮件, 打星标, ) 批量, 星标, (, 前, 两封, 邮件, 打星标, )

其他优化

  • 组合全文索引(Compound textIndex)
  • 用户体验优化
  • 实时性优化

References

posted @   筱团  阅读(2600)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示
目录