前言

我们每天都在用 Google, 百度这些搜索引擎,那大家有没想过搜索引擎是如何实现的呢,看似简单的搜索其实技术细节非常复杂,说搜索引擎是 IT 皇冠上的明珠也不为过,今天我们来就来简单过一下搜索引擎的原理,看看它是如何工作的,当然搜索引擎博大精深,一篇文章不可能完全介绍完,我们只会介绍它最重要的几个步骤,不过万变不离其宗,搜索引擎都离不开这些重要步骤,剩下的无非是在其上添砖加瓦,所以掌握这些「关键路径」,能很好地达到观一斑而窥全貎的目的。

本文将会从以下几个部分来介绍搜索引擎,会深度剖析搜索引擎的工作原理及其中用到的一些经典数据结构和算法,相信大家看了肯定有收获。

  1. 搜索引擎系统架构图

  2. 搜索引擎工作原理详细剖析

搜索引擎系统架构图

搜索引擎整体架构图如下图所示,大致可以分为搜集预处理索引查询这四步,每一步的技术细节都很多,我们将在下文中详细分析每一步的工作原理。

搜索引擎工作原理详细剖析

一、搜集

爬虫一开始是不知道该从哪里开始爬起的,所以我们可以给它一组优质种子网页的链接,比如新浪主页,腾讯主页等,这些主页比较知名,在 Alexa 排名上也非常靠前,拿到这些优质种子网页后,就对这些网页通过广度优先遍历不断遍历这些网页,爬取网页内容,提取出其中的链接,不断将其将入到待爬取队列,然后爬虫不断地从 url 的待爬取队列里提取出 url 进行爬取,重复以上过程...

当然了,只用一个爬虫是不够的,可以启动多个爬虫并行爬取,这样速度会快很多。

1、待爬取的 url 实现

待爬取 url 我们可以把它放到 Redis 里,保证了高性能,需要注意的是,Redis 要开启持久化功能,这样支持断点续爬,如果 Redis 挂掉了,重启之后由于有持续久功能,可以从上一个待爬的 url 开始重新爬。

2、如何判重

如何避免网页的重复爬取呢,我们需要对 url 进行去重操作,去重怎么实现?可能有人说用散列表,将每个待抓取 url 存在散列表里,每次要加入待爬取 url 时都通过这个散列表来判断一下是否爬取过了,这样做确实没有问题,但我们需要注意到的是这样需要会出巨大的空间代价,有多大,我们简单算一下,假设有 10 亿 url (不要觉得 10 亿很大,像 Google, 百度这样的搜索引擎,它们要爬取的网页量级比 10 亿大得多),放在散列表里,需要多大存储空间呢?

我们假设每个网页 url 平均长度 64 字节,则 10 亿个 url 大约需要 60 G 内存,如果用散列表实现的话,由于散列表为了避免过多的冲突,需要较小的装载因子(假设哈希表要装载 10 个元素,实际可能要分配 20 个元素的空间,以避免哈希冲突),同时不管是用链式存储还是用红黑树来处理冲突,都要存储指针,各种这些加起来所需内存可能会超过 100 G,再加上冲突时需要在链表中比较字符串,性能上也是一个损耗,当然 100 G 对大型搜索引擎来说不是什么大问题,但其实还有一种方案可以实现远小于 100 G 的内存:布隆过滤器。

针对 10 亿个 url,我们分配 100 亿个 bit,大约 1.2 G, 相比 100 G 内存,提升了近百倍!可见技术方案的合理选择能很好地达到降本增效的效果。

当然有人可能会提出疑问,布隆过滤器可能会存在误判的情况,即某个值经过布隆过滤器判断不存在,那这个值肯定不存在,但如果经布隆过滤器判断存在,那这个值不一定存在,针对这种情况我们可以通过调整布隆过滤器的哈希函数或其底层的位图大小来尽可能地降低误判的概率,但如果误判还是发生了呢,此时针对这种 url 就不爬好了,毕竟互联网上这么多网页,少爬几个也无妨。

3、网页的存储文件: doc_raw.bin

爬完网页,网页该如何存储呢,有人说一个网页存一个文件不就行了,如果是这样,10 亿个网页就要存 10 亿个文件,一般的文件系统是不支持的,所以一般是把网页内容存储在一个文件(假设为 doc_raw.bin)中,如下

当然一般的文件系统对单个文件的大小也是有限制的,比如 1 G,那在文件超过 1 G 后再新建一个好了。

