BOOST
Boost是一个由C++社区开发的开源库,为C++语言标准库提供扩展。这个库由C++标准委员会库工作组成员发起,旨在提供大量功能和工具,帮助C++开发者更高效地编写代码。Boost库强调跨平台性和对标准C++的遵循,因此与编写平台无关,是C++标准化进程的重要开发引擎之一。
但是BOOST没有站内的搜索引擎,我们在使用想快速查找信息的时候十分不方便,所以我们自己动手实现一个。
技术栈与项目环境
技术栈:
- 用C/C++作为主要编程语言,并利用STL中的容器等功能
- cpp-httplib:轻量级HTTP库,用于构建HTTP服务器和处理HTTP请求
- cppjieba:中文分词工具,用于对查询词和文档进行分词处理。
- HTML/CSS:用于前端开发,构建用户搜索界面和展示搜索结果。
项目环境:
- CentOS7云服务器,作为项目的运行环境
- vim/gcc/g++/Makefile:代码编辑、构建
搜索引擎的原理
正排索引(Forward Index)
正排索引是以文档的ID为关键字,表中记录文档中每个词的位置信息。
文档ID | 文档内容 |
---|---|
1 | 张三在吃饭 |
2 | 张三在买东西 |
索引的ID和文档的内容是一一对应的,记录了文档中出现的关键词及其出现次数和位置信息。正排索引的优势在于可以快速地查找某个文档里包含哪些词项,但不适用于查找包含某个词项的文档有哪些。
倒排索引(Inverted Index)
倒排索引是以词为关键字的索引结构,表中记录了出现这个词的所有文档的ID和位置信息,或者记录了这个词在哪些文档的哪些位置出现过,以及出现多少次。
关键字(具有唯一性) | 文档ID,weight(权重) |
---|---|
张三 | 文档1,文档2 |
吃饭 | 文档1 |
买东西 | 文档2 |
查找过程: 用户输入张三-> 倒排索引->提取文档ID(1,2)->根据正排索引->找到文档的内容 ->title +conent(desc)+url 文档结果进行摘要 ->构建响应结果
项目实现
数据部分
在boost官网把boost的内容下载下来,并在CentOS7下解压。
创建一个data的目录,把boost_1_83_0/doc/html/data拷贝到data目录下的input中来,此时data/input就是我们的数据源。
去标签与数据清洗Parser
上面说到的data/input数据源中我们随便打开一个html文件
可以看到我们需要的是文档中的内容,所以需要把标签去掉,写入到raw_html目录中。
typedef struct DocInfo
{
std::string title; // 文档的标题
std::string content; // 文档的内容 --body里面有内容信息
std::string url; // 该文档在官网中的url
} DocInfo_t;
int main()
{
std::vector<std::string> files_list;
// 1.递归式的把每个html文件命带路径,保存到files_list中
// 方便后期进行一个一个文件的读取
if (!EnumFile(src_path, &files_list))
{
std::cerr << "euum file name error!" << std::endl;
return 1;
}
// 2.按照files_list 读取每个文件的内容,并进行解析
std::vector<DocInfo_t> results; // 解析后的结果存放在results中
if (!ParseHtml(files_list, &results))
{
std::cerr << "parse html error" << std::endl;
return 2;
}
// 3.把解析完毕的各个文件内容,写入到output中,按照\3作为分隔符
if (!SaveHtml(results, output))
{
std::cerr << "sava html error" << std::endl;
return 3;
}
return 0;
}
遍历数据源下所有以 .html 扩展名的文件路径,然后将这些路径存储在一个files_list中。
bool EnumFile(const std::string &src_path, std::vector<std::string> *files_list)
{
namespace fs = boost::filesystem; // 给boost命名空间起别名
fs::path root_path(src_path);
if (!fs::exists(root_path)) // 路径不存在直接退出
{
std::cerr << src_path << "not exists" << std::endl;
return false;
}
// 定义一个迭代器来判断递归的结束
fs::recursive_directory_iterator end;
// 遍历文件路径
for (fs::recursive_directory_iterator iter(root_path); iter != end; iter++)
{
// 判断是不是普通文件
if (!fs::is_regular_file(*iter))
{
continue;
}
// 判断文件名的后缀是否符合要求
if (iter->path().extension() != ".html")
{
continue;
}
files_list->push_back(iter->path().string()); // 把带路径的html保存在files_list中
}
return true;
}
从files_list的HTML文件列表中解析信息,提取titile、continue并构建URL到一个个DocInfo_t中。
bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo_t> *results)
{
for (const std::string &file : files_list)
{
std::string result;
// 读文件
if (!ns_util::FileUtil::ReadFile(file, &result))
{
continue;
}
DocInfo_t doc;
// 解析文件,提取title
if (!ParseTitle(result, &doc.title))
{
continue;
}
// 解析文件,提取continue--去标签
if (!ParseContent(result, &doc.content))
{
continue;
}
// 解析文件路径,构建url
if (!ParseUrl(file, &doc.url))
{
continue;
}
// 完成解析任务,解析结果保存在doc中
results->push_back(std::move(doc)); // 减少拷贝
}
return true;
}
//<title> 我们要的内容 <title>
static bool ParseTitle(const std::string &file, std::string *title)
{
std::size_t begin = file.find("<title>");
if (begin == std::string::npos)
{
return false;
}
std::size_t end = file.find("</title>");
if (end == std::string::npos)
{
return false;
}
begin += std::string("<title>").size();
if (begin > end)
{
return false;
}
*title = file.substr(begin, end - begin);
return true;
}
/*<td align="center"><a href="../../../index.html">Home</a></td>
<td align="center"><a href="../../../libs/libraries.htm">Libraries</a></td>
<td align="center"><a href="http://www.boost.org/users/people.html">People</a></td>
<td align="center"><a href="http://www.boost.org/users/faq.html">FAQ</a></td>
<td align="center"><a href="../../../more/index.htm">More</a></td>*/
//上面的标签都清洗掉
/*<div class="titlepage"><div><div><h2 class="title" style="clear: both">
<a name="array.ack"></a>Acknowledgements</h2></div></div></div>
<p>Doug Gregor </p>*/
//标签中的Acknowledgements 、Doug Gregor保留下来
static bool ParseContent(const std::string &file, std::string *content)
{
//状态机
enum status
{
LABLE,
CONTENT
};
enum status s = LABLE; // 默认为LABLE状态
for (char c : file) //以字符方式遍历file
{
switch (s)
{
case LABLE:
if (c == '>') // 碰到>就意味着标签处理完毕
s = CONTENT;
break;
case CONTENT:
if (c == '<') //碰到<就意味着内容处理完毕
s = LABLE;
else
{
// 不保留原始文件的\n, \n用来做html解析之后的文本分割符
if (c == '\n')
c = ' ';
content->push_back(c);
}
break;
default:
break;
}
}
return true;
}
/*
boost库的官方文档和下载下来的文档是有路径对应关系的
//官网URL
https://www.boost.org/doc/libs/1_83_0/doc/html/accumulators.html
//linux中URL
data/input/accumulators.html//下载下来的boost库
url_head ="https://boost.org/doc/libs/1_83_0/doc/html/"
url_tail= data/input/accumulators.html data/input(删除)--> /accumulators.html
url = url_head + url_tail //相当于一个官网连接 */
static bool ParseUrl(const std::string &file_path, std::string *url)
{
std::string url_head = "https://www.boost.org/doc/libs/1_83_0/doc/html";
std::string url_tail = file_path.substr(src_path.size()); //subsrt去掉data/input
*url = url_head + url_tail;
return true;
}
将一组DocInfo_t类型的数据也就是上面解析出来的内容保存到output中。
#define SEP '\3' //分割符号
bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output)
{
// 二进制写入--写入什么文档保存就是什么
std::ofstream out(output, std::ios::out | std::ios::binary);
if (!out.is_open())
{
std::cerr << "open " << output << "failed" << std::endl;
return false;
}
//title\3content\3url \n title\3content\3url \n ....
//方便getline(ifsream,line)直接获取文档全部内容 title\3content\3url(getline以\n结束)
for (auto &item : results)
{
std::string out_string;
out_string = item.title;
out_string += SEP;
out_string += item.content;
out_string += SEP;
out_string += item.url;
out_string += '\n'; // 文档和文档之间的分隔
out.write(out_string.c_str(), out_string.size());
}
out.close();
return true;
}
索引Index
对清洗过的内容构建正排、倒排索引
struct DocInfo
{
std::string title; //文档的标题
std::string content; //文档对应的去标签之后的内容
std::string url; //官网文档url
uint64_t doc_id; //文档的ID,用uint64_t:为了防止索引越界
};
//倒排结构
struct InvertedElem
{
uint64_t doc_id;//对应的文档id
std::string word;//关键字
int weight; //权重 可以根据权重决定文档显示的先后顺序
InvertedElem():weight(0){}
};
typedef std::vector<InvertedElem> InvertedList;// 倒排拉链
//单例模式
class Index
{
private:
std::vector<DocInfo> forward_index;// 正排拉链
//存放关键字和倒排拉链的映射关系
std::unordered_map<std::string, InvertedList> inverted_index;
public:
Index(){} //但是一定要有函数体,不能delete
~Index(){}
Index(const Index&) = delete;
Index& operator=(const Index&) = delete;
static Index* instance;//指向全局唯一的单例对象
static std::mutex mtx;
public:
//获取全局唯一的单例对象
static Index* GetInstance()
{
//第一次需要加锁,后面都不需要加锁的场景,可以使用双检查加锁
//特点:第一次加锁,后面不加锁,保护线程安全,同时提高效率
if(nullptr == instance)
{
//加锁只有第一次有意义,后面再有线程来没必要加锁,加锁会引发效率低下
mtx.lock();
if(nullptr == instance)
{
instance = new Index();
}
mtx.unlock();
}
return instance;//返回这个全局的单例对象
}
//根据文档id找到找到文档内容
DocInfo* GetForwardIndex(uint64_t doc_id)
{
if(doc_id >= forward_index.size()) //没有该文档id
{
std::cerr << "doc_id out range, error!" << std::endl;
return nullptr;
}
return &forward_index[doc_id]; //数组的下标天然就是文档id,所以直接返回数组对应的内容的地址
}
//根据关键字word,获得倒排拉链
InvertedList *GetInvertedList(const std::string &word)
{
//在哈希表当中查找是否存在这个关键字对应的倒排拉链
auto iter = inverted_index.find(word);
if(iter == inverted_index.end())
{
std::cerr << word << " have no InvertedList" << std::endl;
return nullptr;
}
return &(iter->second);
}
//根据文档内容构造索引,参数:经过数据清洗之后的文档
bool BuildIndex(const std::string &input)
{
std::ifstream in(input, std::ios::in | std::ios::binary);
if(!in.is_open()){
std::cerr << "build index error, open " << input << " failed" << std::endl;
return false;
}
int count = 0;//方便观察当前建立的索引个数
std::string line; //读取一行数据
//对每一个html文件格式化之后的内容进行正排和倒排索引
while(std::getline(in, line))
{
//建立正排索引,返回描述这个文档内容的节点
DocInfo * doc = BuildForwardIndex(line);
if(nullptr == doc)
{
std::cerr << "build " << line << " error" << std::endl; //for deubg
continue;
}
BuildInvertedIndex(*doc);//根据上面返回的正排索引节点,建立倒排索引
count ++;
if(count % 150 == 0)
{
LOG(NORMAL,"当前的已经建立的索引文档:"+std::to_string(count));
}
}
return true;
}
};
建立正排索引
//line:就是一个html文件里面去标签,格式化之后的文档内容
DocInfo *BuildForwardIndex(const std::string &line)
{
//1. 解析line->本质是进行字符串切分,解析成:title, content, url
std::vector<std::string> results;//保存切分好的内容
const std::string sep = "\3"; //分隔符
//切分字符串
ns_util::StringUtil::Split(line, &results, sep);
if(results.size() != 3) //判断切分结果是否正确,要切分为3部分
{
return nullptr;
}
//2. 切分好的字符串进行填充到DocIinfo
DocInfo doc;
doc.title = results[0]; //title
doc.content = results[1]; //content
doc.url = results[2]; ///url
doc.doc_id = forward_index.size();
//3. 插入到正排索引数组当中
forward_index.push_back(std::move(doc));
return &forward_index.back();
}
建立倒排索引
bool BuildInvertedIndex(const DocInfo &doc)//建立倒排索引,参数是正排索引节点
{
//DocInfo里面包含一个html文件的:{title, content, url, doc_id}
struct word_cnt //针对一个词的数据统计
{
int title_cnt;//在标题出现次数
int content_cnt;//在内容中出现次数
word_cnt()
:title_cnt(0)
, content_cnt(0)
{}
};
std::unordered_map<std::string, word_cnt> word_map; //暂存词频的映射表
/* 根据文档标题和内容,分词并进行词频统计 */
//对标题进行分词
std::vector<std::string> title_words;
ns_util::JiebaUtil::CutString(doc.title, &title_words);
//对标题分词之后的结果进行词频统计
for(std::string s : title_words)
{
boost::to_lower(s); //忽略大小写
//查找对应关键词,如果存在就++,不存在就新建
word_map[s].title_cnt++;
}
//对内容进行分词
std::vector<std::string> content_words;
ns_util::JiebaUtil::CutString(doc.content, &content_words);
//对内容进行词频统计
for(std::string s : content_words)
{
boost::to_lower(s);//忽略大小写
word_map[s].content_cnt++;
}
// 自定义相关性
#define X 15 //标题当中出现的词,权重更高
#define Y 1
//建立倒排拉链
for(auto &word_pair : word_map)
{
InvertedElem item;
item.doc_id = doc.doc_id;
item.word = word_pair.first;
//设置权重
item.weight = X*word_pair.second.title_cnt +Y*word_pair.second.content_cnt;
InvertedList& inverted_list = inverted_index[word_pair.first];
inverted_list.push_back(std::move(item));
}
return true;
}
搜索服务Searcher
前面的部分都是前期的准备工作,Searcher提供搜索服务,对用户输入的字符串进行分词,使用倒排索引查找与查询词相关的内容,并进行排序。
namespace ns_searcher
{
struct InvertedElemPrint
{
uint64_t doc_id; // 文档id
int weight; // 累加权重
std::vector<std::string> words; // 一个doc_id对应多个关键字
InvertedElemPrint() : doc_id(0), weight(0)
{}
};
class Searcher
{
private:
ns_index::Index *index; // 供系统进行查找的索引
public:
Searcher() {}
~Searcher() {}
void InitSearcher(const std::string &input)
{
// 1. 获取或者创建index对象
index = ns_index::Index::GetInstance();
LOG(NORMAL, "获取index单例成功");
// 2. 根据index对象建立索引
index->BuildIndex(input);
LOG(NORMAL, "建立正排和倒排索引成功...");
}
// 获取一部分内容
std::string GetDesc(const std::string &html_content, const std::string &word)
{
// 1.在html_content范围内,找word首次出现的位置
auto cmp = [](char x, char y)
{ return (std::tolower(static_cast<unsigned char>(x)) == std::tolower(static_cast<unsigned char>(y))); };
auto iter = std::search(html_content.begin(), html_content.end(),
word.begin(), word.end(), cmp);
if (iter == html_content.end())
{
return "None1";
}
int pos = iter - html_content.begin();
// 2. 获取start,end的位置
int start = 0; // 默认就是在文本的起始位置
int end = html_content.size() - 1; // 默认是文本的结尾
// 找到word在html_content中的首次出现的位置,然后往前找30字节,如果没有,就从 start开始
// 往后找80字节如果没有,到end就可以的 ,然后截取出这部分内容
const int prev_step = 30;
const int next_step = 80;
if (pos - prev_step > 0)
start = pos - prev_step;
if (pos + next_step < end)
end = pos + next_step;
// 3. 截取子串,然后return
if (start >= end)
return "None2";
// 从start开始,截取end - start个字节的内容
std::string desc = html_content.substr(start, end - start);
desc += "...";
return desc;
}
// query:用户搜索关键字 json_string: 返回给用户浏览器的搜索结果
void Search(const std::string &query, std::string *json_string)
{
// 1.对query进行按照searcher的要求进行分词
std::vector<std::string> words;
ns_util::JiebaUtil::CutString(query, &words);
// 2.就是根据分词的各个"词",进行index索引查找
std::vector<InvertedElemPrint> inverted_list_all; // 存放所有经过去重之后的倒排拉链
// 根据doc_id进行去重,凡是id相同的倒排索引节点,其关键字都放在InvertedElemPrint的vector里面
std::unordered_map<uint64_t, InvertedElemPrint> tokens_map;
// 因为我们建立索引的时候,建立index是忽略大小写,所以搜索的时候关键字也需要忽略大小写
for (std::string word : words)
{
boost::to_lower(word); // 将切分之后的查找关键字转为小写
// 先查倒排
ns_index::InvertedList *inverted_list = index->GetInvertedList(word); // 通过关键字获得倒排拉链
if (nullptr == inverted_list)
{
continue; // 检测下一个关键词
}
for (const auto &elem : *inverted_list) // 遍历倒排拉链,合并索引节点
{
//[]:如果存在直接获取,如果不存在新建
auto &item = tokens_map[elem.doc_id]; // 根据文档id获得其对应的关键字集合
// 此时item一定是doc_id相同的倒排拉链打印节点
item.doc_id = elem.doc_id;
item.weight += elem.weight; // 权值累加
item.words.push_back(elem.word); // 把当前的关键字放到去重的倒排拉链打印节点当中的vector当中保存
}
}
for (const auto &item : tokens_map) // 遍历去重之后的map
{
inverted_list_all.push_back(std::move(item.second)); // 插入倒排拉链打印节点到inverted_list_all
}
// 3.汇总查找结果,按照相关性(weight)进行降序排序
auto cmp = [](const InvertedElemPrint &e1, const InvertedElemPrint &e2)
{
return e1.weight > e2.weight;
};
std::sort(inverted_list_all.begin(), inverted_list_all.end(), cmp);
// 4.根据查找出来的结果,构建json串 -- jsoncpp --通过jsoncpp完成序列化&&反序列化
Json::Value root;
for (auto &item : inverted_list_all)
{
// item就是倒排索引打印节点
ns_index::DocInfo *doc = index->GetForwardIndex(item.doc_id); // 根据文档id->查询正排,返回正排索引节点
if (nullptr == doc)
{
continue;
}
// doc里面就有title,url,content,文档id
// 我们想给浏览器返回的是网页的title,内容摘要,链接
Json::Value elem;
elem["title"] = doc->title;
//只需要一部分文档内容
elem["desc"] = GetDesc(doc->content, item.words[0]);
elem["url"] = doc->url;
// for deubg, for delete
elem["id"] = (int)item.doc_id; // doc_id是uint64 ,json可能报错,所以转为int
elem["weight"] = item.weight; // doc_id,weight虽然是int,但是json会帮我们自动转为string
root.append(elem);
}
// Json::StyledWriter writer; //为了方便调试观看
Json::FastWriter writer;
*json_string = writer.write(root);
}
};
}
http_server
监听HTTP请求,解析用户输入的参数,调用Searcher进行搜索,把给结果响应回给客户。
#include "cpp-httplib/httplib.h"
#include "searcher.hpp"//引入搜索引擎
const std::string input = "data/raw_html/raw.txt";//html文件经过parser之后存放的结果的路径
const std::string root_path = "./wwwroot"; //wwroot作为服务器的主页
int main()
{
ns_searcher::Searcher search;
search.InitSearcher(input);
httplib::Server svr;
svr.set_base_dir(root_path.c_str());
svr.Get("/s", [&search](const httplib::Request &req, httplib::Response &rsp) {
if (!req.has_param("word"))
{
rsp.set_content("必须要有搜索关键字!", "text/plain; charset=utf-8");
return;
}
std::string word = req.get_param_value("word");
LOG(NORMAL,"用户在搜索:"+word);
std::string json_string;
search.Search(word, &json_string);
//返回给客户端
rsp.set_content(json_string, "application/json");
});
LOG(NORMAL,"服务器启动成功...");
svr.listen("0.0.0.0", 8081);
return 0;
}
工具util
//文件读取
class FileUtil
{
public:
static bool ReadFile(const std::string &file_path, std::string *out)
{
std::ifstream in(file_path, std::ios::in); // 读文件
if (!in.is_open())
{
std::cerr << "open file" << file_path << "error" << std::endl;
return false;
}
std::string line;
// 为什么getline的返回值是流的引用&,while是bool,能进行判断呢?
// 重载了强制类型转换
while (std::getline(in, line))
{
*out += line;
}
in.close();
return true;
}
};
// 字符串切割
class StringUtil
{
public:
static void Split(const std::string &target, std::vector<std::string> *out, std::string sep)
{
// token_compress_on on:压缩打开 --将所有相连的分隔符压缩成一个 aa\3\3\3\3\3bbb-->aa bbb
// off:压缩关闭
boost::split(*out, target, boost::is_any_of(sep), boost::token_compress_on);
}
};
//Jieba分词
// dict路径
const char *const DICT_PATH = "./dict/jieba.dict.utf8";
const char *const HMM_PATH = "./dict/hmm_model.utf8";
const char *const USER_DICT_PATH = "./dict/user.dict.utf8";
const char *const IDF_PATH = "./dict/idf.utf8";
const char *const STOP_WORD_PATH = "./dict/stop_words.utf8";
class JiebaUtil
{
private:
// static cppjieba::Jieba jieba;
cppjieba::Jieba jieba;
std::unordered_map<std::string, bool> stop_words;
private:
JiebaUtil()
: jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH)
{}
JiebaUtil(const JiebaUtil &) = delete;
static JiebaUtil *instance;
public:
static JiebaUtil *get_instance()
{
static std::mutex mtx;
if (nullptr == instance)
{
mtx.lock();
if (nullptr == instance)
{
instance = new JiebaUtil();
instance->InitJiebaUtil();
}
mtx.unlock();
}
return instance;
}
// 把暂停词加载进来
void InitJiebaUtil()
{
std::ifstream in(STOP_WORD_PATH);
if (!in.is_open())
{
LOG(FATAL, "load stop words file error");
return;
}
std::string line;
while (std::getline(in, line))
{
stop_words.insert({line, true});
}
in.close();
}
/*
去掉暂停词
暂停词是对我们搜索没意义的词语,如:“的”、“地”、“吧”等*/
void CutStringHelper(const std::string &src, std::vector<std::string> *out)
{
jieba.CutForSearch(src, *out);
for (auto iter = out->begin(); iter != out->end();)
{
auto it = stop_words.find(*iter);
if (it != stop_words.end())
{
// 说明当前的string 是暂停词,需要去掉
iter = out->erase(iter);
}
else
{
iter++;
}
}
}
public:
static void CutString(const std::string &src, std::vector<std::string> *out)
{
ns_util::JiebaUtil::get_instance()->CutStringHelper(src, out);
// jieba.CutForSearch(src,*out);
}
};
log
#pragma once
#include<iostream>
#include<string>
#include<ctime>
#define NORMAL 1
#define WARNING 2
#define DEBUG 3
#define FATAL 4
#define LOG(LEVEL,MESSAGE) log(#LEVEL,MESSAGE,__FILE__,__LINE__)
void log(std::string level,std::string message,std::string file,int line)
{
std::cout<<"["<<level<<"]"<<time(nullptr)<<"["<<message<<"]"<<"["<<file<<":"<<line<<"]"<<std::endl;
}
编写js
用原生js成本高,jQuery好
<script>
function Search(){
// 是浏览器的一个弹出框
//alert("hello js!");
// 1. 提取数据, $可以理解成就是JQuery的别称
let query = $(".container .search input").val();
console.log("query = " + query); //console是浏览器的对话框,可以用来进行查看js数据
//2. 发起http请求,ajax: 属于一个和后端进行数据交互的函数,JQuery中的
$.ajax({
type: "GET",
url: "/s?word=" + query,
success: function(data){
console.log(data);
BuildHtml(data);
}
});
}
function BuildHtml(data){
// if(data==''||data==ull)
// {
// document.write("搜索不到");
// return;
// }
// 获取html中的result标签
let result_lable = $(".container .result");
// 清空历史搜索结果
result_lable.empty();
for( let elem of data){
let a_lable = $("<a>", {
text: elem.title,
href: elem.url,
// 跳转到新的页面
target: "_blank"
});
let p_lable = $("<p>", {
text: elem.desc
});
let i_lable = $("<i>", {
text: elem.url
});
let div_lable = $("<div>", {
class: "item"
});
a_lable.appendTo(div_lable);
p_lable.appendTo(div_lable);
i_lable.appendTo(div_lable);
div_lable.appenesult_lable);
}
}
</script>
项目扩展
- 建立整个站的搜索,把boost库的各个版本都进行正排倒排建立索引
- 数据部分设计一个在线更新网页内容,利用爬虫,信号等进行设计
- 添加整站搜索、竞价排名、热词统计等,进一步丰富项目的功能和用户体验。
- 设计登录注册功能,引入mysql
遇到问题
1.存在搜索出相同的内容的问题
解决:在searcher模块用unordered_map对建立的倒排拉链进行去重。
2.查看权重值的时候发现对不上
原因:分词的时候会出现包含单词也会统计的情况,比如我搜sprlit,sprlitabc也会被搜到。
3.使用jieba库的时候出现找不到Log的错误
jieba的include下的头文件有方法,jieba/dict里有分词的库(具体怎么分词),我们采取链接的方式,jieba有一个问题就是需要把deps/limonp拷贝到include/cppjieba下,不然会报错找不到log