CPP项目:Boost搜索引擎

1.项目背景

对于Boost库来说,它是没有搜索功能的,所以我们可以实现一个Boost搜索引擎来实现一个简单的搜索功能,可以更快速的实现Boost库的查找,在这里,我们实现的是站内搜索,而不是全网搜索。

2.对于搜索引擎的相关宏观理解

3.搜索引擎技术栈及项目环境

技术栈:c/c++,c++11,STL,Boost准标准库,Jsoncpp,cppjieba,cpp-httplib,html5,css,js,

Ajax,jQuery,不使用爬虫。

项目环境:Centos 7云服务器,vim/gcc(g++)/Makefile , vs code

4.正排索引、倒排索引

正排索引:通过文档ID寻找文档内容:

比如:文档1:西游记有一只猴子

           文档2:西游记有一只六耳猕猴

文档1西游记有一只猴子
文档2西游记有一只六耳猕猴

目标文档进行分词(目的:方便建立倒排索引和查找):

文档1:西游记/有/一只/猴子/

文档2:西游记/有/一只/六耳/猕猴/

注:停止词:了,的,吗,a,the,一般我们在分词的时候可以不考虑

倒排索引:根据文档内容,分词,整理不重复的各个关键字,对应联系到文档ID的方案。

关键字:文档ID, weight(权重)
西游记文档1.文档2
文档1,文档2
一只文档1.文档2
猴子文档1
六耳文档2
猕猴文档2

模拟一次查找的过程:

用户输入:西游记->倒排索引中查找->提取出文档id->根据正排索引->找到文档内容,通过文档标题,内容,URL,对文档内容进行摘要->构建响应结果。

5.编写数据去标签与数据清洗的模块 Parser

5.1 什么是标签:

我们既然要去标签,那么就要知道什么是标签,比如:

//原始数据 -> 去标签之后的数据
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html> <!--这是一个标签-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Chapter 30. Boost.Process</title>
<link rel="stylesheet" href="../../doc/src/boostbook.css" type="text/css">
<meta name="generator" content="DocBook XSL Stylesheets V1.79.1">
<link rel="home" href="index.html" title="The Boost C++ Libraries BoostBook Documentation
Subset">
<link rel="up" href="libraries.html" title="Part I. The Boost C++ Libraries (BoostBook
Subset)">
<link rel="prev" href="poly_collection/acknowledgments.html" title="Acknowledgments">
<link rel="next" href="boost_process/concepts.html" title="Concepts">

上述代码中,<!--这是一个标签-->,这就是一个标签,<>这些标签对于我们的搜索来说是没有意义的,所以我们需要去掉这些标签。标签分为两类,一类是只有一个的<>,一类是有两个的<>,</>,这些都是我们需要去掉的。

5.2 搜索内容

由于我们使用的是本地搜索,即将Boost库下载下来,通过本地搜索,然后再通过建立网站,实现站内搜索。所以第一步,下载Boost库:

Boost库官网:Boost C++ Libraries

Boost库下载:Index of main/release/1.84.0/source

目前只需要boost库/doc/html目录下的html文件,用它来进行建立索引,不需要使用其他的

下载完成之后,我们把他上传到云服务器上。之后建立一个保存*.html文件的文件夹,我们之后需要使用它,然后再创建一个保存去标签之后的*.html文件的文档,方便我们搜索。

5.3 具体逻辑实现

const std::string src_path = "/home/SSS/data/input";//搜索路径
const std::string output = "/home/SSS/data/raw_html/raw.txt";//保存文档typedef struct DocInfo
{std::string title;   // 文档标题std::string content; // 文档标题std::string url;     // 文档url
} DocInfo_t;// &: 输入
//*: 输出
//&:输入输出
bool EnumFile(const std::string &src_path, std::vector<std::string> *files_list);
bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo_t> *results);
bool SaveHtml(const std::vector<DocInfo_t> &results,const std::string& output);int main()
{std::vector<std::string> files_list; //保存文件路径//递归式的把每个html文件名带路径,保存到files_list中,方便后期进行一个一个的文件进行读取if (!EnumFile(src_path, &files_list)){std::cerr << "enum file name error1" << std::endl;return 1;}// 按照files_list读取每个文件的内容,并进行解析std::vector<DocInfo_t> results;if (!ParseHtml(files_list, &results)){//std::cout<<"1"<<std::endl;std::cerr << "enum file name error2" << std::endl;return 2;}std::cout<<results.size()<<std::endl;//把解析完毕的各个文件内容,写入到output,按照\3作为每个文档的分割符 if (!SaveHtml(results,output)){std::cerr << "enum file name error3" << std::endl;return 3;}return 0;
}

