算法实战(二):剖析搜索引擎背后的经典数据结构和算法

算法实战(二):剖析搜索引擎背后的经典数据结构和算法

如何在一台机器上(这台机器的内存是8GB,硬盘是100+GB)通过少量的代码实现一个小型搜索引擎,搜索引擎分为四个部分:搜集、分析、索引、查询。搜集即利用爬虫爬取网页,分析负责网页内容抽取、分词,构建临时索引,计算PageRank值几个工作,索引负责通过分析阶段得到的临时索引,构建倒排索引,查询负责相应用户的请求,根据倒排索引获取相关网页,计算网页排名,返回查询结果给用户

搜集

对于搜索引擎来说,事先不知道网页都在哪里,那么搜索引擎是如何爬取网页的呢?

搜索引擎把整个互联网看做数据结构中的有向图,把每个页面看做一个顶点,如果某个页面中包含另外一个页面的链接,那么就在两个顶点之间连一条有向边,可以利用图的遍历搜索算法来遍历整个互联网的网页,搜索引擎采用的是广度优先搜索策略,就是先找一些比较知名的网页(权重高的网页)的链接(比如新浪主页,腾讯主页等)作为种子网页链接放入到队列中,爬虫根据广度优先的策略,不停从队列中取出链接,然后取爬取对应的网页,解析出网页里包含的其他网页链接,再将解析出来的链接添加到队列中

1 待爬取网页链接文件:links.bin

在广度优先搜索爬取页面的过程中,爬虫不停解析页面链接,将其放到队列中,于是队列中的链接会多到放不下,用一个存储在磁盘中的文件(links.bin)作为广度优先搜索中的队列,爬虫从links.bin文件中,取出链接去爬取对应的页面,等到爬取到网页之后,将解析出来的链接,直接存储到Links.bin文件中

如何解析页面获取链接?把整个页面看作一个大字符串,利用字符串匹配算法,在这个大字符串中,搜索<link>这样一个网页标签,然后顺序读取<link></link>直接的字符串,就是网页链接

2 网页判重文件:bloom_filter.bin

如何避免重复爬取相同的网页?使用布隆过滤器,可以快速节省内存的实现网页的判重,但是机器宕机重启之后,布隆过滤器就被清空了,可能导致大量已经爬取过了的网页会被重复爬取,可以定期的比如每隔半小时将布隆过滤器持久化到磁盘中,存储在bloom_filter.bin文件中,重启之后,重新将读取磁盘中的bloom_filter.bin文件恢复到内存中

3 原始网页存储文件:doc_raw.bin

爬取网页之后,将其存储下来,以备后面的离线分析、索引之用,如何存储海量的原始网页数据呢?

把多个网页存储在一个文件中,每个网页之间通过一定的标识进行分隔,方便读取,具体格式如下

doc1_id \t doc1_size \t dic1 \r\n\r\n

网页编号\ 网页大小 \ 网页 \ 分隔符

这样一个文件不会太大,因为文件系统对文件大小有一定的限制。设置每个文件的大小不能超过一定的值,当超过这个值的时候,会创建一个新的文件来存储新爬取的网页。

扫描二维码关注公众号,回复: 9867764 查看本文章

一个机器的硬盘大小是100GB左右,一个网页平均大小是64KB,在一个机器上,可以存储100W到200W左右的网页,爬取100W的网页最多花费几个小时时间

4 网页链接及其编号的对应文件: doc_id.bin

网页编号实际上是给每个网页分配一个唯一的ID,方便我们对网页进行分析、索引,如何给网页编号呢?

按照网页被爬取的先后顺序,从小到大依次编号,具体的做法是:维护一个中心的计数器,每爬取到一个网页之后,就从计数器中拿一个号码,分配个这个网页,然后计数器加一,在存储网页的同时,将网页链接跟编号之间的对应关系,存储在另一个doc_id.bin文件中

分析

网页爬取下来之后,对网页进行离线分析,第一是抽取网页文本信息,第二是分词并创建临时索引

1 抽取网页文本信息

网页是半结构化数据,里面有各种标签、JavaScript代码、CSS样式。对于搜索引擎来说,只关心能被用户用肉眼看到的那部分信息,如果从网页中,抽取到文本信息呢?

网页之所以是半结构化数据,因为它本身是按照一定的规则来书写的,就是HTML语法规范,依靠标签来抽取文本信息,抽取的过程分为两步:

  • 去掉Javascript代码、CSS样式以及下拉框中的内容(因为下拉框中的内容用户不操作的时候是看不到的)即先去掉<style></style>,<script></script>,<option></option>三个关键词,利用AC自动机这多模式串匹配算法,在网页大字符串中,一次性查找<style></style>,<script></script>,<option></option>三个关键词,这期间遍历到的字符串连带着便签从网页中删除
  • 去掉所有HTML标签,通过字符串匹配算法实现

