当前位置:   article > 正文

Python实战之手写一个搜索引擎_python搜索引擎

python搜索引擎


一、前言

这篇文章,我们将会尝试从零搭建一个简单的新闻搜索引擎

当然,一个完整的搜索引擎十分复杂,这里我们只介绍其中最为核心的几个模块

分别是数据模块、排序模块和搜索模块,下面我们会逐一讲解,这里先从宏观上看一下它们之间的工作流程

二、工作流程

在这里插入图片描述

三、数据模块

数据模块的主要作用是爬取网络上的数据,然后对数据进行清洗并保存到本地存储

一般来说,数据模块会采用非定向爬虫技术广泛爬取网络上的数据,以保证充足的数据源

但是由于本文只是演示,所以这里我们仅会采取定向爬虫爬取中国社会科学网上的部分文章素材

而且因为爬虫技术我们之前已经讲过很多,这里就不打算细讲,只是简单说明一下流程

首先我们定义一个数据模块类,名为 DataLoader,类中有一个核心变量 data 用于保存爬取下来的数据

以及两个相关的接口 grab_data (爬取数据) 和 save_data (保存数据到本地)

grab_data() 的核心逻辑如下:

1.首先调用 get_entry(),获取入口链接

def get\_entry(self):
    baseurl = 'http://his.cssn.cn/lsx/sjls/'
    entries = \[\]
    for idx in range(5):
        entry = baseurl if idx == 0 else baseurl + 'index\_' + str(idx) + '.shtml'
        entries.append(entry)
    return entries

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.然后调用 parse4links(),遍历入口链接,解析得到文章链接

def parse4links(self, entries):
    links = \[\]
    headers = {
        'USER-AGENT': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
    }
    for entry in entries:
        try:
            response = requests.get(url = entry, headers = headers)
            html = response.text.encode(response.encoding).decode('utf-8')
            time.sleep(0.5)
        except:
            continue

        html\_parser = etree.HTML(html)
        link = html\_parser.xpath('//div\[@class="ImageListView"\]/ol/li/a/@href')
        link\_filtered = \[url for url in link if 'www' not in url\]
        link\_complete = \[entry + url.lstrip('./') for url in link\_filtered\]
        links.extend(link\_complete)

    return links

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

3.接着调用 parse4datas(),遍历文章链接,解析得到文章内容

def parse4datas(self, entries):
    datas = \[\]
    headers = {
        'USER-AGENT': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
    }
    data\_count = 0
    for entry in entries:
        try:
            response = requests.get(url = entry, headers = headers)
            html = response.text.encode(response.encoding).decode('utf-8')
            time.sleep(0.2)
        except:
            continue

        html\_parser = etree.HTML(html)
        title = html\_parser.xpath('//span\[@class="TitleFont"\]/text()')
        content = html\_parser.xpath('//div\[@class="TRS\_Editor"\]//p//text()')
        content = \[cont.replace('\\u3000', '').replace('\\xa0', '').replace('\\n', '').replace('\\t', '') for cont in content\]
        content = \[cont for cont in content if len(cont) > 30 and not re.search(r'\[《|》\]', cont)\]

        if len(title) != 0 or len(content) != 0:
            data\_count += 1
            datas.append({
                'id'  : data\_count,
                'link': entry,
                'cont': '\\t'.join(content),
                'title': title\[0\]
            })

    return datas

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

grab_data() 的核心代码如下:

def grab\_data(self):
    # 获取入口链接
    entries = self.get\_entry()
    # 遍历入口链接,解析得到文章链接
    links = self.parse4links(entries)
    # 遍历文章链接,解析得到文章内容
    datas = self.parse4datas(links)
    # 将相关数据写入变量 data
    self.data = pd.DataFrame(datas)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

save_data() 的核心代码如下:

def save\_data(self):
    # 将变量 data 写入 csv 文件
    self.data.to\_csv(self.data\_path, index = None)
  • 1
  • 2
  • 3

至此,我们已经爬取并保存好数据 data,数据以 DataFrame 形式存储,保存在 csv 文件,格式如下:

|---------------------------------------------------|
|    id    |     link   |     cont     |    title   |
|---------------------------------------------------|
|  page id |  page link | page content | page title |
|---------------------------------------------------|
|  ......  |   ......   |    ......    |   ......   |
|---------------------------------------------------|
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

四、索引模块

索引模型的主要作用是构建倒排索引 (inverted index),这是搜索引擎中十分关键的一环

一般来说,构建索引的目的就是为了提高查询速度

普通的索引一般是通过文章标识索引文章内容,而倒排索引则正好相反,通过文章内容索引文章标识

具体来说,倒排索引会以文章中出现过的词语作为键,以该词所在的文章标识作为值来构建索引

首先我们定义一个索引模块类,名为 IndexModel,类中有一个核心变量 iindex 用于保存倒排索引

以及两个相关的接口 make_iindex (构建索引) 和 save_iindex (保存索引到本地)

make_iindex() 的核心代码如下(具体逻辑请参考注释):