第一个函数通过对文件路径的处理,将我们所需要的html文件保存到vector中,方便我们后续查找。第二个函数进行对html文件的处理,将html文件的标题,内容,url进行提取,保存到数组中。第三个函数将处理好的内容放入到文件中,方便后续进行建立索引。

三个函数的具体实现过程:

第一步:

bool EnumFile(const std::string &src_path, std::vector<std::string> *files_list)
{namespace fs = boost::filesystem;fs::path root_path(src_path);if (!fs::exists(root_path)) // 判断文件路径是否存在,在这里我们使用Boost库中的函数,判断文件路径是否存在{std::cerr << "root_path not exits" << 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)) // 文件是否为普通文件,Boost库中判断文件是否为普通文件。{continue;}if (iter->path().extension() != ".html") // 文件是否为html文件,Boost库函数{continue;}files_list->push_back(iter->path().string());//将所有带路径的html保存在files_list,方便后续进行文本分析}return true;
}

第二步:

namespace ns_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())//c++中文件处理函数{std::cerr <<"open in error"<<std::endl;return false;}std::string line;while(std::getline(in,line))//将文件内容写入到(string)out中{*out += line;//std::cout<<line<<std::endl;}in.close();//关闭文件!!!return true;}};
}// 文档标题
static bool ParseTitle(const std::string &file, std::string *title)
{std::size_t begin = file.find("<title>");//对文档标题进行处理,由于网页的标题保存在<title>标//签中,以</title>结尾,所以中间的内容就是我们所要的标题//std::cout<<begin<<std::endl;if (begin == std::string::npos){//std::cout<<"1:"<<begin<<std::endl;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);//标题//std::cout<<*title<<std::endl;return true;
}
// 对内容进行解析
static bool ParseContent(const std::string &file, std::string *content)
{//去标签,基于一个简易的状态机enum status{LABLE,CONTENT};enum status s = LABLE;for (char c : 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;
}
static bool ParseUrl(const std::string &file_path, std::string *url)
{std::string url_head = "https://www.boost.org/doc/libs/1_78_0/doc/html";std::string url_tail = file_path.substr(src_path.size());*url = url_head + url_tail;//文档的urlreturn true;
}
// 对内容进行解析
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;// 解析文件,提取titleif (!ParseTitle(result, &doc.title)){//std::cout<<"1"<<std::endl;continue;}// 解析文件内容,去标签if (!ParseContent(result, &doc.content)){continue;}// 解析内容,获得urlif (!ParseUrl(file, &doc.url)){continue;}results->push_back(std::move(doc)); // 减少拷贝次数,提高效率//std::cout<<results->back()->title<<std::endl;}return true;
}

ParseHtml()这个函数内部包含四个函数,其中包括读取文件,将文件标题,内容,url分别提取出来,放入到数组中。

网站处理:

官网URL样例: https /www.boost.org/doc/libs/1_79_0/doc/html/accumulators.html
我们下载下来的url样例:boost_1_79_0 / doc / html / accumulators.html
我们拷贝到我们项目中的样例:data / input / accumulators.html
url_head = “https://www.boost.org/doc/libs/1_79_0/doc/html”;
url_tail = (data / input)(删除) / accumulators.html->url_tail = / accumulators.html
url = url_head + url_tail; 相当于形成了一个官网链接。

5.4建立索引

实现原理:

建立索引我们需要建立正排索引和倒排索引,这需要我们建立一个struct,用来保存文档的title,content,url,id。因为正排索引是通过文档id来寻找文档内容的,所以我们可以通过一个数组来实现id的编号,倒排索引是通过关键字来锁定在哪一个文档id里面的,所以我们可以通过建立一个哈希映射来完成,通过关键字来寻找文档id。由于我们只需要建立一次索引,所以我们可以通过创建一个单例模式来实现所以得初始化。

由于我们通过关键字可以映射到多个文档内容中,所以我们可以通过建立相关性的方式来排序我们所查找得的文档内容。

相关性的建立:由于相关性的问题涉及到的范围十分的大,可能有相近词,同义词,不连续的关键词,字符拆分的不确定性等等,在这里我们使用关键字在文档中出现的频次来决定。由于关键字可以出现在标题中,也可以出现在文档内容中,所以我们通过简单粗暴的方式来解决,即在标题中出现的关键在是10倍与在内容中出现的关键字。当然,相关性的处理方式可以自己设定。

