Skip to content

浅谈搜索引擎原理

前言

两年前笔者做过一个小型搜索引擎,虽然代码现在看来很烂,但毕竟总归实现了一个非常基础的搜索引擎,最近又需要用到相关技术,所以又回顾了一下搜索引擎的知识。但是,由于之前也没有任何的博客或者笔记记录当时的过程,所以现在回顾起来比较耗时。

所以这也是笔者为什么要写博客的原因之一,这篇文章就来梳理一下一个搜索引擎,如大家常见的Baidu、Google,的基本结构是怎么样的。

值得注意的是,本文不会详细叙述某一个技术细节,在“具体”和“抽象”之中本文更加偏抽象一点,所以,就算读者没用过Python或者并不做这方面的工作,也应该能看懂并可能有些许作用,算是扩展一下自己的知识面吧,毕竟搜索引擎太常见了,上网必备的。

基本架构

如上就是一个搜索引擎最基本的结构了。当然,现在的搜索引擎越来越人性化,结构肯定更为复杂,比如使用AI去理解用户的输入,对用户进行画像等等,同样输入“苹果”,关注科技的用户的搜索排序结果与关注农产品的用户搜索排序结果显然会有所差异。

这里仅以上述基本结构做介绍,否则可以写本小说了。就算仅介绍上述基本结构,其中的技术细节也非常多,随便单独拿出来一个技术细节都可以写一篇文章了,所以还在再啰嗦一下本文的定位:扩展大家的知识面,带大家了解认识搜索引擎的内部。

本小节将简单介绍这个基本结构,后续小节会理一些重要/经典的技术作为扩展。好了,废话不多说了,进入正题:

首先,我们可以将上述结构分解为两个部分:

  1. 离线系统:图中的上半部分(数据收集、清洗、整理、索引模块),这些部分不需要实时运行
  2. 在线系统:图中的下半部分(搜索接口、检索、排序和摘要计算模块),这些需要快速响应,必须在线运行

搜索引擎的主要数据来源于网页,最后也以网页的形式作为输出。其中数据部分为搜索引擎的内部表示,比如倒排索引知识图谱之类。

然后,笔者再将上上图中过程做一个描述,方便大家理解:

  1. 爬虫采取某种策略爬取互联网上的网页,获取到网页库
  2. 采用某种方式如正则、网页标签结构分析等方式拿到网页中有用的内容(标题、描述、内容...),生成网页对象库
  3. 对该网页对象库进行索引,为了后续搜索速度,采用倒排索引(原因后续详细介绍),既然叫索引,那就是空间换时间,提高速度用的,现在理解这点就好
  4. 等待一段时间,数据较为丰富的时候,此时在线系统就可以开始接受用户交互了
  5. 用户通过前端搜索接口输入搜索词
  6. 搜索引擎通过该搜索词与索引库进行查找比对,找出相关性最高的一些网页
  7. 排序(不同权重计算,如相关性、时效性、用户画像、网页是否花钱【doge】等)
  8. 对网页内容进行摘要处理,把与搜索词最相关的内容放在摘要部分
  9. 返回展示即可

整体过程相信读者们已经了解,接下来将对其中重要的技术进行理解,如爬虫的策略、为什么要使用倒排索引、如何做到相关性比对之类的。请读者移步下一小节:

爬虫策略

本小节你需要有一定的数据结构-图的知识,深度优先算法与广度优先算法的基础

在讲策略之前,你首先需要知道爬虫的基本任务是什么,简单来说就两步:

  1. 将给定的种子站点的URL所代表的网页内容下载下来
  2. 提取该网页中包含的全部URL,重复第一个步骤

如此,种子网站的选用就非常重要了,通常是一些权威级别的导航网站;然后就是如何指定爬取的策略了,比如获取到网页中的全部URL,是对其中一个URL进行深度爬取还是将该网页包含的这些URL进行广度爬取呢等等