def make\_iindex(self):
    # 读取数据
    df = pd.read\_csv(self.data\_path)
    # 特殊变量,用于搜索模块
    TOTAL\_DOC\_NUM = 0 # 总文章数量
    TOTAL\_DOC\_LEN = 0 # 总文章长度
    # 遍历每一行
    for row in df.itertuples():
        doc\_id = getattr(row, 'id') # 文章标识
        cont = getattr(row, 'cont') # 文章内容

        TOTAL\_DOC\_NUM += 1
        TOTAL\_DOC\_LEN += len(cont)

        # 对文章内容分词
        # 并将其变成 {word: frequency, ...} 的形式
        cuts = jieba.lcut\_for\_search(cont)
        word2freq = self.format(cuts)

        # 遍历每个词,将相关数据写入变量 iindex
        for word in word2freq:
            meta = {
                'id': doc\_id,
                'dl': len(word2freq),
                'tf': word2freq\[word\]
            }
            if word in self.iindex:
                self.iindex\[word\]\['df'\] = self.iindex\[word\]\['df'\] + 1
                self.iindex\[word\]\['ds'\].append(meta)
            else:
                self.iindex\[word\] = {}
                self.iindex\[word\]\['df'\] = 1
                self.iindex\[word\]\['ds'\] = \[\]
                self.iindex\[word\]\['ds'\].append(meta)

    # 将特殊变量写入配置文件
    self.config.set('DATA', 'TOTAL\_DOC\_NUM', str(TOTAL\_DOC\_NUM)) # 文章总数
    self.config.set('DATA', 'AVG\_DOC\_LEN', str(TOTAL\_DOC\_LEN / TOTAL\_DOC\_NUM)) # 文章平均长度
    with open(self.option\['filepath'\], 'w', encoding = self.option\['encoding'\]) as config\_file:
        self.config.write(config\_file)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

save_iindex() 的核心代码如下:

def save\_iindex(self):
    # 将变量 iindex 写入 json 文件
    fd = open(self.iindex\_path, 'w', encoding = 'utf-8')
    json.dump(self.iindex, fd, ensure\_ascii = False)
    fd.close()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

至此,我们们经构建并保存好索引 iindex,数据以 JSON 形式存储,保存在 json 文件,格式如下:

{
    word: {
        'df': document\_frequency,
        'ds': \[{
            'id': document\_id,
            'dl': document\_length,
            'tf': term\_frequency
        }, ...\]
    },
    ...
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

五、搜索模块

在得到原始数据和构建好倒排索引后,我们就可以根据用户的输入查找相关的内容

具体怎么做呢?

1.首先我们对用户的输入进行分词

2.然后根据倒排索引获取每一个词相关的文章

3.最后计算每一个词与相关文章之间的得分,得分越高,说明相关性越大

这里我们定义一个搜索模块类,名为 SearchEngine,类中有一个核心函数 search 用于查询搜索

def search(self, query):
    BM25\_scores = {}

    # 对用户输入分词
    # 并将其变成 {word: frequency, ...} 的形式
    query = jieba.lcut\_for\_search(query)
    word2freq = self.format(query)

    # 遍历每个词
    # 计算每个词与相关文章之间的得分(计算公式参考 BM25 算法)
    for word in word2freq:
        data = self.iindex.get(word)
        if not data:
            continue
        BM25\_score = 0
        qf = word2freq\[word\]
        df = data\['df'\]
        ds = data\['ds'\]
        W = math.log((self.N - df + 0.5) / (df + 0.5))
        for doc in ds:
            doc\_id = doc\['id'\]
            tf = doc\['tf'\]
            dl = doc\['dl'\]
            K = self.k1 \* (1 - self.b + self.b \* (dl / self.AVGDL))
            R = (tf \* (self.k1 + 1) / (tf + K)) \* (qf \* (self.k2 + 1) / (qf + self.k2))
            BM25\_score = W \* R
            BM25\_scores\[doc\_id\] = BM25\_scores\[doc\_id\] + BM25\_score if doc\_id in BM25\_scores else BM25\_score

    # 对所有得分按从大到小的顺序排列,返回结果
    BM25\_scores = sorted(BM25\_scores.items(), key = lambda item: item\[1\])
    BM25\_scores.reverse()
    return BM25\_scores

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

示例代码仅供参考和学习


关于Python技术储备

学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

保存图片微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
在这里插入图片描述

二、Python基础学习视频

② 路线对应学习视频

还有很多适合0基础入门的学习视频,有了这些视频,轻轻松松上手Python~在这里插入图片描述
在这里插入图片描述

③练习题

每节视频课后,都有对应的练习题哦,可以检验学习成果哈哈!
在这里插入图片描述
因篇幅有限,仅展示部分资料

三、精品Python学习书籍

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。
在这里插入图片描述

四、Python工具包+项目源码合集
①Python工具包

学习Python常用的开发软件都在这里了!每个都有详细的安装教程,保证你可以安装成功哦!
在这里插入图片描述

②Python实战案例

光学理论是没用的,要学会跟着一起敲代码,动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。100+实战案例源码等你来拿!
在这里插入图片描述

③Python小游戏源码

如果觉得上面的实战案例有点枯燥,可以试试自己用Python编写小游戏,让你的学习过程中增添一点趣味!
在这里插入图片描述

五、面试资料

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
在这里插入图片描述
在这里插入图片描述

六、Python兼职渠道

而且学会Python以后,还可以在各大兼职平台接单赚钱,各种兼职渠道+兼职注意事项+如何和客户沟通,我都整理成文档了。
在这里插入图片描述
在这里插入图片描述
这份完整版的Python全套学习资料已经上传CSDN,朋友们如果需要可以保存图片微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/608006
推荐阅读
相关标签
  

闽ICP备14008679号