图中网页 id 是怎么生成的,显然一个 url 对应一个网页 id,所以我们可以增加一个发号器,每爬取完一个网页,发号器给它分配一个 id,将网页 id 与 url 存储在一个文件里,假设命名为 doc_id.bin,如下

二、预处理

爬取完一个网页后我们需要对其进行预处理,我们拿到的是网页的 html 代码,需要把 <script>,<style>,<option> 这些无用的标签及标签包含的内容给去掉,怎么查找是个学问,可能有人会说用 BF ,KMP 等算法,这些算法确实可以,不过这些算法属于单模式串匹配算法,查询单个字段串效率确实不错,但我们想要一次性查出<script>,<style>,<option>这些字段串,有啥好的方法不,答案是用AC 自动机多模式串匹配算法,可以高效一次性找出几个待查找的字段串,有多高效,时间复杂度接近 0(n)!关于 AC 自动机多模式匹配算法的原理不展开介绍,大家可以去网上搜搜看, 这里只是给大家介绍一下思路。

找到这些标签的起始位置后,剩下的就简单了,接下来对每个这些标签都查找其截止标签 </script>,</style>,</option>,找到之后,把起始终止标签及其中的内容全部去掉即可。

做完以上步骤后,我们也要把其它的 html 标签去掉(标签里的内容保留),因为我们最终要处理的是纯内容(内容里面包含用户要搜索的关键词)

三、分词并创建倒排索引

拿到上述步骤处理过的内容后,我们需要将这些内容进行分词,啥叫分词呢,就是将一段文本切分成一个个的词。比如 「I am a chinese」分词后,就有 「I」,「am」,「a」,「chinese」这四个词,从中也可以看到,英文分词相对比较简单,每个单词基本是用空格隔开的,只要以空格为分隔符切割字符串基本可达到分词效果,但是中文不一样,词与词之类没有空格等字符串分割,比较难以分割。以「我来到北京清华大学」为例,不同的模式产生的分词结果不一样,以 github 上有名的 jieba 分词开源库为例,它有如下几种分词模式

【全模式】: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学
【精确模式】: 我/ 来到/ 北京/ 清华大学
【新词识别】:他, 来到, 了, 网易, 杭研, 大厦
【搜索引擎模式】: 小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, 后, 在, 日本, 京都, 大学, 日本京都大学, 深造

分词一般是根据现成的词库来进行匹配,比如词库中有「中国」这个词,用处理过的网页文本进行匹配即可。当然在分词之前我们要把一些无意义的停止词如「的」,「地」,「得」先给去掉。

经过分词之后我们得到了每个分词与其文本的关系,如下

细心的你一定发现了,不同的网页内容有可能出现同样的分词,所以我们把具有相同分词的网页归在一起,如下所示

这样我们在搜「大学」的时候找到「大学」对应的行,就能找到所有包含有「大学」的文档 id 了。

看到以上「分词」+「倒排索引」的处理流程,大家想到了什么?没错,这不就是 ElasticSearch 搜索引擎干的事吗,也是 ES 能达到毫秒级响应的关键!

这里还有一个问题,根据某个词语获取得了一组网页的 id 之后,在结果展示上,哪些网页应该排在最前面呢,为啥我们在 Google 上搜索一般在第一页的前几条就能找到我们想要的答案。这就涉及到搜索引擎涉及到的另一个重要的算法: PageRank,它是 Google 对网页排名进行排名的一种算法,它以网页之间的超链接个数和质量作为主要因素粗略地分析网页重要性以便对其进行打分。我们一般在搜问题的时候,前面一两个基本上都是 stackoverflow 网页,说明 Google 认为这个网页的权重很高,因为这个网页被全世界几乎所有的程序员使用着,也就是说有无数个网页指向此网站的链接,根据 PageRank 算法,自然此网站权重就啦,恩,可以简单地这么认为,实际上 PageRank 的计算需要用到大量的数学知识,毕竟此算法是 Google 的立身之本,大家如果有兴趣,可以去网上多多了解一下。

完成以上步骤,搜索引擎对网页的处理就完了,那么用户输入关键词搜索引擎又是怎么给我们展示出结果的呢。

四、查询

用户输入关键词后,首先肯定是要经过分词器的处理。比如我输入「中国人民」,假设分词器分将其分为「中国」,「人民」两个词,接下来就用这个两词去倒排索引里查相应的文档

得到网页 id 后,我们分别去 doc_id.bin,doc_raw.bin 里提取出网页的链接和内容,按权重从大到小排列即可。

