MongoDB 实现中文全文搜索
<h1 id="prerequisite">Prerequisite</h1><p>倒排索引是所有支持全文搜索的数据库的基础。比如 <code>i am iron man</code> 和 <code>i will be soon back</code>,欲查找 <code>be</code>,先查第一句,再查第二局,这是正排;将每个单词提取出来形成一个排序,<code>i {1, 2}</code>、<code>am {1}</code> 和 <code>be {2}</code> 形成一个排序,再要搜索 <code>be</code> 的时候,立刻就搜索到了,并且知道对应第二句,这就是倒排。</p>
<p>MongoDB 内置的默认分词器,是按空格分切即可,这是因为英文默认的,那么倒排的最小单位就是单词。如果文档中存在中文,句子直接没有空格,那么倒排的最小单位就是一个字了,解决这个问题也很简单,就是找到最合适的中文分词。</p>
<h3 id="一元分词和二元分词">一元分词和二元分词</h3>
<p>所谓一元分词:就是一个字一个字地切分,把字当成词(<code>我是团子</code> -> <code>我</code>、<code>是</code>、<code>团</code>、<code>子</code>)<br>
所谓二元分词:就是按两字两个分词(<code>我是团子</code> -> <code>我是</code>、<code>是团</code>、<code>团子</code>)</p>
<p>很正常的想到,如果将二元分词,作为中文的倒排最小单位,那么就可以全文检索中文了,那么来写实现吧 ~</p>
<h3 id="编写索引程序">编写索引程序</h3>
<p>先来写二元分词生成器,这个很简单</p>
<pre><code class="language-python">def bigram_tokenize(word):
return ' '.join(word for i in range(len(word)) if i + 2 <= len(word))
print(bigram_tokenize('我是团子')) # 我是 是团 团子
</code></pre>
<p>再对 MongoDB 建立二元分词索引,采用的方法是:创建 <code>_t</code> 字段,用于储存一个句子的全部二元分词即可<br>
我选择使用 pymongo 实现</p>
<pre><code class="language-python">"""
# 实际文档
{
_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()
</code></pre>
<h3 id="二元分词索引">二元分词索引</h3>
<p>那么再来看一下,如何实现检索的,不妨在 MongoDB 数据库中查看</p>
<pre><code class="language-cmd"># 获取全部索引
# 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: '切换 换至 至非 非聚 聚合 合模 模式'
}
]
</code></pre>
<p>再用 pymongo 来实现</p>
<pre><code class="language-python">from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client["邮件"]
collection = db['邮件多选']
def bigram_tokenize(word):
return ' '.join(word 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'}
</code></pre>
<p>重点就在于,只用检索其中的文字(如 “切换至聚合模式” 中的 “聚合模式”),就可以检索出来<br>
本质就在于,二元分词之间有空格隔开,所以可以像英语那样搜索得到</p>
<p>初步结果,可以实现中文全文检索,但效率不高,因为一个句子的二元分词占用太多,如果可以缩减分词数量就好了</p>
<h3 id="优化">优化</h3>
<p><strong>结巴中文分词优化</strong>,最流行的Python中文分词组件,它有一种搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词</p>
<pre><code class="language-python">import jieba
import paddle
import pprint
paddle.enable_static()
test = "批量星标(前两封邮件打星标)"
jieba.enable_paddle()
strs=
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: 批量/ 星标/ (/ 前/ 两封/ 邮件/ 打星标/ )
批量, 星标, (, 前, 两封, 邮件, 打星标, )
批量, 星标, (, 前, 两封, 邮件, 打星标, )
</code></pre>
<p><strong>其他优化</strong></p>
<ul>
<li>组合全文索引(Compound textIndex)</li>
<li>用户体验优化</li>
<li>实时性优化</li>
</ul>
<h1 id="references">References</h1>
<ul>
<li>纯 MongoDB 实现中文全文搜索</li>
<li>菜鸟教程:MongoDB 全文检索</li>
</ul>
</div>
<div id="MySignature" role="contentinfo">
喜欢划水摸鱼的废人<br><br>
来源:https://www.cnblogs.com/CourserLi/p/16644260.html
頁:
[1]