性能优化大幅提升!Python 实现海量内容分词搜索引擎(3.0版)

2022-12-06 20:57:44 浏览数 (1)

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)对比核心代码的变化来体会优化思路。

0 人点赞