搜索引擎的原理

搜索引擎具体实现起来分为以下四个方面:搜集、分析、索引、查询。本文根据整个构建过程中涉及的文件来串起整个实现过程,仅仅是串起来。

搜集(利用爬虫爬取网页)

原理剖析

搜索引擎把整个互联网看作是数据结构中的有向图,把每个页面看作一个顶点,如果某个页面中包含另外一个页面的链接,那我们就在两个顶点之间连一条有向边。可以利用图的遍历搜索算法,来遍历整个互联网中的网页。

搜搜索引擎采用的是广度优先算法。先找一些权重比较高的链接,作为种子网页链接,放入队列中。爬虫按照广度优先的策略,不停的从队列中取出链接,然后爬取相应的网页,解析出网页里包含的其他网页链接,再将解析出来的链接添加到队列中

待爬取的网页链接文件(links.bin)

利用一个存储在磁盘中的文件来作为广度优先搜索中的队列。爬虫从links.bin文件中,取出链接爬取对应的页面。等爬取到网页之后,将解析出来的链接,直接存储到links.bin文件中.

将链接都存储在文件中的优势是:支持断点续爬,当机器断电之后,网页链接不会丢失;当机器重启之后,还可以从之前爬取到的位置继续爬取。

如何解析页面获取链接?把整个页面看作一个大的字符串,我们可以利用字符串匹配算法,在这个大字符串中,搜索网页标签,然后顺序读取之间的字符串。这就是网页链接

网页判重文件(bloom_flter.bin)

使用布隆过滤器,可以快速并且非常节省内存地实现网页的判重

如果把布隆过滤器存储在内存中,那机器宕机重启之后,布隆过滤器就被清空了。这样就导致大量已经爬取的网页会被重复爬取!我们可以定期地将布隆过滤器持久化到磁盘中,存储在bloom_filter.bin文件中。当机器重启之后,我们就可以重新读取磁盘中bloom_filter.bin文件,将其恢复到内存中

原始网页存储文件(doc_raw.bin)

我们把多个网页存储在一个文件中。每个网页之间,通过一定的标识进行分割,方便后序读取。格式如下:
doc1_id \t doc1_sieze \t doc1 \r\n\r\n
网页编号 分隔符 网页大小 分隔符 网页 分隔符

注:这样的文件不能太大,可以设置每个文件的大小不能超过一定的值。当文件大小超过设定的值时,我们就创建一个新的文件,用来存取新爬取的网页

网页链接及其编号涉及的对应文件(doc_id.bin)

网页编号实际上就是给每个网页分配一个唯一的ID,方便我们后续对网页进行分析、索引。

我们维护一个中心的计数器,每爬取到一个网页之后,就从计数器中拿一个号码,分配给这个网页,然后计数器加一。在存储网页的同时,我们将网页链接跟编号之前的对应关系,存储在另一个doc_id.bin文件中

分析(网页内容抽取、分词,构建临时索引,甲酸PageRank值)

原理剖析

对爬虫获取的网页进行离线分析:
第一步:抽取网页文本信息;

第二步:分词并创建临时索引

抽取网页文本信息过程

第一步:去掉JavaScript代码、CSS格式以及下拉框中的内容。也就是,,这三组标签之间的内容。可以利用AC自动机实现。

第二步:去掉所有HTML标签。可以通过字符串匹配算法来实现

分词并创建临时索引
临时索引文件 tmp_Index.bin

分词我们使用比较简单的思路:基于字典和规则的分词方法

我们可以直接从网上下载别人整理好的字典(词库)、我们采用最长匹配规则,来对文本进行分词;我们可以将词库中的单词,构建成Trie树结构,然后拿网页文本在Trie树中匹配。

每个网页的文本信息在分词完成之后,我们都得到一组单词列表。我们把单词与网页之间的对应关系,写入到临时索引文件中(这个临时索引文件用来构建倒排索引文件),格式如下:

