首页 > 数据库 >MongoDB 实现中文全文搜索

MongoDB 实现中文全文搜索

时间:2022-09-01 10:49:05浏览次数:112  
标签:中文 MongoDB list 模式 分词 seg id 全文 邮件

Prerequisite

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

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

一元分词和二元分词

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

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

编写索引程序

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

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 实现

"""
# 实际文档
{
  _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 数据库中查看

# 获取全部索引
# 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 来实现

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中文分词组件,它有一种搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词

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

标签:中文,MongoDB,list,模式,分词,seg,id,全文,邮件
From: https://www.cnblogs.com/CourserLi/p/16644260.html

相关文章

  • 为自定义域名的网站获取安全文件 (群晖7.X版)
    在上篇介绍中我们提到,虽然此时位于群晖NAS上的网站已经成功配置了自定义域名,但这个域名还只是http前缀,即没有配置现在流行的https协议,因此在访客浏览时,网站会被定义为不安......
  • idea使用jdk18控制台中文输出为乱码问题
    idea使用jdk18控制台中文输出为乱码问题问题描述:使用jdk18以下版本是,控制台中文正常显示,但换为jdk18是就输出乱码,但在终端或者cmd窗口运行java程序则正常显示中文......
  • Keras中文手册.pdf
    keras是一款使用纯python编写的神经网络API,使用keras能够快速实现你的深度学习方案,所以keras有着为快速试验而生的美称。keras以Tensorflow、Theano、CNTK为后......
  • Python极客项目编程 中文PDF完整版入门到精通
     《Python极客项目编程》中文PDF完整版免费下载地址内容简介  · · · · · · Python是一种强大的编程语言,容易学习而且充满乐趣。但掌握了基本知识后,......
  • CentOS 下 docker POSIX改中文
    cannotchangelocale(zh_CN.UTF-8):NosuchfileordirectoryCentOS下docker容器默认字符集为POSIX如果需要默认情况下查看字符集如下,可修改POSIX为 zh_CN.UTF-8......
  • Antd之No Data转换为中文
    Antd默认的暂无数据是英文的,如下图表格所示。修改的方法为:使用a-config-provider1.在App.vue增加a-config-provider,包装显示的页面<template><a-config-provider......
  • Nik Collection 5 for Mac(PS滤镜插件套装)中文版
    NikCollection中文版是一款ps滤镜插件套装,其包含了七款ps插件,功能涵盖修图、调色、降噪、胶片滤镜等方面。NikCollection作为很多摄影师和摄影爱好者所熟悉的一大照片......
  • 高亮显示并更改JTextArea中文本的颜色
    我必须编写一个程序,我必须突出显示并更改JTextarea中句子的颜色.我有一个名为原始文件和摘要文件的文件.原始文件将显示在textarea框中,我必须在textarea框中突出显示摘要......
  • NodeJS 网关 — 第 2 部分:设置我们的数据库 (MongoDB)
    NodeJS网关—第2部分:设置我们的数据库(MongoDB)Photoby鲁拜图尔·阿扎德on不飞溅NoSQL数据库使您可以轻松地开始使用基本模式开发项目或应用程序,并且由于......
  • Python - 处理 requets 请求接口时, 传输中文数据乱码问题
     #使用  ensure_ascii=False data={'name':'测试名称'}url="https://api.weixin.qq.com/xxx/"data=json.dumps(data,ensure_ascii=False)head......