通常来说,爬取策略选择广度优先爬取,原因如下:

  1. 首先,重要的网页往往离种子站点的距离较近,这符合直觉。我们通常在打开某些新闻网站时,进入眼帘的往往是最重要的新闻。随着不断地冲浪(可以理解为深度不断加深),所看到的网页的重要性越来越低,甚至偶尔会出现无法访问的情况。
  2. 其次,WWW 的深度只有19,没有我们想象的那么深,到达某一个网页的路径通常很多,总会存在一条很短的路径到达。
  3. 最后,广度优先策略有利于多爬虫合作抓取。这是因为该策略开始抓取的网页通常都是站内网页,逐渐才会遇到站外链接,因此抓取的封闭性较强。

除此之外,你还应该考虑如下技术细节:

  1. 提取出来的URL是绝对路径还是相对路径,相对路径的话就应该进行拼接
  2. 通过某种方式(如哈希-布隆过滤器)保存已经抓取过的URL,避免重复抓取
  3. 哪些网页具有优先权,比如反链的数量与质量,域名的后缀(com、cn、org),距离种子网站的深度等等指标都可以决定网页的优先权,从而在有限的资源里,抓取更重要的网页内容。
  4. 网页变化了怎么办,如何实现重访策略,是以同样的频率重访已经抓取过的全部网页,还是根据不同网页的更新频率决定重访该个体页面的频率
  5. 爬虫自身的技术问题:爬虫与反爬虫,如何提速
  6. 友好性工作,遵守ROBOT协议
  7. 非列表页数据的获取,比如京东淘宝的数据很多都藏在搜索框中
  8. 略略略...

网页结构分析

提取出结构化内容

本小节主要为对于已经下载的网页(一堆HTML文本文件),如何提取出其中有用的信息。

网页对象中常见的信息包括但不限于:

  1. 网页使用的字符编码(charset)
  2. 网页使用的语言(language)
  3. 网页的发布时间(time)
  4. 网页的关键词信息(keywords)
  5. 网页的标题(title)
  6. 正文标题(content title)
  7. 正文(content)
  8. 正向链接(link)
  9. 锚文本(anchor text)

如何提取其中的信息就极为重要,对于特定的网页,特定的网站,我们也许可以找到提取规律,使用正则表达式直接拿到想要的数据,但是对于互联网上成千上万的数据,搜索引擎就得找到一个统一的策略方法去提取HTML中的有用内容。

这也是为什么前端需要尽量使用语义化标签的原因,除了给人看,更多是给搜索引擎这类机器看。

但是,很多前端工程师都是div标签一个走天下,根本不会遵守规范,并且内容部分也不会仅仅包含在一个标签中,所以这时候搜索引擎就需要提取其中的class类名、标签名,比如class="content" <p></p>多半就是内容标签,由此可以指定投票机制,包含哪些标签的加分,最后拿到最优解。

网页去重

刚才在爬虫策略中也提到了避免重复爬取,这里也是避免重复,两者有什么区别呢?

前者是URL重复,后者是网页内容重复或高度相似,大家都懂的==>COPY、COPY、COPY导致的,即使不同的URL,网页内容也可能是重复的,所以这时候就需要进行第二次去重,去重也涉及到两个步骤:

  1. 如何判断网页是重复的或者高度相似的
  2. 多个重复的到底该保留哪一个URL对应的网页内容,比如从版权的角度考虑要尊重原创,如何判断原创呢?

1)网页查重

为了避免本文陷入算法的细节陷阱中,这里仅简单列出几种常见的文档查重算法思想供了解,需要深入自行搜索关键词即可(后续也可能单独写一篇文章来详细介绍):

  1. 文本分块签名算法:采用分块的方法,利用hash函数计算每一块的哈希值,如果相同块的数量大于某个阈值,则认为是相似的;难点在于确定分块数量和阈值
  2. Shingle
  3. I-Match
  4. SimHash

2)网页去重

网页查重的目标是消重,消重不可避免地会遇到这样一个问题,即在相同或者相似的网页集合中保留哪一个,而消除哪一个些呢?比如某网页A被复制为B,复制为C,复制为D

  • 从版权的角度考虑,应该尊重原创,过滤转载者复制的网页,即保留网页A;
  • 从网页寿命的角度考虑,过滤掉那些网站质量不高的网页,保留大型网站的网页;
  • 从容易实现的角度考虑,保留首先被爬虫抓取的网页,丢弃后续抓取的相同或相似网页。