2 分词并创建临时索引

取出文本信息之后进行分词,并创建临时索引

英文网页分词非常简单, 只需要通过空格、标点符号等分隔符即可,中文网页的话可以基于字典和规则的分词方法

字典即词库,包含大量常用的词语,借助词库并采用最长匹配规则,对文本进行分词,所谓最长匹配规则,即匹配尽可能长的词语

比如要分词的文本是“中国人民解放了”词库中有“中国”“中国人”“中国人民”“中国人民解放军”这几个词,就取最长匹配,就是“中国人民”划分一个词,而不是把“中国”“中国人”划分一个词,将词库中的单词,构建成Trie树结构,然后拿网页文本在Trie树中匹配

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

term1_id \t doc_id \r\n

单词编号 \ 网页编号 \分隔符

在临时索引文件中,存储的是单词编号,即term_id,不是单词,目的是节省存储空间,这编号从何而来?维护一个计算器,每当从网页文本信息中分割出一个新单词的时候,就从计数器中取一个编号分配,然后计数器+1

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

当所有的网页分词之后,将这个单词跟编号之间的对应关系写入磁盘文件中,并命名为term_id.bin

索引

将分析阶段产生的临时索引构建成倒排索引(单词被哪些文档包含),倒排索引记录了每个单词以及包含它的网页列表,倒排索引的结构图为:

tid1 \t did1,did2,did3……,didx \r\n

term_id缩写,单词编号 \ did1,……,didx是包含单词的网页编号列表

构建倒排索引文件的时候,因为索引文件很大无法一次性的加载到内存中,使用多路归并排序实现,先对临时索引文件,按照单词编号的大小进行排序,因为临时索引很大,一般基于内存的排序算法无法处理,用到归并排序的思想,将其分割成多个小文件,先对每个小文件独立排序,最后再合并一起。可以直接用MapReduce处理

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

除了倒排文件之外,还需要一个文件来记录每个单词编号在倒排索引文件中的偏移位置,term_offset.bin,这个文件的作用是帮助我们快速查找某个单词编号在倒排索引中存储的位置,进而快速从倒排索引中读取单词编号对应的网页编号列表

tid1 \t offset1 \r\n

单词编号 偏移位置 分隔符

查询

利用之前几个文件来实现最终的用户搜索功能:

  • doc_id.bin
  • term_id.bin
  • index.bin
  • term_offset.bin

将除了index.bin的其他三个文件都加载到内存中,组织成散列表这种数据结构,当用户在搜索框中,输入某个查询文本的时候,先对用户输入的文本进行分词处理,分词之后得到K个单词,拿这K个单词,去term_id.bin对应的散列表中,查找对应的单词编号,经过这个查询之后,得到这K个单词对应的单词编号,拿着K个单词编号,去term_offset.bin对应的散列表中,查找每个单词编号在倒排索引文件中的偏移位置,得到了K个偏移位置,去倒排索引中,查找K个单词对应的包含它的网页编号列表

针对这K个网页编号列表,统计每个网页编号出现的次数,可以借助散列表来统计,统计得到的结果,按照出现次数的多少,从小到大排序,出现次数越多,说明包含越多的用户查询单词,然后拿网页编号,去doc_id.bin文件中查找对应的网页链接,分页显示给用户即可

总结

计算网页权重的PageRank算法、计算查询结果排名的tf-idf模型

写代码实现一个搜索引擎

怎么实现支持摘要信息和网页快照?

摘要信息:
增加 summary.bin 和 summary_offset.bin。在抽取网页文本信息后,取出前 80-160 个字作为摘要,写入到 summary.bin,并将偏移位置写入到 summary_offset.bin。
summary.bin 格式:
doc_id \t summary_size \t summary \r\n\r\n
summary_offset.bin 格式:
doc_id \t offset \r\n
Google 搜索结果中显示的摘要是搜索词附近的文本。如果要实现这种效果,可以保存全部网页文本,构建搜索结果时,在网页文本中查找搜索词位置,截取搜索词附近文本。

网页快照:
可以把 doc_raw.bin 当作快照,增加 doc_raw_offset.bin 记录 doc_id 在 doc_raw.bin 中的偏移位置。
doc_raw_offset.bin 格式:
doc_id \t offset \r\n

发布了75 篇原创文章 · 获赞 9 · 访问量 9162

猜你喜欢

转载自blog.csdn.net/ywangjiyl/article/details/104893377