建立索引对象:

 //文档属性struct DocInfo{std::string _title;//文档标题std::string _contant;//文档内容std::string _url;//文档urluint64_t  _id;//文档id};//倒排索引struct InvertedElem{std::string _word;//关键字uint64_t _id;//idint _weight;//权重InvertedElem():_weight(0){}};//倒排拉链typedef std::vector<InvertedElem> InvertedList;

建立索引类:

 class index{private:index(){};index(const index&)=delete;index operator=(const index&)=delete;public:static index* GetIndex()//初始化单例模式,在这里我们使用懒汉模式来建立索引{if(nullptr==instance){mtx.lock();//多线程问题if(nullptr==instance){instance=new index();}mtx.nulock();}return instance;}public://正排索引DocInfo *GetForwardIndex(uint64_t _id)//根据文档id找文档内容{if(_id>=_forward_list.size())//数组下标从0开始{std::cerr << "doc_id out range, error!" << std::endl;return nullptr;}return &_forward_list[_id];}//根据关键字,获得文档倒排拉链,倒排索引InvertedList *GetInvertedList(const std::string &word){auto iter = _inverted_index.find(word);//寻找关键字if(iter==_inverted_index.end())//没找到{std::cerr << "_inverted_index out range, error!" << 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<<" open file error"<<std::endl;}std::string line;int count=0;while (std::getline(in, line))//读取内容{DocInfo *doc = BuildForwardIndex(line);if (nullptr == doc){std::cerr << "build " << line << " error" << std::endl; // for deubgcontinue;}BuildInvertedIndex(*doc);count++;}return true;}private:DocInfo* BuildForwardIndex(const std::string &line){//进行字符串切分std::vector<std::string> results;const std::string sep = "\3";   //行内分隔符ns_util::StringUtil::Split(line, &results, sep);//分词if(results.size()!=3){std::cout<<"split error"std::endl;return nullptr;}//2. 字符串进行填充到DocIinfoDocInfo doc;doc._title = results[0];//titledoc._content = results[1];// contentdoc._url = results[2];/// urldoc._id = forward_index.size(); // 先进行保存id,在插入,对应的id就是当前doc在vector中的下标!// 3. 插入到正排索引的vector_forward_index.push_back(std::move(doc)); // doc,html文件内容return &forward_index.back();}bool BuildInvertedIndex(const DocInfo &doc){// DocInfo{title, content, url, doc_id}// word -> 倒排拉链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(auto e:title_words){boost::to_lower(s);      // 需要统一转化成为小写word_map[s].title_cnt++; // 如果存在就获取,如果不存在就新建}//内容分词std::vector<std::string> contant_words;ns_util::JiebaUtil::CutString(doc._contant, &contant_words);//分词for (std::string s : content_words){boost::to_lower(s);word_map[s].content_cnt++;}//权重计算#define X 10#define Y 1for(auto e:word_map){InvertedElem item;item._id = doc._id;item.word = e.first;item.weight = X * e.second.title_cnt + Y * e.second.content_cnt; // 相关性InvertedList &inverted_list = inverted_index[e.first];_inverted_list.push_back(std::move(item));}}private:std::vector<DocInfo> _forward_list;//正排//倒排索引一定是一个关键字和一组(个)InvertedElem对应[关键字和倒排拉链的映射关系]std::unordered_map<std::string, InvertedList> _inverted_index;static Index* instance;static std::mutex mtx;};index* index::instance = nullptr;std::mutex index::mtx;

 在创建索引的过程中,我们需要对我们搜索的关键字进行分词,而分词需要我们使用cppjieba分词工具,通过使用分词工具来进行分词(当然,有能力的同学可以自己实现一个分词程序)。

由于我们需要使用cppjieba分词工具,我们就需要下载

cppjiaba下载地址:git clone https://gitcode.com/yanyiwu/cppjieba-server.git

下载完成之后,我们只需要使用 这个目录下的文件即可:cppjieba/include/cppjieba

使用细节:使用cppjieba需要注意,我们需要自己执行:cd cppjieba; cp -rf deps/limonp include/cppjieba/, 不然会编译报错。

我们可以建立软链接来使用cppjieba库。

对于cppjieba的使用来说,我们可以到这个路径下查看使用情况:cppjieba/test.

对于使用cppjieba来说,在这个项目中这需要掌握CutString()这个函数的使用,其他的不用我们掌握太多,当然如果你想要学习的更多,可以学习其他函数的使用。 

切分函数的实现

#pragma once 
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <mutex>
#include <unordered_map>
#include <boost/algorithm/string.hpp>
#include "cppjieba/Jieba.hpp"namespace ns_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())//c++中文件处理函数{std::cerr <<"open in error2"<<std::endl;return false;}std::string line;while(std::getline(in,line))//将文件内容写入到(string)out中{*out += line;//std::cout<<line<<std::endl;}in.close();//关闭文件!!!return true;}};class StringUtil//切割字符串{public:static void Split(const std::string &target, std::vector<std::string> *out, const std::string &sep){//boost splitboost::split(*out, target, boost::is_any_of(sep), boost::token_compress_on);}};const char* const DICT_PATH = "/home/SSS/cppjieba/dict/jieba.dict.utf8";const char* const HMM_PATH = "/home/SSS/cppjieba/dict/hmm_model.utf8";const char* const USER_DICT_PATH = "/home/SSS/cppjieba/dict/user.dict.utf8";const char* const IDF_PATH = "/home/SSS/cppjieba/dict/idf.utf8";const char* const STOP_WORD_PATH = "/home/SSS/cppjieba/dict/stop_words.utf8";class JiebaUtil{private:static cppjieba::Jieba _jieba;//不去暂停词private://去暂停词//cppjieba::Jieba _jieba;//创建对象//std::unordered_map<std::string, bool> _stop_words;//哈希映射//static JiebaUtil* _instance;private://JiebaUtil():_jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH) {}//JiebaUtil(const JiebaUtil &) = delete;//ns_util::JiebaUtil operaror=(const JiebaUtil& x)=delete;public://去暂停词// static JiebaUtil* GetInstance()//初始化// {//     static std::mutex _mtx;//     if(nullptr==_instance)//     {//         _mtx.lock();//         if(nullptr==_instance)//         {//             _instance=new JiebaUtil();//             _instance->InitJiebaUtil();//加载文件//         }//         _mtx.unlock();//     }//     return _instance;//     //return nullptr;// }// void InitJiebaUtil()// {//     std::ifstream in(STOP_WORD_PATH);//     if(!in.is_open()) //     {//         std::cerr<<"open file error"<<std::endl;//         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())//         {//             iter = out->erase(iter);//去暂停词//         }//         else iter++;//     }// }public:static void CutString(const std::string &src, std::vector<std::string> *out){//去暂停词,如果云服务器配置高的情况下可以使用,性能不高可能导致出现不必要的错误//ns_util::JiebaUtil::GetInstance()->CutStringHelper(src, out);//低配版,不去暂停词_jieba.CutForSearch(src, *out);}};//不去暂停词cppjieba::Jieba JiebaUtil::_jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH);//去暂停词//JiebaUtil* _instance=nullptr;
}

 由于我们使用的是cppjieba分词工具,所以我们需要在使用的时建立分词路径,防止出现错误,在建立cppjieba类的时候,我们也是创建的单例模式,只需要建立一个对象即可,不需要创建多个对象,这和建立索引时候的功能是一样的。

