如何搭建一个站内搜索引擎(二) 第2章 概述

时间:2022-07-26 08:36:45

        从第1章如何搭建一个站内搜索引擎(一) 第1章 写在最前已经可以简要看出一个站内搜索的雏形。他主要包括2个方向的内容:灌库和搜索。

        在这篇文章中,我们将较为系统的描述整个部分的架构。

1、灌库

        从数据库(如mysql)中执行查询出数据,首要的前提是数据库中必须存在数据。同理,如果想从搜索引擎中查找到数据,那么搜索引擎中必须存在数据。因此,搜索引擎非常核心的一个部分就是灌库--即把要搜索的数据按照一定的格式灌入到索引库中。

        不同类别的搜索引擎灌库方式不同,但是他们的总体流程非常一致。主要有如下图2-1所示的4步,下面一一叙述。

如何搭建一个站内搜索引擎(二) 第2章 概述

图2-1

1.1 抓取数据

        不同类型搜索引擎的区别主要体现与此。

        非站内搜索引擎(用户搜索引擎),如Google/百度,是通过spider去抓取别人网站的html格式数据,并进行存储。在此处,搜索引擎需要考虑很多因素。针对抓取源,抓取算法需要考虑抓取的时间、抓取的周期。如新闻网站,抓取周期比较短,对于热词网站,甚至差不多能达到实时的效果,*部门的静态网页,往往几周甚至几个月抓取一次。

        站内搜索或者和别人合作的垂直搜索则不需要考虑抓取内容。如果是本站内容,我们可以通过数据库直接生产json格式。

1.2 拼装数据

        因为抓取的内容格式千奇百怪,我们自己站内的数据一般存储在mysql等数据库中。而对外合作的数据可能是html,也可能是json,甚至是xml,所以这里需要通过正则匹配(html)、拼装数据库内容等一系列操作取得固定的结构化数据(如json,百度内部可以使用二进制的mcpack)。

1.3 parser解析数据

        parser从1.2中获得数据之后,需要对数据进行解析。在这里面,一份资源,有些字段属于检索query的词语,需要建立倒排,在建立倒排的时候,按照具体需要,有些字段需要切词,有些则不需要切词;而且不同字段的权重理论上也是不一样的(一般是 标题>摘要>正文)。有些字段属于筛选或者加权的字段,需要建立正排字段,比如买书的搜索,可能要塞选评论数大于100的书就需要通过正排来实现。 还有些字段甚至同时需要正排和倒排。当然还有一些字段纯粹是打酱油的,他们不需要建立正排和倒排,只需要放入作为这个资源摘要信息存储在非关系数据库中即可(最好使用内存数据库)。

        最终parser需要将解析处理后的数据发送给索引系统。

1.4 index解析parser数据

        索引系统是非常复杂的,索引都是要存在在内存里面(定期dump到硬盘,所以可以从硬盘启动)。

        针对parser的传来的数据,索引系统也要解析数据。有些数据是删除资源,需要清除内存数据。新加数据则比较简单,直接新增内存数据即可。而修改内存则比较麻烦,一种方法是删除部分内存数据,然后新增部分内存数据,同时修改部分内存数据,另外一种方法是清楚所有内存数据,在新加目前内存数据,好的方法是后者,实现简单,而不需要进行大量复杂操作。

        删除内存数据可以有2种方式,一种是实时删除,另外一种则为标记删除,然后定期内存重整。好的方案也是后者,这样减少内存重整的次数,能大大提高系统性能。


2、搜索(检索)

        在进行灌库之后,用户就可以进行搜索了。这个时候就需要利用检索系统和索引系统。一般而言索引系统工作的部分被称为BS(basic search),检索系统则被称为AS(advance search)。         不同搜索引擎按照实现方式以及具体需求前差万别。但是总体而言无外乎BS + AS 完成整个搜索。下面我将根据我做过的工作给出一个大概流程。
如何搭建一个站内搜索引擎(二) 第2章 概述
图2-2
        从图2-2可以看出整个检索主要分为2个模块:AS模块和BS模块。AS处理数据拼装固定的查询数据发送给BS, BS解析AS查询条件查询并将查询结果返回给AS, AS处理BS数据并拼装结果,这样用户就可以看到搜索的数据了。

2.1 AS模块

        AS为advance search模块。他主要作为用户和BS(基础检索)的桥梁-接受用户传来的搜索请求,处理之后发给BS,解析拼装BS查询的结果并返回给用户。流程如下图。

2.1.1 query 分词

        如果做旅游搜索,当用户搜索一次词语“北京一日游”,我们首先需要进行分词,根据具体需要,分词词典可能有很多种,那么这个词语最终可以分为,北京/一日游/一/日游。当然,这几个词肯定是不一样的。北京属于景点目的地,权重肯定比较高;一这个词语出现太多,无实际意义则应该丢弃;而一日游/日游则需要也并入查询条件。
        从上面例子可以看出,query分词是非常关键的一部分,它的分词直接决定了查询的词语以及各个词语的重要性。

2.1.2 同义词扩展

        在分词的基础上,需要额外做一些工作。如果用户搜索“帝都一日游”,最后切出来的目的地肯定是“帝都”,很明显,帝都和北京是同义词,所以需要针对这个切词结果做同义词扩展,把帝都加一个同义词“北京”。 此外,还有前缀扩展等,这个在具体搜索系统中会有作用,所以在后面详细讲诉检索时会提到。