term1_id \t doc_id \r\n
单词编号 分隔符 网页编号 分隔符

单词编号文件 term_id.bin

给单词编号的方式,跟给网页编号类似。我们维护一个计数器,每当从网页文本信息中分割出一个新的单词的时候,我们就从计数器中取一个编号,分配给它,然后计数器加一

我们需要使用散列表,记录已经编过号的单词。在对网页文本信息分词的过程中,我们拿分割出来的单词,先到散列表中查找,如果找到,那就直接使用已有的编号;如果没有找到,我们再去计数器中拿号码,并且将这个新单词以及编号添加到散列表中

单词跟编号之间的对应关系写入到term_id.bin文件中

索引(通过分析阶段得到的临时索引,构建倒排序索引)

原理剖析

我们先对临时索引文件,按照单词编号的大小进行排序。

临时索引文件很大,所以一般基于内存的排序算法就没法处理这个问题了。我们可以用归并排序的处理思想,将其分割成多个小文件,先对每个小文件独立排序,最后再合并在一起。

临时索引文件排序完成之后,相同的单词就被排列到了一起,我们只需要顺序地遍历排好序的临时索引文件,就能将每个单词对应的网页编号列表找出来,然后把它们存储到倒排索引文件中。

创建索引
倒排索引文件index.bin

结构图:
term_id \t did1,did2,did3,…,didx \r\n
单词编号 分隔符 网页编号列表 分隔符

单词编号在索引文件中的偏移位置文件 term_offset.bin

帮助我们快速地查找某个单词编号在倒排索引中存储的位置,进而快速地从倒排索引中读取单词编号对应的网页编号列表,结构如下:

term_id1 \t offset1 \r\n
单词编号 分隔符 偏移位置 分隔符

正排索引和倒排索引

正排索引

由key查询实体的过程,是正排索引

网页内容分词后,page_content会对应一个分词后的集合list;正排索引可以理解为Map<url,list>,能够由网页快速(时间复杂度O(1))找到内容的一个数据结构

倒排索引

由item查询key的过程,是倒排索引

对于网页搜索,倒排搜索可以理解为Map<item,list>,能够由查询词快速(时间复杂度O(1))找到包含这个查询词的网页的数据结构

实现搜索引擎搜索关键字功能 搜索引擎的实现方法_搜索引擎原理

查询(响应用户的请求,根据倒排序索引获取相关网页,计算网页排名,返回查询结果给用户)

准备工作

为了更快很好的查询,我们进行了上面三个步骤的准备,得到了四个搜索过程中用到的文件

  1. doc_id.bin : 记录网页链接和编号之间的对应关系
  2. term_id.bin : 记录单词和编号之间的对应关系
  3. index.bin : 倒排索引文件,记录每个单词编号以及对应包含它的网页编号列表
  4. term_offset.bin : 记录每个单词编号在倒排索引文件中的偏移位置

搜索过程

当用户在搜索框中,输入某个查询文本的时候,我们先对用户输入的文本进行分词处理。假设在分词之后,我们得到k个单词

我们拿着k个单词,去term_id.bin对应的散列表中,查找对应的单词编号。经过这个查询之后,我们得到了这k个单词对应的单词编号

我们拿着k个单词编号,去term_offset.bin对应的散列表中,查找每个单词编号在倒排索引文件中的偏移位置。经过这个查询之后,我们得到了k个偏移位置

我们拿这k个偏移位置,去倒排索引(index.bin)中,查找k个单词对应的包含它的网页编号列表。经过这一步查询之后,我们得到了k个网页编号列表。

我们针对这k个网页编号列表,统计每个网页编号出现的次数。具体实现,我们可以借助散列表来进行统计。统计得到的结果,我们按照出现次数的多少,从小到大排序。出现次数越多,说明包含越多的用户查询单词

经过这一系列查询,我们就得到了一组排好序的网页编号。我们拿着网页编号,去doc_id.bin文件中查找对应的网页链接,分页显示给用户就可以了