对于切分字符来说,也就是CutStringHelper()函数来说,我们有两种方法实现它,可以直接调用jieba分词工具直接使用,生成关键字分词,这样做的情况下可能会生成较多的暂停词,搜索结果可能又不太准确的情况,所以我们可以去掉暂停词,这样可以减小关键词的数量,减小查找次数,二区掉暂停词依然需要调用Jieba分词库当中的函数。

对于去暂停词来说,如果服务器配置不高的情况下,不要去暂停词,可能程序运行直接报错!!

5.5建立搜索

索引建议好之后,我们进行搜索模块的建立,对于搜索模块来说,我们需要使用到的工具为Jsoncpp和cpp-httplib这两个工具,使用这两个工具我们先下载下来。

Jsoncpp下载方式:sudo yum install -y jsoncpp-devel

json的功能是实现序列化和反序列化,当然如果你可以使用其他序列化和反序列化的工具,你也可是使用和其他的,比如ProtoBuf,XML等。

cpp-httplib下载方式:GitHub - yhirose/cpp-httplib: A C++ header-only HTTP/HTTPS server and client library

注意:如果使用 centOS 环境,yum源带的 g++ 最新版本是4.8.5,发布于2015年,年代久远。编译该项目会出现异常。将 gcc/g++ 升级为更高版本可解决问题。

# 升级参考:https://juejin.cn/post/6844903873111392263
# 安装gcc 8版本
yum install -y devtoolset-8-gcc devtoolset-8-gcc-c++
# 启用版本
source /opt/rh/devtoolset-8/enable
# 查看版本已经变成gcc 8.3.1
gcc -v