2.1.3 拼装查询表达式以及查询数据

        剩下的就是拼装BS可以认识的结果。按照具体业务逻辑,帝都一日游,帝都同义词为北京,他们属性为目的地,是必须字段,而一日游/日游为可丢弃字段。那么我们可以拼装一个BS认识的数据结构。比如我们传给BS这样一个数据结构。
typedef struct query_term_t;
{
char query_term[1024];
int weight;
} query_term;

struct as_query_data
{
char query_expr[1024];
query_term * terms;
}

        query_expr为 ($0 | $1) &( ? $2 | ? $3),  $x表示x个term, ?表示可以丢弃,即如果资源没有也符合查询结果,用这个例子的说法,一个资源如果只有北京,没有一日游,应该也被召回。 &表示并, |表示或。 在这个例子里面$0为as_query_data.terms[0],为帝都,其他以此类推。
总体表达含义为 北京或者帝都出现一个就必须得召回,都不出现不召回。如果出现一日游/日游该资源的基础权值应该做加权。 当然,传给BS的数据肯定比这个要多,而且根据不同业务需求可能会多不少字段。

2.1.4  处理BS结果

        我们把查询结果发送给BS,BS会将数据返回给AS。         一个查询可能会调用好几个BS(单一BS拉链太长会超时,所以采用分布式拉链)。所以需要在这进行多个BS结果合并。将BS解析并合并成一个一个数据,然后最终合并成一条拉链。

2.1.5 根据BS结果从非关系型数据库取出摘要信息

        每个资源都有一个资源号rsno, 在上面灌库的时候说到,非关系型数据库中存在该资源的摘要信息,所以需要根据rsno做key获得摘要信息。

2.1.6 拼装结果

        根据具体返回,可能有不同拼装结果。比如传来参数需要返回给json,xml,甚至为html,都需要在这一部分完成。         同时,这部分需要考虑到飘红截断(如果摘要太长,在数据传输的时候是非常耽误时间的)。         最终,将拼装的结果返回给UI端(最终给用户).

2.2 BS 模块

        BS模块是整个系统最为复杂,最为重要,最为核心的模块(没有之一),同时,也是难度最大的一个模块。要想测底理解需要花非常多的篇幅,在后续的章节中,我们慢慢讲到。         
        图2-2简要描述了他的流程。所以我在这里也简要叙述。

2.2.1 解析AS传来的查询表达式以及查询数据

        BS接收到AS的数据之后,第一步也是解析参数,至少要获得查询表达式以及查询的关键词。

2.2.2 归并拉链

        AS传来的查询表达式为易读的中缀表达式,但是机器更加喜欢后缀表达式(逆波兰表达式)。所以需要先把中缀表达式变成后缀表达式(具体怎么变化可以自行百度或者Google,下面有例子)
正常的表达式 逆波兰表达式
a+b ---> a,b,+
a+(b-c) ---> a,b,c,-,+
a+(b-c)*d ---> a,b,c,-,d,*,+
a+d*(b-c)--->a,d,b,c,-,*,+
        这样很容易从索引库中进行归并拉链获得数据。        值得注意的是,如果拉链太长,做后续的操作很容易引起超时。

2.2.3 屏蔽/Brief筛选

        在第1章提到过,再牛逼的检索系统也需要人工干预,都会出现badcase,所以我们需要有一个人工干预屏蔽子系统。如搜索“北京一日游”召回“北京遇上西雅图”这个就是不合理的。“北京遇上西雅图”说的是西雅图,其实和北京相关度不大。        此外,我们有需求搜索,北京一日游需要花费500元以上的资源。那么也需要在这里通过brief进行筛选。
        因此,这一步主要是根据具体业务需求,找出最终需要的结果。

2.2.4 Rank排序

        这一部分是最为复杂的部分。涉及到非常多的排序策略,比如可能按照某一个正排进行排序, 也可能按照基础相关性+正排属性排序,也许需要考虑时效性等加权等。这一部分在后续章节会做重点讲述。

2.2.5 拼装结果返回给AS

        排序之后的结果基本就是需要返回给AS的结果,当然根据具体业务需求,需要进行少量调整。         当然,如果一个query的拉链太长,那么我们没必要把所有的数据都返回给AS,因为用户不需要查看那么多结果,所以只需要做截断即可。         例如百度只显示760条结果(76页),Google貌似是75页。

3、小结

         图2-3是对整个系统的小结。从上文以及图中可以看出我们一共有4个模块。
如何搭建一个站内搜索引擎(二) 第2章 概述
图2-3

3.1 ui模块

        主要是发出灌库或者查询请求,其中灌库可以是实时的,也可以是线下灌库,取决于具体的业务需求,稳定性要求很低(挂了对用户基本无影响)。但是查询则为online操作,要求保证系统的稳定性。

3.2 parser模块

        解析UI传来的灌库请求,并发送给index索引库进行灌库。

3.3 se搜索模块

        解析UI传来的搜索请求,并发给index索引库进行搜索。

3.4 index索引库模块

        灌库时候结束parser数据并进行更新索引库。查询时候根据se查询条件进行查询。