1、前言
前期分享文章
仅30行代码,实现一个搜索引擎(1.0版)
短短几十行 Python 代码,实现分词功能搜索引擎(2.0版)
分别介绍:
如何使用 30 行 Python 代码快速实现一个简易版搜索引擎;
通过对检索内容进行分词的升级版搜索引擎;
具体 Python 源码实现请点击上方链接阅读与获取。
缺陷:
1.0版本搜索引擎:仅支持单个词语的检索,当检索文件内容量大,文件个数多时检索效率低。
2.0版本搜索引擎:每次查询时都需要遍历所有文件及其内容,如果检索文件数量庞大,每次都全部遍历十分耗时。
2、优化思路
每次需要检索的单词数量不会很多,最多在十几,二十个左右,试着从这里着手优化呢?
在前面两个版本中,使用文件名作为 key,其内容作为 value 的格式存储于字典中,每次检索时需要遍历每个单词,再遍历每个单词是否在每个文件中。
如果把文件内容的每个单词作为 key,其出现在哪些文件中作为 value, 这样就可以只需程序第一次启动时进行全量文件内容的计算,得出一个结果字典。
以后每一次检索都只从结果字典中去查找遍历就好了,结果字典不需要随着每次检索而重新计算,又节省了一笔开销。(毕竟检索词库不会频繁更新)
这种 key,value 的处理方式也就是十分著名的搜索引擎方法——倒序索引
在检索时只需要将被检索的文本内容对应的 value 拿出来,然后再去寻找这些 value 之间共有的元素即文件名称,就能得到检索想要的结果。
思路梳理
思路清晰后,实现方式就不限了,这里我采用的是使用 Python 多个列表间求交集来实现,具体实现方式请参见下方的源码。
3、Python 核心代码实现
代码语言:javascript复制import re
from functools import reduce
from BaseEngine import SearchEngineBase, main
class BOWInvertedModelEngine(SearchEngineBase):
def __init__(self):
"""
1.super(BOWInvertedModelEngine, self).__init__()含义是指:对继承自父类的属性使用父类的初始化方法进行初始化。
2.这里的__init__()括号里可以加上父类中初始化时定义的属性,因为此处父类初始化时没有定义任何属性,所以这里括号里为空。
"""
super(BOWInvertedModelEngine, self).__init__()
self.inverted_index = {} # 子类BOWInvertedModelEngine自定义的私有属性
def process_search_contents(self, file_path, content):
"""
该函数实现功能:重写了父类的process_search_contents方法, 将每个文件对应的文件内容中每个单词作为key,
该单词所出现在哪些文件中以append方式写入list作为value填充inverted_index字典。
:param file_path: 完整路径下的文件名称,例如:/search_contents/1.txt
:param content: 具体文件内容
:return:
"""
words = self.parse_text_to_words(content) # 将每个文件对应的文本内容进行一定规则处理后返回无重复的单词set(集合)
for word in words:
"""
1、遍历set集合words,如果word作为key不存在于inverted_index字典中,
则填充字典inverted_index的格式为:inverted_index = {'word':[]}
2、然后再将words文本内容对应的文件名append至空[]中
3、如果同一个word作为key,其value非空[],则'word not in self.inverted_index'条件不成立时直接
将文件名append至已有的列表中,最终可能出现的数据格式为:
inverted_index = {'a':['1.txt','2.txt'], 'b':['3.txt']}
"""
if word not in self.inverted_index:
self.inverted_index[word] = []
self.inverted_index[word].append(file_path)
def search(self, query_content):
"""
:param query_content: 需要被检索的内容
:return: 检索结果文件list
"""
query_contents = list(self.parse_text_to_words(query_content)) # 将需要检索的文本内容进行一定规则处理后返回无重复的单词set(集合)并将其强转为list类型
# 如果需要检索的文本内容(每个单词)只要有一个不存在于inverted_index字典的key中,则说明检索无结果,返回空list
for query_content in query_contents:
if query_content not in self.inverted_index:
return []
# 逻辑走到这里说明检索的单词作为key在词库inverted_index中有对应的value,即至少有一个文件中存在该key对应的检索单词
query_key = []
query_value = []
for query_content in query_contents:
query_key.append(query_content)
query_value.append(self.inverted_index[query_content])
query_result_dict = dict(zip(query_key, query_value)) # 将检索语句按照每个单词(key)及其所在的文件名(value为list类型)组装成字典
result = []
for v in query_result_dict.values():
result.append(v) # 将所有被检索单词对应的value(list类型)统一追加至result
return list(reduce(lambda x, y: set(x) & set(y), result)) # 求result列表中多个小列表之间的交集,即是要求的最终结果list
@staticmethod
def parse_text_to_words(content):
"""
该函数实现功能:将检索文本内容进行一定规则处理后返回无重复的单词set(集合)
:param content: 检索文本,例如:we will alive long
:return: 无重复单词的集合,格式为:{'we','will','alive'}
"""
content = re.sub(r'[^w ]', ' ', content) # 使用正则表达式去除标点符号和换行符
content = content.lower() # 搜索文本全部转换为小写
word_list = content.split(' ') # 使用空格将文本内容进行分隔,生成所有单词的列表
word_list = filter(None, word_list) # 生成的单词列表再去除空白单词
return set(word_list) # 返回单词的set(无重复的集合), 格式为: {'we','will','alive'}
search_engine = BOWInvertedModelEngine()
main(search_engine)
PS:
1.核心代码块中每个函数实现的功能都有详细的解释说明,请注意仔细阅读,这将非常有助于理解搜索引擎的执行流程和代码流的流转。
2.检索文件内容和被继承的基类SearchEngineBase
实现代码都是和
仅30行代码,实现一个搜索引擎(1.0版)
短短几十行 Python 代码,实现分词功能搜索引擎(2.0版)
这两篇文章中所使用的内容是一模一样的,本次只优化了继承父类的子类实现代码。
4、实现效果预览
至此,性能优化后的高阶版搜索引擎就完成了。
PS:
源码包中包含了1.0和2.0版本的实现代码和搜索样本文件,可以由简到难(1.0->2.0->3.0)对比核心代码的变化来体会优化思路。