搜索引擎大致可以分为四个部分:搜集、分析、索引、查询。
搜集:利用爬虫爬取网页;
分析:网页内容抽取、分词,构建临时索引,计算PageRank值;
索引:通过分析阶段得到的临时索引,构建倒排索引;
查询:响应用户的请求,根据倒排索引获取相关网页,计算网页排名,返回查询结果给用户。
搜集
对搜索引擎来说,它事先并不知道网页都在哪里。搜索引擎把整个互联网看做数据结构中的有向图,把每个页面看做一个顶点。如果某个页面中包含另外一个页面的链接,就在两个顶点之间连一条有向边。可以利用图的遍历搜索算法,来遍历整个互联网中的网页。
搜索引擎采用的是广度优先搜索策略。先找一些比较知名的网页(权重比较高)的链接,作为种子网页链接,放入到队列中。爬虫按照广度优先的策略,不停的从队列中取出链接,然后去爬取对应的网页,解析出网页里包含的其他网页链接,再将解析出来的链接添加到队列中。
待爬取网页链接文件:links.bin
在广度优先搜索爬取页面的过程中,爬虫会不停的解析页面链接,将其放到队列中。队列中的链接会越来越多,可能会多到内存放不下。用一个存储在磁盘中的文件-links.bin来作为广度优先搜索中的队列。爬虫从links.bin文件中,取出链接去爬取对应的页面。等爬取到网页之后,将解析出来的链接,直接存储到links.bin文件中。用文件存储网页链接的方式,支持断点续爬。当机器断点之后,网页链接不会丢失;当机器重启之后,还可以从之前爬取到的位置继续爬取。
把整个页面看做一个大的字符串,然后利用字符串匹配算法,在这个大字符串中,搜索这样一个网页标签,然后顺序读取之间的字符串,就是网页链接。
网页判重文件:bloom_filter.bin
采用位图数据结构,使用布隆过滤器快速并且非常节省内存的实现网页的判重。解决机器宕机重启后,存储在内存中的布隆过滤器被清空的问题(导致已经爬取的网易会被重复爬取)。定期(每个半个小时)将布隆过滤器持久化到磁盘中,存储在bloom_filter.bin文件中。即便出现宕机,也只会丢失布隆过滤器中的部分数据,当机器重启后,可以重新读取磁盘中的bloom_filter.bin文件,将其恢复到内容中。
原始网页存储文件:doc_raw.bin
爬取到网页之后,需要将其存储下来,以备后面离线分析、索引之用。把多个网页存储在一个文件中(避免一个网页对应一个独立文件造成大量文件。同时一个文件不能太大,每个文件大小超过一定的值就创建新的文件)。每个网页之间,通过一定的表示进行分隔,方便后续读取。(网页编号 \t 网页大小 \t 网页 \r\n\r\n)
网页链接及其编号的对应文件:doc_id.bin
网页编号就是给每个网页分配一个唯一的ID,方便后续对网页进行分析、索引。可以按照网页被爬取的先后顺序,从小到大依次编号。维护一个中心的计数器,每爬取到一个网页之后,就从计数器中拿一个号码,分配给这个网页,然后计数器加一。在存储网页的同时,将网页链接跟编号之间的对应关系,存储在另一个doc_id.bin文件中。
搜集阶段得到四个文件:links.bin和bloom_filter.bin是爬虫自身所用。
doc_raw.bin和doc_id.bin作为搜集阶段的成果,供后面的分析、索引、查询使用。
分析
网页爬取下来之后,需要对网页进行离线分析。分析阶段主要包含两个步骤:抽取网页文本信息、分词并创建临时索引。
抽取网页文本信息
网页是半结构化数据,里面夹杂着各种标签、JavaScript代码、CSS样式。对于搜索引擎来说,它只关心网页中的文本信息,也就是网页显示在浏览器中时,能被用户肉眼看到的那部分信息。去掉JavaScript代码、CSS格式以及下拉框中的内容。(script、style、option标签之间的内容,利用AC自动机多模式串匹配算法处理,)
去掉所有HTML标签。
分词并创建临时索引
对于英文网页,只需要通过空格、标点符号等分隔符,将每个电磁分割开来就可以。对于中文来说,采用基于字典和规则的分词方法。
字典也叫词库,里面包含大量常用的词语,借助词库并采用最长匹配原则(匹配尽可能长的词语),来对文本进行分词。
将词库中的单词,构建成Trie树结构,然后拿网页文本在Trie树中匹配。每个网页的文本信息在分词完成之后,得到一组单词列表。把单词与网页之间的对应关系,写入到一盒临时索引文件中tmp_index.bin,用来构建倒排索引。(单词编号 \t 网页编号 \r\n)
在临时索引文件中,存储的是单词编号term_id,而非单词本身,主要是为了节省存储空间。
给单词编号的方式,跟给网页编号类似。维护一个计数器,每当网页文本信息中分割出一个新的单词的时候,就从计数器中取一个编号,分配给他,然后计数器加一。
在这个过程中使用散列表,记录已经变过号的单词。在对网页文本信息分词的过程中,拿分割出来的单词,先到散列表中查找,如果找到就直接使用已有的编号;如果没有找到,再去计数器中拿号码,并且将这个新单词以及编号添加到散列表中。当所有的网页处理完成之后,再将单词跟编号之间的对应关系,吸入到磁盘文件中,命名为term_id.bin。
分析阶段得到两个文件:临时索引文件tmp_index.bin和单词编号文件term_id.bin。
索引
索引阶段主要负责将分析阶段产生的临时索引,构建成倒排索引—Inverted index。倒排索引中记录了每个单词以及包含它的网页列表。(term_id缩写,单词编号 \t did1,…didx表示包含单词的网页编号列表 \r\n)
正排-文档中包含哪些单词
倒排-单词被哪些文档包含
采用多路归并排序的方法,通过临时索引文件,构建出倒排索引文件。
先对临时索引文件,按照单词编号的大小进行排序。采用归并排序的处理思想,将其分割成多个小文件,先对每个小文件独立排序,最后再合并在一起。(可直接用MapReduce来处理)
临时索引文件排序完成之后,相同的单词被排列到了一起。只需要顺序地遍历遍历排好序的临时索引文件,就能将每个单词对应的网页编号列表找出来,然后把他们存储在倒排索引文件中。
除了倒排文件外,需要一个记录每个单词编号在倒排索引文件中的偏移位置的文件—term_offset.bin。这个文件可以快速的查找某个单词编号在倒排索引中存储的位置,进而快速的从倒排索引中读取单词编号对应的网页编号列表。(单词编号 \t 偏移位置 \r\n)
索引阶段得到两个文件:倒排索引文件index.bin和记录单词编号在索引文件中的偏移位置的文件term_offset.bin。
查询
利用之前产生的几个文件,实现最终的用户搜索功能。
- doc_id.bin:记录网页链接和编号之间的对应关系。
- term_id.bin:记录单词和编号之间的对应关系。
- index.bin:倒排索引文件,记录每个单词编号以及对应包含它的网页编号列表。
- term_offset.bin:记录每个单词编号在倒排索引文件中的偏移位置。
除了倒排索引文件比较大之外其他都比较小。将其他三个文件都加载到内存中,并组织成散列表这种数据结构。
当用户在搜索框中,输入某个查询文本的时候,先对用户输入的文本进行分词处理。假设分词之后,得到k个单词。
拿这k个单词,去term_id.bin对应的散列表中,查找对应的单词编号,得到这k个单词对应的单词编号。
拿这k个单词编号,去term_offset.bin对应的散列表中,查找每个单词编号在倒排索引文件中的偏移位置,得到k个偏移位置。
拿这k个偏移位置,去倒排索引index.bin中,查找k个单词对应的包含它的网页编号列表,得到k个网页编号列表。
针对k个网页编号列表,统计每个网页编号出现的次数。对统计结果,按照出现的次数的多少,从小到大排序。出现次数越多,说明包含越多的用户查询单词。
经过这一系列查询,得到一组排好序的网页编号,拿着网页编号,去doc_id.bin文件中查找对应的网页链接,分页显示给用户。
扩展
计算网页权重PageRank算法
计算查询结果排名th-idf模型
搜索结果展示摘要信息和网页快照