目录
1.项目相关背景
2.项目宏观原理
3.技术栈和项目环境
4.正排索引&&倒排索引
5.去标签与数据清洗
6.构建索引模块Index
6.1正排索引
6.2 建立倒排
jiebacpp使用
建立分词
7.搜索引擎模块Searcher
Jsoncpp -- 通过jsoncpp进行序列化和反序列化
处理Content
8.引入http-lib
9.Web前端代码编写
10.项目日志编写
11.项目测试
由于boost官网是没有站内搜索的,因此我们需要自己做一个。我们所做的是站内搜索,所谓站内搜索,其搜索的数据更垂直,数据量更小。
技术栈:C/C++ C++11, STL, 准标准库Boost,Jsoncpp,cppjieba,cpp-httplib , 选学: html5,css,js、jQuery、Ajax.
项目环境:Centos 7云服务器,vim/gcc(g++)/Makefile , vs2019 or vs code
文档举例:
文档ID
文档内容
1
雷军买了四斤小米
2
雷军发布了小米手机
正排索引:从文档ID找到文档内容
分词:方便建立倒排索引和查找
雷军买了四斤小米:雷军/买/了/四斤/小米
雷军发布了小米手机:雷军/发布/了/小米/手机
倒排索引:根据关键字,找到文档ID的内容
关键字
文档ID
雷军
1,2
买
1
四斤
1
小米
1,2
四斤小米
1
发布
2
小米手机
2
停止词:了,的,吗,a,the,一般我们在分词的时候可以不考虑
用户输入:小米->倒排索引中查找到文档ID->提取出文档ID(1,2)->根据正排索引->找到文档ID->构建响应结果
我们只需要boost下doc/html/中文件建立索引
作用:原始数据->去标签->把结果放在同一个行文本文档中
Parser.cc
Parser.cc中有主要有三个函数EnumFile,ParseHtml,SaveHtml
5.1 EnumFile():
作用:递归式把每个html文件名带路径,保存到files_list中
步骤:
- 判断路径是否存在
- 判断文件是不是普通文件,因为.html文件是普通文件
- 判断后缀是否符合要求,必须是.html结尾
要使用Boost库中filesystem
boost开发库安装:sudo yum install -y boost-devel
递归遍历:使用boost库中recursive_directory_iterator
必须是.html文件才可以被遍历插入
iter->path().extension() == ".html"
5.2 ParseHtml()
作用:读取每个文件的内容,解析文件内容建立DocInfo_t
步骤:
- 读取文件
- 解析指定文件,提取title -> <title> </title>
- 解析指定文件,提取content
- 解析指定文件,提取url
5.2.1 读取文件
5.2.2 解析指定文件,提取title
由于title标题都是在<title> </title>标签之间,因此我们可以使用字符串操作来进行提取
5.2.3 去掉标签
去标签是基于一个状态机来读取的,在进行遍历的时候,一旦碰到'>',说明该标签处理完毕了,我们不想保留原始文本中的' ',就设定为空字符
5.2.4 拼接url
我们在观察boost官方库的url可以发现,boost库下的官方文档和下载下来的文档有路径对应关系的。
官网URL样例: https://www.boost.org/doc/libs/1_78_0/doc/html/accumulators.html
其中url是由url_head和url_tail拼接而成。而url_head为固定的字符串构成:
"https://www.boost.org/doc/libs/1_81_0/doc/html"
而url_tail正式我们html文件的文件路径名,只保留文件名
5.3 SaveHtml函数
作用:将解析内容写入文档中,一定要考虑下一次在读取的时候也方便操作。因此我们这里采用的格式如下:
类似:title3content3url ...
这样方便我们以后使用getline(ifsream, line),直接获取文档的全部内容:title3content3url
这一步我们要构建正排索引和倒排索引。
//正排索引的数据结构用数组,数组的下标天然是文档的ID
std::vector<DocInfo> forward_index; //正排索引
正排索引是根据doc_id找到文档内容
步骤:
- 字符串切分,解析line
- 字符串进行填充到DocInfo
- 将DocInfo插入到正排索引的vector中
这里使用了boost库中的split方法。如果有多个"3"分割符时,要将第三个参数设置为boost::token_compress_on
注意:先进行保存id,再插入,对应的id就是当前doc在vector中的下标!
根据文档内容,行成一个或者多个InvertedElem(倒排拉链),因为当前我们是在一个文档进行处理的,一个文档中会包含多个"词",都对应到当前doc_id。
- 需要对title和content进行分词
- 设置词和文档的相关性 -- 我们这里使用词频统计 使用weight,因此我们需要定义一个倒排拉链的结构体,这里做一个简单处理。
- 自定义相关性,让在标题出现的关键字的weight值更高。因此weight = 10*title + content。
安装cpp-jieba:
获取链接:cppjieba: cppjieba cppjieba
下载成功后rz -E 加入到我们的项目路径下。之后我们建立软连接到当前的目录之下
ln -s cppjieba/dict dict -- 词库
ln -s cppjieba/include/cppjieba/ cppjieba --相关头文件
注意:在使用cppjieba时有一个坑,比如把deps/limonp 下的文件拷贝到include/cppjieba/ 下才能正常使用
cp deps/limonp include/cppjieba/ -rf
jiebacpp使用
获取单例时可能会有线程安全的问题,我们对其进行加锁
至此我们引入了jieba分词,我们可以正是编写倒排索引了
建立分词
首先对title和content进行分词,因此当title和content分完词后我们要对词和词频建立映射表。我们对title和content进行分词是想统计各个词出现的词频。我们建立vector来保存分出来的词语。
当这个词语出现在title时认为其权重较重,在content出现时认为其权重较轻。
注意:由于搜索的时候本身是不区分大小写的,因此我们在分词结束之后将出现的词语全部转换成小写,然后进行统计。
我们编写Searcher首先需要获取或穿件index对象,然后根据index对象建立索引
建立正排和倒排索引成功之后,用户要进行搜索了。首先我们要先将用户输入的关键字进行分词,然后根据分词的各个词进行index查找,建立index时要忽略大小写。然后进行合并排序,汇总查找结果,按照相关性进行降序排序,将权重高的排在前面,最后我们根据查找出来的结果,构建Json串
查找时我们首先需要获取倒排拉链。
Jsoncpp -- 通过jsoncpp进行序列化和反序列化
jsoncpp的安装:sudo yum install -y jsoncpp-devel
那我们如何使用Jsoncpp呢?我们做一下演示
这里注意我们在编译时是需要链接json库的否则会报连接时错误
需要 -ljsoncpp
我们发现打印的结果进行了序列化。我们还有另一种形式FastWriter,这种形式更加简洁
做好这些准备工作之后我们进行构建Json串
这里还有一个需要注意的地方是content是文档的去标签结果,但是不是我们想要的,我们只需要一部分,因此这里需要进行处理。
处理Content
引入http-lib:cpp-httplib: cpp-httplib - Gitee.com
注意:在引入http-lib的时候需要较新版本的gcc,使用gcc -v便可查看gcc版本
如果您当前是较低版本,请先升级至较新版本,升级方法:升级GCC-Linux CentOS7
当GCC版本更新之后我们在当前文件下创建httplib的软连接
ln -s /home/Lxy/cpp-httplib-v0.7.15 cpp-httplib
了解html,css,js
html: 是网页的骨骼 -- 负责网页结构
css:网页的皮肉 -- 负责网页美观的
js(javascript):网页的灵魂---负责动态效果,和前后端交互
教程: w3school 在线教程
项目部署到Linux服务器上
nohup ./http_server > log/log.txt 2>&1 &