文章目录
搜索引擎是如何应用数据结构和算法的?
1. 整体系统介绍
搜索引擎大致可以分为四个部分:搜集、分析、索引、查询。
- 搜集,就是我们常说的利用爬虫爬取网页。
- 分析,主要负责网页内容抽取、分词,构建临时索引,计算PageRank值这几部分工作。
- 索引,主要负责通过分析阶段得到的临时索引,构建倒排索引。
- 查询,主要负责响应用户的请求,根据倒排索引获取相关网页,计算网页排名,返回查询结果给用户。
2. 搜集
搜索引擎把整个互联网看作数据结构中的有向图,把每个页面看作一个顶点。如果某个页面中包含另外一个页面的链接,那我们就在两个顶点之间连一条有向边。我们可以利用图的遍历搜索算法,来遍历整个互联网中的网页。
搜索引擎采用的是广度优先搜索策略。
2.1 待爬取网页链接文件:links.bin
链接很多,内存放不下,因此存储在磁盘中的文件(links.bin)来作为广度优先搜索中的队列。额外的好处,支持断点续爬。
2.2 网页判重文件:bloom_filter.bin
使用布隆过滤器判断网页是否重复爬取,将布隆过滤器定时持久化到硬盘,存储在bloom_filter.bin文件中。
2.3 原始网页存储文件:doc_raw.bin
如果把每个网页都存储为一个独立的文件,那磁盘中的文件就会非常多,数量可能会有几千万,甚至上亿。
可以把多个网页存储在一个文件中。每个网页之间,通过一定的标识进行分隔,方便后续读取。
这样的文件也不用太大,超过一定的阈值,新建一个文件存储。
2.4 网页链接及其编号的对应文件:doc_id.bin
按照网页被爬取的先后顺序,从小到大依次编号。维护一个中心的计数器,在存储网页的同时,我们将网页链接跟编号之间的对应关系,存储在另一个doc_id.bin文件中。
3.分析
3.1 抽取网页文本信息
网页是半结构化数据,按照一定的规则来书写。这个规则就是HTML语法规范。我们依靠HTML标签来抽取网页中的文本信息。这个抽取的过程,大体可以分为两步。
第一步是去掉JavaScript代码、CSS格式以及下拉框中的内容(因为下拉框在用户不操作的情况下,也是看不到的)。也就是, , </option)为止。而这期间遍历到的字符串连带着标签就应该从网页中删除。
第二步是去掉所有HTML标签。这一步也是通过字符串匹配算法来实现的。过程跟第一步类似。
3.2 分词并创建临时索引
- 英文网页,只需要通过空格、标点符号等分隔符,将每个单词分割开来就可以
- 中文网页,基于字典和规则的分词方法。字典也叫词库,里面包含大量常用的词语,构建成Trie树结构,然后拿网页文本在Trie树中匹配。
每个网页的文本信息在分词完成之后,我们都得到一组单词列表。我们把单词与网页之间的对应关系,写入到一个临时索引文件中(tmp_Index.bin),这个临时索引文件用来构建倒排索引文件。临时索引文件的格式如下:
经过分析阶段,我们得到了两个重要的文件。它们分别是临时索引文件(tmp_index.bin)和单词编号文件(term_id.bin)。
4.索引
将分析阶段产生的临时索引,构建成倒排索引。倒排索引( Inverted index)中记录了每个单词以及包含它的网页列表。
临时索引文件很大,无法一次性加载到内存中,搜索引擎一般会选择使用多路归并排序的方法来实现。
除了倒排文件之外,我们还需要一个文件,来记录每个单词编号在倒排索引文件中的偏移位置。我们把这个文件命名为term_offset.bin。这个文件的作用是,帮助我们快速地查找某个单词编号在倒排索引中存储的位置,进而快速地从倒排索引中读取单词编号对应的网页编号列表。
经过索引阶段的处理,我们得到了两个有价值的文件,它们分别是倒排索引文件(index.bin)和记录单词编号在索引文件中的偏移位置的文件(term_offset.bin)。
5.查询
前面三个阶段的处理,只是为了最后的查询做铺垫。因此,现在我们就要利用之前产生的几个文件,来实现最终的用户搜索功能。
- doc_id.bin:记录网页链接和编号之间的对应关系。
- term_id.bin:记录单词和编号之间的对应关系。
- index.bin:倒排索引文件,记录每个单词编号以及对应包含它的网页编号列表。
- term_offsert.bin:记录每个单词编号在倒排索引文件中的偏移位置。
这四个文件中,除了倒排索引文件(index.bin)比较大之外,其他的都比较小。为了方便快速查找数据,我们将其他三个文件都加载到内存中,并且组织成散列表这种数据结构。
当用户在搜索框中,输入某个查询文本的时候,我们先对用户输入的文本进行分词处理。假设分分词之后,我们得到k个单词。
我们拿这k个单词,去term_id.bin对应的散列表中,查找对应的单词编号。经过这个查询之后,我们得到了这k个单词对应的单词编号。
我们拿这k个单词编号,去term_offset.bin对应的散列表中,查找每个单词编号在倒排索引文件中的偏移位置。经过这个查询之后,我们得到了k个偏移位置。
我们拿这k个偏移位置,去倒排索引(index.bin)中,查找k个单词对应的包含它的网页编号列表。经过这一步查询之后,我们得到了k个网页编号列表。
我们针对这k个网页编号列表,统计每个网页编号出现的次数。具体到实现层面,我们可以借助散列表来进行统计。统计得到的结果,我们按照出现次数的多少,从小到大排序。出现次数越多,说明包含越多的用户查询单词(用户输入的搜索文本,经过分词之后的单词)。
经过这一系列查询,我们就得到了一组排好序的网页编号。我们拿着网页编号,去doc_id.bin文件中查找对应的网页链接,分页显示给用户就可以了。