最后一种方法最为简单实用,由于保留了先被爬虫抓取的网页,同时很大程度上也保证了优先保留原创网页的原则,因此被广泛采用。

PageRank

PageRank可以说是历史上颠覆性的算法了,非常经典,它是判断网页质量的重要指标。

PageRank的计算基于以下两个基本假设:

  1. 一个网页被多个网页指向,则它是重要网页,又称为权威(Authority)网页;一个网页虽然没有被多个网页指向,但是它被某个重要网页指向,则它也是重要网页;一个网页的重要性被平均的传递到它所指向的网页;
  2. 假定用户一开始随机的访问网页集合中的一个网页,以后跟随网页的链接浏览网页,不回退浏览,浏览某个网页的概率就是该网页的PageRank值。

认识一下网页链接,很明显是一个有向图数据结构:

Pagerank的计算公式如下: $$PR(A) = (1-d) + d (PR(T_1)/C(T_1) + ... + PR(T_n)/C(T_n))$$

其中:

  • PR(A)表示网页A的PageRank值;
  • d是一个介于0和1之间的阻尼因子,表示用户点击链接继续浏览的概率;
  • PR(Ti)表示指向网页A的其他网页Ti的PageRank值;
  • C(Ti)表示网页Ti的出链数量。

简单来说,每个网页的PageRank值由指向它的其他网页的PageRank值决定,并且与这些链接的数量有关。

在计算过程中,会通过迭代的方法不断更新每个网页的PageRank值,直到达到收敛。这个过程可以理解为网页之间相互传递PageRank值的过程,最终得到每个网页的最终PageRank值。

需要注意的是,PageRank计算公式中的阻尼因子d是用来避免概率泄漏的问题,它表示用户点击链接继续浏览的概率。一般来说,阻尼因子的值取0.85。

倒排索引

在此之前你应该有自然语言处理的基础--分词(把一段话分成多个词)

这里考虑的就是如何存储该结构化内容以达到用户检索时可以迅速查找,快速响应的效果,所以这里使用了倒排索引的技术,既然有倒排索引,那必然就有正排索引,了解它俩的区别你就自然知道了为什么要使用倒排索引了。

正排索引就是我们常见的思维:以文档为主键

此时,用户如果搜索关键词“苹果”,按照这种存储方式,搜索引擎就只能通过文档ID去一行一行遍历,查找该文档是否包含“苹果”这个关键词,显然效率是非常低的。

倒排索引就是以关键词为主键:

此时,用户输入关键词,就可以很快找到对应关键词那一行,从而找到哪些文档包含了该关键词,再对这些文档进行下一步的操作...

这里简单介绍了为什么要使用倒排索引,但倒排索引的建立过程也是较为复杂且值得研究的部分,继续挖坑,后续可能单开文章介绍。

搜索模块

上述几个章节都是离线模块的介绍,本章节就是在线系统的讲解了,从用户输入搜索词开始,到返回对应的网页列表结束,如下是一个完整的过程图:

简单描述一下:

  1. 用户输入搜索词
  2. 对搜索词进行处理,比如去掉停用词,“北京的故宫”==> “北京”,“故宫”
  3. 从索引库中找到与这些关键词相关的文档
  4. 检索词表示与文档表示做相似度计算(布尔模型、向量空间模型等等)
  5. 取出前K个文档
  6. 通过一系列手段进行排序
  7. 摘要,可以是离线摘要,就是提前对网页做出摘要处理
  8. 生成结果

当然,肯定是有缓存技术的,不过这对于程序员来说太常见了,这里就不单独介绍了,但毋庸置疑其是非常重要的。

当然,一切都是为了用户体验,搜索接口中如下操作也是极为影响用户体验的:

  • 搜索智能能提示
  • 文本纠错
  • 搜索词高亮显示(需要记录关键词在文档中的偏移位置)
  • 略...

最后

搜索引擎的内容非常之多,本文斗胆谈了谈搜索引擎,对其基本架构及较为关键的技术进行了简单介绍,希望对你有所帮助,扩展一下大家的知识面。