这里的权重除了和上文说的 PageRank 算法有关外,还与另外一个「 TF-IDF 」(https://zh.wikipedia.org/wiki/Tf-idf)算法有关,大家可以去了解一下。

另外相信大家在搜索框输入搜索词的时候,都会注意到底下会出现一串搜索提示词,

如图示:输入 chin 这四个字母后,底下会出现一列提示词。

如何实现的,这就不得不提到一种树形结构:Trie 树。Trie 树又叫字典树、前缀树(Prefix Tree)、单词查找树,是一种多叉树结构,如下图所示:

这颗多叉树表示了关键字集合 ["to","tea","ted","ten","a","i","in", "inn"]。从中可以看出 Trie 树具有以下性质:

  1. 根节点不包含字符,除根节点外的每一个子节点都包含一个字符

  2. 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串

  3. 每个节点的所有子节点包含的字符互不相同

通常在实现的时候,会在节点结构中设置一个标志,用来标记该结点处是否构成一个单词(关键字)。

另外我们不难发现一个规律,具有公共前缀的关键字(单词),它们前缀部分在 Trie 树中是相同的,这也是 Trie 树被称为前缀树的原因,有了这个思路,我们不难设计出上文所述搜索时展示一串搜索提示词的思路:

一般搜索引擎会维护一个词库,假设这个词库由所有搜索次数大于某个阈值(如 1000)的字符串组成,我们就可以用这个词库构建一颗 Trie 树,这样当用户输入字母的时候,就可以以这个字母作为前缀去 Trie 树中查找,以上文中提到的 Trie 树为例,则我们输入「te」时,由于以「te」为前缀的单词有 ["tea","ted","ted","ten"],则在搜索引擎的搜索提示框中就可以展示这几个字符串以供用户选择。

五、寻找热门搜索字符串

Trie 树除了作为前缀树来实现搜索提示词的功能外,还可以用来辅助寻找热门搜索字符串,只要对 Trie 树稍加改造即可。假设我们要寻找最热门的 10 个搜索字符串,则具体实现思路如下:

一般搜索引擎都会有专门的日志来记录用户的搜索词,我们用用户的这些搜索词来构建一颗  Trie 树,但要稍微对 Trie 树进行一下改造,上文提到,Trie 树实现的时候,可以在节点中设置一个标志,用来标记该结点处是否构成一个单词,也可以把这个标志改成以节点为终止字符的搜索字符串个数,每个搜索字符串在 Trie 树遍历,在遍历的最后一个结点上把字符串个数加 1,即可统计出每个字符串被搜索了多少次(根节点到结点经过的路径即为搜索字符串),然后我们再维护一个有 10 个节点的小顶堆(堆顶元素比所有其他元素值都小,如下图示)

如图示:小顶堆中堆顶元素比其他任何元素都小

依次遍历 Trie 树的节点,将节点(字符串+次数)传给小顶堆,根据搜索次数不断调整小顶堆,这样遍历完 Trie 树的节点后,小顶堆里的 10 个节点即是最热门的搜索字符串。

总结

本文简述了搜索引擎的工作原理,相信大家看完后对其工作原理应该有了比较清醒的认识,我们可以看到,搜索引擎中用到了很多经典的数据结构和算法,所以现在大家应该能明白为啥 Google, 百度这些公司对候选人的算法要求这么高了。

本文只是介绍了搜索引擎的基本工作原理,要深入了解还需多查资料了解哦。

扫描下方二维码

添加好友,备注【交流群

拉你到学习路线和资源丰富的交流群

搜索引擎的查找算法实现相关推荐

  1. 数据结构-常用的查找算法

    总第124篇/张俊红 本篇讲讲数据结构里面常用的几个查找算法,数据结构理论篇系列差不多接近尾声了,接下来会分享一些比较特殊的概念,比如KMP.郝夫曼树等等,讲完概念以后会进入刷题阶段.刷题会用Pyth ...

  2. 经典查找算法及其Python实现

    上一篇介绍了几大排序算法,从基本原理解释到Python代码实现,平时有空的话还需要经常翻出来复习复习.今天就主要来看看另外一大类算法:经典查找算法. 参考资料: <大话数据结构>.< ...

  3. 【精】沐浴智慧之光:研究查找算法

    首先保证这一篇分析查找算法的文章,气质与大部分搜索引擎搜索到的文章不同,主要体现在代码上面,会更加高级,会结合到很多之前研究过的内容,例如设计模式,泛型等.这也与我的上一篇面向程序员编程--精研排序算 ...

  4. 大话数据结构读书笔记艾提拉总结 查找算法 和排序算法比较好 第1章数据结构绪论 1 第2章算法 17 第3章线性表 41 第4章栈与队列 87 第5章串 123 第6章树 149 第7章图 21

    大话数据结构读书笔记艾提拉总结 查找算法 和排序算法比较好 第1章数据结构绪论 1 第2章算法 17 第3章线性表 41 第4章栈与队列 87 第5章串 123 第6章树 149 第7章图 211 第 ...

  5. 数据结构——四大查找算法(工作必备)

    在java中,常用的查找有四种 顺序(线性)查找 二分查找折半查找 插值查找 斐波那契查找 目录 1. 线性查找 2. 二分查找 3. 插值查找 4. 斐波那契查找 1. 线性查找 线性查找是逐一比对 ...

  6. java快速查找算法_Java实现的快速查找算法示例

    本文实例讲述了Java实现的快速查找算法.分享给大家供大家参考,具体如下: 快速查找算法,可以根据想要找的是第几个大的数,每次循环都能固定下来一个数在数组完整排完序之后的位置,每次循环都能定一个数的位 ...

  7. java 二分搜索获得大于目标数的第一位_程序员常用查找算法(顺序、二分、插值、分块、斐波那契)...

    顺序查找 基本思想 属于线性查找和无序查找,从一端开始顺序扫描,直到找到与目标值value相等的元素. 这是最基本的查找方法,也是时间复杂度最高的查找算法. 在数据过多时,这种方法并不适用. 代码实现 ...

  8. 二分查找算法的一点改进

    在计算机科学中,二分查找,是一种在有序数组中查找某一特定元素的搜索算法.这种搜索算法每一次比较都使搜索范围减半.第一篇二分查找的论文发表于1946年,然而第一个没有bug的二分查找算法却是在1962年 ...

  9. python二分法查找算法_顺序查找算法和折半(二分法)查找算法,C语言查找算法详解...

    查找是指在大量的信息中寻找一个特定的信息.在计算机中,查找是非常重要的一个应用,比如"百度".查找算法的好坏直接影响查找的速度. 常用的查找算法主要有顺序查找和折半(二分法)查找: ...

  10. php查找二维数组下标,PHP实现二维数组中的查找算法小结

    本文实例讲述了PHP实现二维数组中的查找算法.分享给大家供大家参考,具体如下: 方法1:silu从左下角最后一行的第一个元素开始,遍历.如果小于target 则遍历该行的所有元素,找到结束.如果大于继 ...

最新文章

  1. 【硬核书】矩阵代数基础
  2. RHCT 模拟题库及答案
  3. python【Multiprocessing and threading】深入浅出多进程和多线程
  4. 学python能赚什么外卖-python爬取外卖
  5. 排序---快速排序及其切分函数Partition应用
  6. SqlServer 根据一个表数据更新另外一个表
  7. mysql 1067 42000_MySQL:ERROR 1067 (42000): Invalid default value for 'end_time'
  8. 计算机专业科普知识图文并茂,科普下计算机专业需要什么基础知识?
  9. 基于智能的网络空间安全防御
  10. 前端学习(2462):打包优化
  11. 关于使用Transaction对于非数据库事务的操作
  12. __invoke,try{}catch(){},microtime(),is_callable()
  13. Linux学习笔记-消息队列的打开、创建、控制
  14. C语言图书借阅管理系统
  15. android 盒子 关闭电视,电视盒子正确使用方法,速速来取!
  16. python pandas read_excel 参数详解 to_excel 读写Excel
  17. 利用Host-only模式使用虚拟机静态IP上网
  18. 计算机考证打字训练题
  19. Flutter编程之BoxDecoration用法详解
  20. 【修真院PM小课】什么是消息推送?

热门文章

  1. android手机浏览器测评,九款手机浏览器评测总结
  2. dell机架式服务器型号,戴尔R910机架式服务器
  3. vue 的ui组件库
  4. linux使用磁盘配额,linux磁盘配额使用
  5. Retrofit(Okhttp)Dns解析服务器域名异常时尝试使用服务器IP访问
  6. 通过雷达图的面积进行总体对比分析是不可靠的!
  7. html指南针绘制,Fireworks绘制指南针详解
  8. Ps 初学者教程「68」如何创建海报?
  9. 组态王通过Modbus TCP与ESP32通讯
  10. VM12虚拟机安装xp系统教程