目录
boost库 搜索引擎
项目背景
引入
市面上已经有很多公司设计出很多的搜索引擎
- 百度,搜狗,goole,360搜索等等
- 这种大型的项目我们一个人是没办法完成的,因为它是全网搜索,涉及到的内容过于庞大
但我们可以实现站内搜索
- 比如cplusplus.com
与全网搜索的不同点在于,站内搜索得到的结果更垂直,数据量更小
- 垂直 -- 数据之间具有很强的相关性
表现形式
搜索引擎以什么方式显示给用户?
- 搜索框+搜索按钮
搜索关键字后,每个搜索结果基本由三大部分组成:
- 并且,提供跳转网页的功能
- 其他的搜索引擎也基本是这些内容,只是细节有所不同
boost库介绍
Boost库是一个广泛使用的C++库集合,提供了许多功能强大且高效的工具和组件,可以帮助开发者提高代码的质量和效率
- Boost库包含超过160个独立的库,这些库各自提供特定的功能,包括数据结构、算法、并发编程、网络编程等
但是boost库的官网并没有提供搜索功能,但网站提供的资源量非常大
- 所以需要我们自己做一个boost库的搜索引擎
- 注意,搜索的是boost官网上罗列的手册(接口什么的介绍),而不是boost库的源码
项目环境
centos7云服务器,vim,gcc/g++,makefile,vscode
数据源
下载文档
在boost官网下可以下载 -- Boost C++ Libraries
- 点击下图的download
然后将下载的文件上传到云服务器下
- 如果在上传过程中出现乱码(文件如果很大,就可能会出现乱码)
- 可以在使用rz命令时添加E选项
上传成功后,进行解压缩
页面目录
我们在boost官网下的文档进行查询时,点击其中某个标签页,会发现得到的新页面大多数是放在/doc/html目录下的
- 所以我们在后续使用该网页上的文档作为搜索引擎数据源时,就只需要使用/doc/html目录下的文件即可
这些文件就是boost各个组件对应的手册内容,以网页形式存在
- 我们后面就用它们来建立索引
- 所以我们直接将这个目录拷贝到我们项目文件中
不过也有不在doc下的网页文件,但我们不考虑这些
查看html文件的数量
我们可以查看一下我们的目标目录中一共有多少html文件:
ls -Rl | grep -E '*.html' | wc -l
技术栈
- c/c++,c++11,stl,boost库
- jsoncpp -- cs两端进行数据交换的格式
- cppjieba -- 分词工具(对文档和用户输入内容进行分词)
- cpp-httplib -- 构建http服务器
- html5,css,JavaScript,jQuery,ajax -- 前端内容
原理
过程
客户端(pc或手机)
- 上面会运行着很多软件(浏览器)
服务器(比如公司服务主机)
- 上面也会服务运行软件(searcher)
在提供服务前,需要在各个网站中抓取网页,放入主机磁盘中
- 并且对抓取到的数据进行处理,并建立索引
- 完成之后,就可以基于搜索引擎进行搜索了
用户通过浏览器
- 向服务器发送http请求,其中携带有搜索关键字
服务器接收后
- 使用关键字在索引中进行检索,得到相关网页
- 最后将多个网页信息经过包装(title+desc+url),形成一个新的网页后,返回给客户端
我们这里要完成的内容,就是红框内的功能
- 至于爬虫,我们可以通过正规渠道将目标网站的内容下载下来,也是一样的(也就是前面说的数据源)
正排/倒排索引
假设有两个文档
正排索引
从文档id 找到 文档内容
建立正排索引:
分词
将一段文本切分成更小的单元(称为“词”或“标记”)
我们建立搜索引擎,需要对目标文档分词
- 目的 -- 方便建立倒排索引和查找
可以分出来的不止这些词语
- 因为有些词语是可以连起来的
- 比如四斤小米,或是直接是整句话,总之会有很多种组合方式
暂停/停止词
对文本的意义或分析贡献较小的常见词汇
- 通常用于构建句子的结构,帮助表达语法关系,但对于主题识别和信息检索的贡献有限
- 所以,区分唯一性的价值不大,还会增加搜索成本,一般会在分词的时候忽略掉
- 比如 : 了,的,在,是,吗,a,the
倒排索引
根据关键字(也就是我们对文档进行分词后得到的结果),找到文档id
- 关键字具有唯一性,如果关键字在多个文档中出现,只保留一份
建立倒排索引 -- 我们将文档内容分词后,整理出不重复的关键字,与文档id联系起来
模拟查找过程
接下来,基于这两个索引,我们来模拟一次查找的过程:
假设用户输入"小米"
- 那第一步一定是使用"小米"在倒排索引中进行查找,提取出对应的文档id(1,2)
- 再拿着文档id在正排索引中查找,找到两个文档对应的内容,然后分别对文档结果进行格式化(提取出title+desc+url)
- 最后将结果以网页的形式构建出响应,返回给用户
因为可能有多个文档与关键字匹配
- 所以还需要给每个文档赋权值,谁的权重更高,就把哪个文档显示在前面
- 就像是在各种搜索引擎上搜索出来的内容,一定会经过排序的,而顺序一定是由权重决定的
parser模块
整体思路 -- 对下载下来的文档信息(网页)进行去标签和数据清洗
读取文件
首先肯定是要先拿到文件,再对文件进行处理
- 也就是需要先找到所有文件名,以vector的形式组织起来
- 然后挨个进行读取
因为c++和stl对文件系统的支持不是特别好,所以我们需要使用boost库中的filesystem
- boost开发库的安装 -- sudo yum install boost-devel
- centos7下默认下载的是1.53版本的boost开发库,所以我们去官网上查看接口使用方法时,也应该查看1.53版本的
- 注意:开发库 和 官网上下载到的文档(手册网页信息) ,不是一个东西
查看手册中filesystem的教程,并随便点击一个函数,进入filesystem库的接口说明:
因为boost库并不是c++的标准库,所以我们需要在编译语句中指明我们要使用boost库
- 否则会链接错误 -- -lboost_system 和 -lboost_filesystem
标签
这里截取一部分html目录下某文件的内容:
html的标签都被<>包裹起来
- 对搜索没有意义,只是用来组成网页格式,并且这些标签在每个html文件中都会存在
- 我们需要的是去掉这些标签后的部分,这才是对于搜索引擎来说的有效数据
- 所以需要去标签
标签有成对出现的,也有单独出现的
- eg:<head> 和 </head>,<link...>
如何存放
将文件数据做完去标签化后,该放在哪里呢?
- 新建一个文件夹,并只创建一个文件,再将每个文档去标签的内容都写入到该文件中
- 每个文档之间用特殊符号隔开,相当于每个文档的内容只占"一行"
因为文档中的字符基本都是打印字符,是可显的
- 而控制字符是不可显的,就不会污染我们形成的新的文档
- 所以我们可以选择使用控制字符作为分隔符
于是我们就有了html文档的原始数据和有效内容
代码编写思路
遍历目录
将html文档所在目录定义成一个path对象
- 并在核心逻辑开始前,判断该路径是否存在
我们这里使用boost库提供的迭代器(注意,一定要使用递归迭代器recursive_directory_iterator,但在前期测试的时候可以使用普通迭代器directory_iterator,不然每次运行都可慢)
- 迭代遍历目录下的子文件,直到为空
- 迭代方式就和迭代查询一样,会将目录下所有文件都查出来,包括子目录下的文件
但遍历时,可能遍历到的文件不是html文件,而是其他文件
- 所以,需要判断
- 是否是常规文件 -- is_regular_file()
- 后缀是否是.html -- extension()
path()
- 提取迭代器指向的路径对象
string()
- 获取路径对应的字符串形式(可以用来debug,以及传参)
读取文件
我们可以借助c++的文件流对象
- 打开文件可以使用直接构建文件流对象的形式,然后结合getline读取
如何理解getline读取到文件结束?
- getline的返回值是一个引用,但while判断的是bool类型
- 判断过程的本质是因为重载了强制类型转化
我们可以将像这样的辅助函数全部写在一个文件中,并且限制下作用域
格式化
根据每个文档的内容都形成三个部分,方便我们进行后续操作
接下来是提取三大部分
标题
每个文档的标题都在<titile>...</title>内
- 所以,提取的重点就是识别出这一对标签
然后根据迭代器位置,进行截取
- 注意,在截取之前,我们还需要考虑一种情况,虽然这种情况的概率很小:begin在end之后
- 这样的话就会导致截取到的字符串的字符个数是负数,不符合常理