# 启动: 细节,命令行启动只能在本会话有效

source /opt/rh/devtoolset-8/enable

#可选:如果想每次登陆的时候,都是较新的gcc

cat ~/.bash_profile

# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/.local/bin:$HOME/bin
export PATH
#每次启动的时候,都会执行这个命令

source /opt/rh/devtoolset-8/enable

httplib使用·:

测试代码:

#include "cpp-httplib/httplib.h"
int main()
{httplib::Server svr;svr.Get("/hi", [](const httplib::Request &req, httplib::Response &rsp){rsp.set_content("你好,世界!", "text/plain; charset=utf-8");});svr.listen("0.0.0.0", 8081);return 0;
}

搜索代码实现:

建立搜索代码之前,我们需要先建立索引,这样才可以快速查找目标。所以我们先实现一个类,包含我们所需要的内容,因为我们是通过关键字找文档id,再通过文档id寻找文档内容,在这期间我们需要通过权重,来找到不同文档的先后顺序,这样我们的类里面应该包含文档id,权重,关键字等。

 struct InvertedElemPrint{uint64_t _id;int _weight;std::vector<std::string> _words;InvertedElemPrint() : _id(0), _weight(0) {}};

对于建立搜索得类:

    class Searcher{private:ns_index::index *_index=nullptr; // 建立索引,进行查找public:Searcher() {}~Searcher() {}public:void InitSearch(const std::string &input){// 获取index对象index = ns_index::index::GetInstance();//根据index对象建立索引index->BulidIndex();}// query: 搜索关键字// json_string: 返回给用户浏览器的搜索结果void search(const std::string &query, std::string *json_string){std::vector<std::string> words;ns_util::JiebaUtil::CutString(query, &words);//2.[触发]:就是根据分词的各个"词",进行index查找,建立index是忽略大小写,所以搜索,关键字也需要//ns_index::InvertedList inverted_list_all; //内部InvertedElemstd::vector<InvertedElemPrint> inverted_list_all;std::unordered_map<uint64_t, InvertedElemPrint> tokens_map;for(auto word:words){boost::to_lower(word);ns_index::InvertedList *inverted_list = index->GetInvertedList(word);if(inverted==nullptr) continue;for(const auto &elem:*inverted_list){auto &item = tokens_map[elem._id]; //[]:如果存在直接获取,如果不存在新建// item一定是doc_id相同的print节点item._id = elem._id;item._weight += elem._weight;item.words.push_back(elem._words);}}for (const auto &item : tokens_map){inverted_list_all.push_back(std::move(item.second));}//3.[合并排序]:汇总查找结果,按照相关性(weight)降序排序std::sort(inverted_list_all.begin(), inverted_list_all.end(),[](const InvertedElemPrint &e1, const InvertedElemPrint &e2){return e1._weight > e2._weight;});//序列化和反序列化Json::Value root;for(auto &item : inverted_list_all){ns_index::DocInfo * doc = index->GetForwardIndex(item._id);if(doc==nullptr) continue;Json::Value elem;elem["title"]=doc->_title;elem["desc"] = GetDesc(doc->content, item.words[0]); //content是文档的去标签的结果,但是不是我们想要的,我们要的是一部分 TODOelem["url"]=doc->url;elem["id"] = (int)item._id;elem["weight"] = item._weight; // int->stringroot.append(elem);}Json::FastWriter writer;*json_string = writer.write(root);}//得到contantstd::string GetDesc(const std::string &html_content, const std::string &word){//找到word在html_content中的首次出现,然后往前找50字节(如果没有,从begin开始),往后找100字节(如果没有,到end就可以的)//截取出这部分内容const int prev_step=50;const int next_step=150;//找到关键字auto iter = std::search(html_content.begin(), html_content.end(), word.begin(), word.end(), [](int x, int y){ return (std::tolower(x) == std::tolower(y)); });if(iter==html.content.end()) return "NONE1";int pos = std::distance(html_content.begin(), iter);int start=0;int end=html_content.size()-1;if(pos > start + prev_step) start = pos - prev_step;if(pos < end - next_step) end = pos + next_step;if(start>end) return "NONE2";std::string desc = html_content.substr(start, end - start);desc += "...";return desc;}};
}

对于搜索类来讲。我们通过实现三个函数来解决,第一个函数为InitSearch(),实现初始化函数

完成获取对象和建立对象索引。

search()函数需要我们通过搜索关键字来返回给用户浏览器的搜索结果。这就需要通过序列化和反序列化来实现,通过查找到的内容,合并文档信息,按照权重来进行排序。

GetDesc()函数,通过对得到的信息进行打印,具体实现看代码细节。

至此,后端代码全部实现完成,开始实现前端代码,前端代码的实现需要html,css,js三种编程语言。

5.6 前端代码实现

html: 是网页的骨骼 -- 负责网页结构
css:网页的皮肉 -- 负责网页美观的
js(javascript):网页的灵魂---负责动态效果,和前后端交互

前端教程:w3school 在线教程

前端代码的编写

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><script src="http://code.jquery.com/jquery-2.1.1.min.js"></script><title>boost 搜索引擎</title><style>/* 去掉网页中的所有的默认内外边距,html的盒子模型 */* {/* 设置外边距 */margin: 0;/* 设置内边距 */padding: 0;}/* 将我们的body内的内容100%和html的呈现吻合 */html,body {height: 100%;}/* 类选择器.container */.container {/* 设置div的宽度 */width: 800px;/* 通过设置外边距达到居中对齐的目的 */margin: 0px auto;/* 设置外边距的上边距,保持元素和网页的上部距离 */margin-top: 15px;}/* 复合选择器,选中container 下的 search */.container .search {/* 宽度与父标签保持一致 */width: 100%;/* 高度设置为52px */height: 52px;}/* 先选中input标签, 直接设置标签的属性,先要选中, input:标签选择器*//* input在进行高度设置的时候,没有考虑边框的问题 */.container .search input {/* 设置left浮动 */float: left;width: 600px;height: 50px;/* 设置边框属性:边框的宽度,样式,颜色 */border: 1px solid black;/* 去掉input输入框的有边框 */border-right: none;/* 设置内边距,默认文字不要和左侧边框紧挨着 */padding-left: 10px;/* 设置input内部的字体的颜色和样式 */color: #CCC;font-size: 14px;}/* 先选中button标签, 直接设置标签的属性,先要选中, button:标签选择器*/.container .search button {/* 设置left浮动 */float: left;width: 150px;height: 52px;/* 设置button的背景颜色,#4e6ef2 */background-color: #4e6ef2;/* 设置button中的字体颜色 */color: #FFF;/* 设置字体的大小 */font-size: 19px;font-family:Georgia, 'Times New Roman', Times, serif;}.container .result {width: 100%;}.container .result .item {margin-top: 15px;}.container .result .item a {/* 设置为块级元素,单独站一行 */display: block;/* a标签的下划线去掉 */text-decoration: none;/* 设置a标签中的文字的字体大小 */font-size: 20px;/* 设置字体的颜色 */color: #4e6ef2;}.container .result .item a:hover {text-decoration: underline;}.container .result .item p {margin-top: 5px;font-size: 16px;font-family:'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;}.container .result .item i{/* 设置为块级元素,单独站一行 */display: block;/* 取消斜体风格 */font-style: normal;color: green;}</style>
</head>
<body><div class="container"><div class="search"><input type="text" value="请输入搜索关键字"><button onclick="Search()">搜索一下</button></div><div class="result"><!-- 动态生成网页内容 --><!-- <div class="item"><a href="#">这是标题</a><p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p><i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i></div><div class="item"><a href="#">这是标题</a><p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p><i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i></div><div class="item"><a href="#">这是标题</a><p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p><i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i></div><div class="item"><a href="#">这是标题</a><p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p><i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i></div><div class="item"><a href="#">这是标题</a><p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p><i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i></div> --></div></div><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){// 获取html中的result标签let result_lable = $(".container .result");// 清空历史搜索结果result_lable.empty();for( let elem of data){// console.log(elem.title);// console.log(elem.url);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.appendTo(result_lable);}}</script>
</body>
</html>

5.7 添加日志

#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <cstdarg>
#include <ctime>#define DEBUG 0
#define NORMAL 1
#define WARING 2
#define ERROR 3
#define FATAL 4
#define LOGFILE "./calculator.log"const char* gLevelMap[]={"DEBUG","NORMAL","WARING","ERROR","FATAL"};//完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名),可以将其写到文件中
void logMessage(int level,const char* format,...)
{//if(level==0) printf("正确");char stdBuff[10024];time_t timestamp = time(nullptr);//时间snprintf(stdBuff,sizeof(stdBuff),"[%s] [%ld] ", gLevelMap[level], timestamp);char logBuff[1024]; //自定义部分// va_list args;// va_start(args, format);// // vprintf(format, args);// vsnprintf(logBuffer, sizeof logBuffer, format, args);// va_end(args);snprintf(logBuff,sizeof(logBuff),"[%s] [%ld] ", gLevelMap[level], timestamp);FILE *fp = fopen(LOGFILE, "a");// printf("%s%s\n", stdBuffer, logBuffer);fprintf(fp, "%s %s\n", stdBuff, logBuff);fclose(fp);
}

日志分为5个等级,每个等级对应不同的内容,不同的等级需要不同的数字来对应。可以将其写到文件中将其保存。当然,对于不同的级别,日志可能会有不同的表现,具体情况视情况而定。

5.8 结尾

最后需要将写好的程序部署到linux服务器上,完成最后网站的建立。

nohup ./http_server > log/log.txt 2>&1 &[1] 26890

项目扩展方向

1. 建立整站搜索
2. 设计一个在线更新的方案,信号,爬虫,完成整个服务器的设计
3. 不使用组件,而是自己设计一下对应的各种方案(有时间,有精力)
4. 在我们的搜索引擎中,添加竞价排名(强烈推荐)
5. 热次统计,智能显示搜索关键词(字典树,优先级队列)(比较推荐)
6. 设置登陆注册,引入对mysql的使用(比较推荐的)

有兴趣的可以尝试做下。

项目完整代码网址:

C-C++项目: C/C++项目 - Gitee.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/255953.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

11.0 Zookeeper watcher 事件机制原理剖析

zookeeper 的 watcher 机制&#xff0c;可以分为四个过程&#xff1a; 客户端注册 watcher。服务端处理 watcher。服务端触发 watcher 事件。客户端回调 watcher。 其中客户端注册 watcher 有三种方式&#xff0c;调用客户端 API 可以分别通过 getData、exists、getChildren …

ELAdmin 前端启动

开发工具 官方指导的是使用WebStorm&#xff0c;但是本人后端开发一枚&#xff0c;最终还是继续使用了 idea&#xff0c;主打一个能用就行。 idea正式版激活方式&#xff1a; 访问这个查找可用链接&#xff1a;https://3.jetbra.in/进入任意一个能用的里面&#xff0c;顶部提…

kafka教程

Kafka 中&#xff0c;Producer采用push模型&#xff0c;而Consumer采用pull模型。 Topic Topic&#xff08;主题&#xff09;是消息的逻辑分类或通道。它是Kafka中用于组织和存储消息的基本单元。一个Topic可以被看作是一个消息发布的地方&#xff0c;生产者将消息发布到一个…

Axios设置token到请求头的三种方式

1、为什么要携带token? 用户登录时&#xff0c;后端会返回一个token&#xff0c;并且保存到浏览器的localstorage中&#xff0c;可以根据localstorage中的token判断用户是否登录&#xff0c;登录后才有权限访问相关的页面&#xff0c;所以当发送请求时&#xff0c;都要携带to…

ubuntu彻底卸载cuda 重新安装cuda

sudo apt-get --purge remove "*cublas*" "*cufft*" "*curand*" \"*cusolver*" "*cusparse*" "*npp*" "*nvjpeg*" "cuda*" "nsight*" cuda10以上 cd /usr/local/cuda-xx.x/bin/ s…

fast.ai 机器学习笔记(四)

机器学习 1&#xff1a;第 11 课 原文&#xff1a;medium.com/hiromi_suenaga/machine-learning-1-lesson-11-7564c3c18bbb 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 来自机器学习课程的个人笔记。随着我继续复习课程以“真正”理解它&#xff0c;这些笔记将继续…

idea自带的HttpClient使用

1. 全局变量配置 {"local":{"baseUrl": "http://localhost:9001/"},"test": {"baseUrl": "http://localhost:9002/"} }2. 登录并将结果设置到全局变量 PostMapping("/login")public JSONObject login(H…

华为机考入门python3--(9)牛客9-提取不重复的整数

分类&#xff1a;列表 知识点&#xff1a; 从右往左遍历每一个字符 my_str[::-1] 题目来自【牛客】 def reverse_unique(n): # 将输入的整数转换为字符串&#xff0c;这样可以从右向左遍历每一位 str_n str(n) # 创建一个空列表来保存不重复的数字 unique_digits []…

【C++】STL之string 超详解

目录 1.string概述 2.string使用 1.构造初始化 2.成员函数 1.迭代器 2.容量操作 1.size和length 返回字符串长度 2.resize 调整字符串大小 3.capacity 获得字符串容量 4.reserve 调整容量 5.clear 清除 6.empty 判空 3.string插入、追加 、拼接 1.运算…

16 亚稳态原理和解决方案

1. 亚稳态原理 亚稳态是指触发器无法在某个规定的时间段内到达一个可以确认的状态。在同步系统中&#xff0c;输入总是与时钟同步&#xff0c;因此寄存器的setup time和hold time是满足的&#xff0c;一般情况下是不会发生亚稳态情况的。在异步信号采集中&#xff0c;由于异步…

寒假9-蓝桥杯训练

//轨道炮 #include<iostream> using namespace std; #include<algorithm> int logs[100010]; int main() {int n;cin >> n;for (int i 1;i < n;i){cin >> logs[i];}sort(logs 1, logs n 1);int ans 1000000000;for (int i 2;i < n;i){if (…

【数学建模】【2024年】【第40届】【MCM/ICM】【F题 减少非法野生动物贸易】【解题思路】

一、题目 &#xff08;一&#xff09; 赛题原文 2024 ICM Problem F: Reducing Illegal Wildlife Trade Illegal wildlife trade negatively impacts our environment and threatens global biodiversity. It is estimated to involve up to 26.5 billion US dollars per y…

分布式系统架构介绍

1、为什么需要分布式架构&#xff1f; 增大系统容量&#xff1a;单台系统的性能瓶颈&#xff0c;多台机器才能应对大规模的应用场景&#xff0c;所以就需要我们的应用支撑平台具备分布式架构。 加强系统的可用&#xff1a;为了满足业务的SLA要求&#xff0c;需要通过分布式架构…

深度学习(13)--PyTorch搭建神经网络进行气温预测

一.搭建神经网络进行气温预测流程详解 1.1.导入所需的工具包 import numpy as np # 矩阵计算 import pandas as pd # 数据读取 import matplotlib.pyplot as plt # 画图处理 import torch # 构建神经网络 import torch.optim as optim # 设置优化器 1.2.读取并处理数据…

第五篇:MySQL常见数据类型

MySQL中的数据类型有很多&#xff0c;主要分为三类:数值类型、字符串类型、日期时间类型 三个表格都在此网盘中&#xff0c;需要者可移步自取&#xff0c;如果觉得有帮助希望点个赞~ MySQL常见数据类型表 数值类型 &#xff08;注&#xff1a;decimal类型举例&#xff0c;如1…

DevOps:CI、CD、CB、CT、CD

目录 一、软件开发流程演化快速回顾 &#xff08;一&#xff09;瀑布模型 &#xff08;二&#xff09;原型模型 &#xff08;三&#xff09;螺旋模型 &#xff08;四&#xff09;增量模型 &#xff08;五&#xff09;敏捷开发 &#xff08;六&#xff09;DevOps 二、走…

【开源】SpringBoot框架开发天沐瑜伽馆管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 瑜伽课程模块2.3 课程预约模块2.4 系统公告模块2.5 课程评价模块2.6 瑜伽器械模块 三、系统设计3.1 实体类设计3.1.1 瑜伽课程3.1.2 瑜伽课程预约3.1.3 系统公告3.1.4 瑜伽课程评价 3.2 数据库设计3.2.…

【Kubernetes】kubectl top pod 异常?

目录 前言一、表象二、解决方法1、导入镜像包2、编辑yaml文件3、解决问题 三、优化改造1.修改配置文件2.检查api-server服务是否正常3.测试验证 总结 前言 各位老铁大家好&#xff0c;好久不见&#xff0c;卑微涛目前从事kubernetes相关容器工作&#xff0c;感兴趣的小伙伴相互…

【Kubernetes】在k8s1.24及以上版本基于containerd容器运行时测试pod从harbor拉取镜像

基于containerd容器运行时测试pod从harbor拉取镜像 1、安装高版本containerd2、安装docker3、登录harbor上传镜像4、从harbor拉取镜像 1、安装高版本containerd 集群中各个节点都要操作 yum remove containerd.io -y yum install containerd.io-1.6.22* -y cd /etc/containe…

《UE5_C++多人TPS完整教程》学习笔记2 ——《P3 多人游戏概念(Multiplayer Concept)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P3 多人游戏概念&#xff08;Multiplayer Concept&#xff09;》 的学习笔记&#xff0c;该系列教学视频为 Udemy 课程 《Unreal Engine 5 C Multiplayer Shooter》 的中文字幕翻译版&#xff0c;UP主&#xff08;也是译…