【C++|Linux|计网】构建Boost站内搜索引擎的技能实践与探索 ...

打印 上一主题 下一主题

主题 963|帖子 963|积分 2889

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
目录
1、项目的相关配景
2.搜索引擎的相关宏观原理
3.搜索引擎技能栈和项目环境
4.正排索引vs倒排索引-搜索引擎具体原理
5.编写数据去标签与数据洗濯的模块 Parser
5.1.去标签
目的:
5.2.代码的团体框架:
EnumFile函数的实现:
EnumFile测试结果
如何提取网页的url呢?
测试解析网页title,content,url是否精确?
6.编写建立索引的模块index
6.1.index模块的根本框架:
6.2.建立正派索引:
split()函数具体使用阐明:
6.3.建立倒排索引:
7.编写搜索引擎模块searcher
7.1.根本代码框架:
7.2.建立摘要
问题:搜索结果出现重复文档的问题
7.3.修改后去重的代码:
7.4.测试:
8.编写 http_server 模块
9.简单的日记系统
10.前端代码
11.终极的测试结果(成品展示)
12.个人问题汇总:
问题一:
问题二:
问题三:
问题四:
问题五:
问题六:
问题七:

1、项目的相关配景



  • 公司:百度、搜狗、360搜索、头条新闻客户端,我们自己实现是不可能的!
  • 站内搜索:搜索的数据更垂直,数据量其实更小
  • boost的官网是没有站内搜索的,需要我们自己做⼀个

boost网站中是没有相关的搜索引擎的,我们自己实现一个!
   boost 官网: https://www.boost.org/
  我们使用最新的boost_1_86_0/doc/html⽬录下的html⽂件,⽤它来进⾏建⽴索引
  2.搜索引擎的相关宏观原理


3.搜索引擎技能栈和项目环境

技能栈:C/C++ C++11, STL, 标准库Boost,Jsoncpp,cppjieba,cpp-httplib ,
选学: html5,css,js、jQuery、Ajax(前端) 
项目环境: Ubuntu 9.4.0云服务器,vim/gcc(g++)/Makefile ,vs2022 or vscode
4.正排索引vs倒排索引-搜索引擎具体原理



  • 文档1:雷军买了四斤小米
  • 文档2:雷军发布了小米手机
正排索引:就是从⽂档ID找到⽂档内容(⽂档内的关键字)
目的文档进行分词(目的:方便建立倒排索引和查找):


  • 文档1[雷军买了四斤小米]: 雷军/买/四斤/小米/四斤小米
  • 文档2[雷军发布了小米手机]:雷军/发布/小米/小米手机
制止词:了,的,吗,a,the,一般我们在分词的时候可以不考虑
倒排索引:根据文档内容,分词,整理不重复的各个关键字,对应接洽到文档ID的方案。

模仿一次查找的过程:
用户输入:小米 ->倒排索引中查找->提取出文档ID(1,2)->根据正排索引->找到文档的内容 ->
title+conent(desc)+url 文档结果进行摘要->构建响应结果
倒排->正排->文档摘要
5.编写数据去标签与数据洗濯的模块 Parser

5.1.去标签

我们首先需要将boost网站里的站内资源进行下载,并压缩到我们的项目当中,作为初始数据进行生存
什么是标签?
<> : html的标签,这个标签对我们进⾏搜索是没有价值的,需要去掉这些标签,⼀般标签都是成 对出现的!
为什么要去标签?我们任意打开一个压缩好的网页资源,他是如许的:

大部门内容其实都是标签,对我们进行搜索是没有用的,以是我们要进行去标签。
目的:

把每个文档都去标签,然后写入到同一个文件中!每个文档内容不需要任何\n!文档和文档之间用\3 区分
parser文件的编写
5.2.代码的团体框架:

  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. #include<boost/filesystem.hpp>
  5. #include"util.hpp"
  6. const std::string src_path = "data/input/";        //这⾥放的是原始的html⽂档
  7. const std::string output = "data/raw_html/raw.txt";//这是放的是去标签之后的⼲净⽂档
  8. typedef struct DocInfo
  9. {
  10.     std::string title;  // 文档的标题
  11.     std::string contnt; // 文档内容
  12.     std::string url;    // 该文档在官网中的url
  13. } DocInfo_t;
  14. // const &: 输⼊
  15. //*: 输出
  16. //&:输⼊输出
  17. bool EnumFile(const std::string &src_path, std::vector<std::string>*files_list);
  18. bool ParseHtml(const std::vector<std::string> &files_list,std::vector<DocInfo_t> *results);
  19. bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output);
  20. int main()
  21. {
  22.     std::vector<std::string> files_list;
  23.     // 第⼀步: 递归式的把每个html⽂件名带路径,保存到files_list中,⽅便后期进⾏⼀个⼀个的⽂件进⾏读取
  24.     if (!EnumFile(src_path, &files_list))
  25.     {
  26.         std::cerr << "enum file name error!" << std::endl;
  27.         return 1;
  28.     }
  29.     // 第⼆步: 按照files_list读取每个⽂件的内容,并进⾏解析
  30.     std::vector<DocInfo_t> results;
  31.     if (!ParseHtml(files_list, &results))
  32.     {
  33.         std::cerr << "parse html error" << std::endl;
  34.         return 2;
  35.     }
  36.     // 第三步: 把解析完毕的各个⽂件内容,写⼊到output,按照\3作为每个⽂档的分割符
  37.     if (!SaveHtml(results, output))
  38.     {
  39.         std::cerr << "sava html error" << std::endl;
  40.         return 3;
  41.     }
  42.     return 0;
  43. }
复制代码
EnumFile函数的实现:

  1. bool EnumFile(const std::string &src_path, std::vector<std::string>*files_list)
  2. {
  3.     namespace fs = boost::filesystem;
  4.     fs::path root_path(src_path);
  5.     //判断路径是否存在,不存在,就没有必要往后走了
  6.     if(!fs::exists(root_path))
  7.     {
  8.         std::cerr << src_path << "not exists" << std::endl;
  9.         return false;
  10.     }
  11.     //定义一个空的迭代器,用来判断递归结束
  12.     fs::recursive_directory_iterator end;
  13.     for(fs::recursive_directory_iterator iter(root_path); iter != end; iter++)
  14.     {
  15.         //判断文件是否是普通文件,html都是普通文件
  16.         if(!fs::is_regular_file(*iter))
  17.         {
  18.             continue;
  19.         }
  20.         if(iter->path().extension() != ".html")//判断文件名的后缀是否符合要求
  21.         {
  22.             continue;
  23.         }
  24.         //std::cout << "debug: " << iter->path().string() << std::endl;
  25.         //当前的路径一定是一个合法的,以.html结束的普通网页文件
  26.         files_list->push_back(iter->path().string());//将所有带路径的html保存在file_list,方便后续进行文本分析
  27.     }
  28.     return true;
  29. }
复制代码
EnumFile测试结果

如下,可以把全部的.html网页输出出来了

我们提取网页中的title和content都比较简单。
提取title是直接在网页内容中查找<title>,然后进行字符串的截取即可。
  1. bool ParseTitle(const std::string &file, std::string *title)
  2. {
  3.     std::size_t begin = file.find("<title>");
  4.     if(begin == std::string::npos)
  5.     {
  6.         return false;
  7.     }
  8.     std::size_t end = file.find("</title>");
  9.     if(end == std::string::npos)
  10.     {
  11.         return false;
  12.     }
  13.     begin += std::string("<title>").size();
  14.     if(begin > end)
  15.     {
  16.         return false;
  17.     }
  18.     *title = file.substr(begin, end - begin);
  19.     return true;
  20. }
复制代码
提取content就是一个去标签的过程,我们这里采用的是基于简单的状态机进行去标签。
  1. bool ParseContent(const std::string &file, std::string *content)
  2. {
  3.     //去标签,基于一个简易的状态机
  4.     enum status
  5.     {
  6.         LABLE,
  7.         CONTENT
  8.     };
  9.     enum status s = LABLE;
  10.     for(char c : file)
  11.     {
  12.         switch(s)
  13.         {
  14.             case LABLE:
  15.                 if(c == '>') s = CONTENT;
  16.                 break;
  17.             case CONTENT:
  18.                 if(c == '<') s = LABLE;
  19.                 else
  20.                 {
  21.                     //我们不想保留原始文件中的\n,因为我们想用\n作为html解析之后文本的分隔符
  22.                     if(c == '\n') c = ' ';
  23.                     content->push_back(c);
  24.                 }
  25.                 break;
  26.             default:
  27.                 break;
  28.         }
  29.     }
  30.     return true;
  31. }
复制代码
如何提取网页的url呢?

   boost库的官方文档,和我们下载下来的文档,是有路径的对应关系的
官网URL样例:
https://www.boost.org/doc/libs/1_86_0/doc/html/accumulators.html
我们下载下来的url样例:boost/1_86_0/doc/html/accumulators.html
我们拷贝到我们项目中的样例:data/input/accumulators.html //我们把下载下来的boost库doc/html/* copy data/input/
url head ="https://www.boost.org/doc/libs/1_86_0/doc/html";
  url tail = [data/input](删除)/accumulators.html -> url tail =/accumulators.html
url = url_head + url_tail ;相当于形成了一个官网链接!
  1. bool ParseUrl(const std::string &file_path ,std::string *url)
  2. {
  3.     std::string url_head = "https://www.boost.org/doc/libs/1_86_0/doc/html/";
  4.     std::string url_tail = file_path.substr(src_path.size());
  5.     *url = url_head + url_tail;
  6.     return true;
  7. }
复制代码
将解析内容写入文件中:
  1. bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output)
  2. {
  3. #define SEP '\3'
  4.     //按照二进制方式进行写入
  5.     std::ofstream out (output, std::ios::out | std::ios::binary);
  6.     if(!out.is_open())
  7.     {
  8.         std::cerr << "open " << output << " failed!" << std::endl;
  9.         return false;
  10.     }
  11.     //可以开始进行文件内容的写入了
  12.     for(auto &item : results)
  13.     {
  14.         std::string out_string;
  15.         out_string = item.title;
  16.         out_string += SEP;
  17.         out_string += item.contnt;
  18.         out_string += SEP;
  19.         out_string += item.url;
  20.         out_string += '\n';
  21.         out.write(out_string.c_str(), out_string.size());
  22.     }
  23.     return true;
  24. }
复制代码
测试解析网页title,content,url是否精确?



   vim data/input/mpi/history.html
  在自己下载的文件里面进行验证,发现精确,没问题!

在网站中验证,也没问题!
最后将测试将结果内容添补到raw.txt
 

6.编写建立索引的模块index

6.1.index模块的根本框架:

  1. #pragma once
  2. #include <iostream>
  3. #include <string>
  4. #include <fstream>
  5. #include <vector>
  6. #include <mutex>
  7. #include <unordered_map>
  8. #include "util.hpp"
  9. namespace ns_index
  10. {
  11.     struct DocInfo
  12.     {
  13.         std::string title;   // 文档的标题
  14.         std::string content; // 文档对应的去标签之后的内容
  15.         std::string url;     // 官网文档url
  16.         uint64_t doc_id;     // 文档的ID,暂时先不做过多理解
  17.     };
  18.     struct InvertedElem
  19.     {
  20.         uint64_t doc_id;
  21.         std::string word;
  22.         int weight; // 权重
  23.     };
  24.     // 倒排拉链
  25.     typedef std::vector<InvertedElem> InvertedList;
  26.     class Index
  27.     {
  28.     private:
  29.         // 正排索引的数据结构用数组,数组的下标天然是文档的ID
  30.         std::vector<DocInfo> forward_index; // 正排索引
  31.         // 倒排索引一定是一个关键字和一组(个)InvertedElem对应[关键字和倒排拉链的映射关系]
  32.         std::unordered_map<std::string, InvertedList> inverted_index; // 倒排索引
  33.     private:
  34.         Index() {} // 但是一定要有函数体,不能delete
  35.         Index(const Index &) = delete;
  36.         Index &operator=(const Index &) = delete;
  37.         static Index *instance;
  38.         static std::mutex mtx;
  39.     public:
  40.         ~Index() {}
  41.     public:
  42.         static Index *GetInstance()
  43.         {
  44.             if (nullptr == instance)
  45.             {
  46.                 mtx.lock();
  47.                 if (nullptr == instance)
  48.                 {
  49.                     instance = new Index();
  50.                 }
  51.                 mtx.unlock();
  52.             }
  53.             return instance;
  54.         }
  55.     public:
  56.         // 根据doc_id找到文档内容
  57.         DocInfo *GetForwardIndex(uint64_t doc_id)
  58.         {
  59.             if (doc_id >= forward_index.size())
  60.             {
  61.                 std::cerr << "doc_id out range, error!" << std::endl;
  62.                 return nullptr;
  63.             }
  64.             return &forward_index[doc_id];
  65.         }
  66.         // 根据关键字string,获得倒排拉链
  67.         InvertedList *GetInvertedList(const std::string &word)
  68.         {
  69.             auto iter = inverted_index.find(word);
  70.             if (iter == inverted_index.end())
  71.             {
  72.                 std::cerr << word << " have no InvertedList" << std::endl;
  73.                 return nullptr;
  74.             }
  75.             return &(iter->second);
  76.         }
  77.         // 根据去标签,格式化之后的文档,构建正排和倒排索引
  78.         bool BuildIndex(const std::string &input) // parse处理完毕的数据交给我
  79.         {
  80.             std::ifstream in(input, std::ios::in | std::ios::binary);
  81.             if (!in.is_open())
  82.             {
  83.                 std::cerr << "sorry, " << input << " open error" << std::endl;
  84.                 return false;
  85.             }
  86.             std::string line;
  87.             int count = 0;
  88.             while (std::getline(in, line))
  89.             {
  90.                 DocInfo *doc = BuildForwardIndex(line);
  91.                 if (nullptr == doc)
  92.                 {
  93.                     std::cerr << "build " << line << " error" << std::endl; // for deubg
  94.                     continue;
  95.                 }
  96.                 BuildInvertedIndex(*doc);
  97.                 count++;
  98.                 if(count % 50 == 0){
  99.                 std::cout <<"当前已经建立的索引文档: " << count <<std::endl;
  100.                 // LOG(NORMAL, "当前的已经建立的索引文档: " + std::to_string(count));
  101.                 }
  102.             }
  103.             return true;
  104.         }
复制代码
6.2.建立正派索引:

  1. DocInfo *BuildForwardIndex(const std::string &line)
  2.         {
  3.             // 1. 解析line,字符串切分
  4.             // line -> 3 string, title, content, url
  5.             std::vector<std::string> results;
  6.             const std::string sep = "\3"; // 行内分隔符
  7.             ns_util::StringUtil::Split(line, &results, sep);
  8.             // ns_util::StringUtil::CutString(line, &results, sep);
  9.             if (results.size() != 3)
  10.             {
  11.                 return nullptr;
  12.             }
  13.             // 2. 字符串进行填充到DocIinfo
  14.             DocInfo doc;
  15.             doc.title = results[0];            // title
  16.             doc.content = results[1];          // content
  17.             doc.url = results[2];              /// url
  18.             doc.doc_id = forward_index.size(); // 先进行保存id,在插入,对应的id就是当前doc在vector中的下标!
  19.             // 3. 插入到正排索引的vector
  20.             forward_index.push_back(std::move(doc)); // doc,html文件内容
  21.             return &forward_index.back();
  22.         }
复制代码
这里正排索引在切分字符串的时候,我采用了boost库中的split函数
  1.     class StringUtil
  2.     {
  3.     public:
  4.         static void Split(const std::string &target, std::vector<std::string> *out, const std::string &sep)
  5.         {
  6.             // boost split
  7.             boost::split(*out, target, boost::is_any_of(sep), boost::token_compress_on);
  8.         }
  9.     };
复制代码
split()函数具体使用阐明:

boost 库中split函数用来字符串的切割
引用的头文件 <boost/algorithm/string.hpp>
boost::split()函数用于切割string字符串,将切割之后的字符串放到一个std::vector<std::string> 之中;
有4个参数:
以boost::split(type, select_list, boost::is_any_of(","), boost::token_compress_on);
(1)、type范例是std::vector<std::string>,用于存放切割之后的字符串
(2)、select_list:传入的字符串,可以为空。
(3)、boost::is_any_of(","):设定切割符为,(逗号)
(4)、 boost::token_compress_on:将一连多个分隔符默认为压缩一个!默认没有打开,当用的时候一般是要打开的。
测试代码:

最后输出就是三部门,没有空格!
6.3.建立倒排索引:

需要对 title && content都要先分词 --使⽤jieba分词,而且搜索内容不区分巨细写,同一变成小写。
 使用jieba的时候有一个坑,需要我们手动将limonp这个头文件拷贝到include头文件当中,不然编译会报错!


  1.         bool BuildInvertedIndex(const DocInfo &doc)
  2.         {
  3.             // DocInfo{title, content, url, doc_id}
  4.             // word -> 倒排拉链
  5.             struct word_cnt
  6.             {
  7.                 int title_cnt;
  8.                 int content_cnt;
  9.                 word_cnt() : title_cnt(0), content_cnt(0) {}
  10.             };
  11.             std::unordered_map<std::string, word_cnt> word_map; // 用来暂存词频的映射表
  12.             // 对标题进行分词
  13.             std::vector<std::string> title_words;
  14.             ns_util::JiebaUtil::CutString(doc.title, &title_words);
  15.             // if(doc.doc_id == 1572){
  16.             //     for(auto &s : title_words){
  17.             //         std::cout << "title: " << s << std::endl;
  18.             //     }
  19.             // }
  20.             // 对标题进行词频统计
  21.             for (std::string s : title_words)
  22.             {
  23.                 boost::to_lower(s);      // 需要统一转化成为小写
  24.                 word_map[s].title_cnt++; // 如果存在就获取,如果不存在就新建
  25.             }
  26.             // 对文档内容进行分词
  27.             std::vector<std::string> content_words;
  28.             ns_util::JiebaUtil::CutString(doc.content, &content_words);
  29.             // if(doc.doc_id == 1572){
  30.             //     for(auto &s : content_words){
  31.             //         std::cout << "content: " << s << std::endl;
  32.             //     }
  33.             // }
  34.             // 对内容进行词频统计
  35.             for (std::string s : content_words)
  36.             {
  37.                 boost::to_lower(s);
  38.                 word_map[s].content_cnt++;
  39.             }
  40. #define X 10
  41. #define Y 1
  42.             // Hello,hello,HELLO
  43.             for (auto &word_pair : word_map)
  44.             {
  45.                 InvertedElem item;
  46.                 item.doc_id = doc.doc_id;
  47.                 item.word = word_pair.first;
  48.                 item.weight = X * word_pair.second.title_cnt + Y * word_pair.second.content_cnt; // 相关性
  49.                 InvertedList &inverted_list = inverted_index[word_pair.first];
  50.                 inverted_list.push_back(std::move(item));
  51.             }
  52.             return true;
  53.         }
  54.     };
复制代码
7.编写搜索引擎模块searcher

7.1.根本代码框架:

  1. #include "index.hpp"
  2. namespace ns_searcher{
  3. class Searcher{
  4. private:
  5. ns_index::Index *index; //供系统进⾏查找的索引
  6. public:
  7. Searcher(){}
  8. ~Searcher(){}
  9. public:
  10. void InitSearcher(const std::string &input)
  11. {
  12. //1. 获取或者创建index对象
  13. //2. 根据index对象建⽴索引
  14. }
  15. //query: 搜索关键字
  16. //json_string: 返回给⽤⼾浏览器的搜索结果
  17. void Search(const std::string &query, std::string *json_string)
  18. {
  19. //1.[分词]:对我们的query进⾏按照searcher的要求进⾏分词
  20. //2.[触发]:就是根据分词的各个"词",进⾏index查找
  21. //3.[合并排序]:汇总查找结果,按照相关性(weight)降序排序
  22. //4.[构建]:根据查找出来的结果,构建json串 -- jsoncpp
  23. }
  24. };
  25. }
复制代码
7.2.建立摘要

为什么要建立摘要?
由于我们正常在搜索引擎搜到的内容,是不可能将网页的一整个内容表现给客户的,肯定要将网页的摘要返回给客户,相当于提炼出主旨,那我们怎么实现呢?
找到word在html_content中的首次出现,然后往前找50字节(如果没有,从begin开始),往后找100字节(如果没有,到end就可以的)

注意界说start和end双指针的时候,要注意size_t范例与int范例的符号比较,很容易出错!

  • 由于size_t是无符号范例,如果使用不妥(好比使用负数做运算),可能会导致意想不到的结果。例如,将负数赋值给size_t会导致它变成一个很大的正数。
代码:
  1. std::string GetDesc(const std::string &html_content, const std::string &word)
  2.             {
  3.                 //找到word在html_content中的首次出现,然后往前找50字节(如果没有,从begin开始),往后找100字节(如果没有,到end就可以的)
  4.                 //截取出这部分内容
  5.                 const int prev_step = 50;
  6.                 const int next_step = 100;
  7.                 //1. 找到首次出现
  8.                 //不能使用find查找,可能因为大小写不匹配而报错
  9.                 auto iter = std::search(html_content.begin(), html_content.end(), word.begin(), word.end(), [](int x, int y){
  10.                         return (std::tolower(x) == std::tolower(y));
  11.                         });
  12.                 if(iter == html_content.end()){
  13.                     return "None1";
  14.                 }
  15.                 int pos = std::distance(html_content.begin(), iter);
  16.                 //2. 获取start,end , std::size_t 无符号整数
  17.                 int start = 0;
  18.                 int end = html_content.size() - 1;
  19.                 //如果之前有50+字符,就更新开始位置
  20.                 if(pos > start + prev_step) start = pos - prev_step;
  21.                 if(pos < end - next_step) end = pos + next_step;
  22.                 //3. 截取子串,return
  23.                 if(start >= end) return "None2";
  24.                 std::string desc = html_content.substr(start, end - start);
  25.                 desc += "...";
  26.                 return desc;
  27.             }
复制代码
问题:搜索结果出现重复文档的问题

好比我们在搜索“你是一个好人”时,jieba会将该语句分解为你/一个/好人/一个好人,在建立图的时候,可能会指向同一个文档,导致我们在搜索的时候会出现重复的结果。
征象:

我们将一个boost库中的文档修改内容为“你是一个好人”,我们在搜索你是一个好人的时候就会出现重复结果:

以是我们要做去重操作,如何判断相同呢?直接看文档id即可。而且要将权值修改,我们应该将搜索到的相同内容进行权值的累加,作为该文档的真正权值!
去重之后的结果:

7.3.修改后去重的代码:

  1. #pragma once#include "index.hpp"#include "util.hpp"#include "log.hpp"#include <algorithm>#include <unordered_map>#include <jsoncpp/json/json.h>namespace ns_searcher{    struct InvertedElemPrint{        uint64_t doc_id;        int weight;        std::vector<std::string> words;        InvertedElemPrint():doc_id(0), weight(0){}    };    class Searcher    {        private:            ns_index::Index *index; //供系统进行查找的索引        public:            Searcher(){}            ~Searcher(){}        public:            void InitSearcher(const std::string &input)            {                //1. 获取大概创建index对象                index = ns_index::Index::GetInstance();                std::cout << "获取index单例成功..." << std::endl;                //LOG(NORMAL, "获取index单例成功...");                //2. 根据index对象建立索引                index->BuildIndex(input);                std::cout << "建立正排和倒排索引成功..." << std::endl;                //LOG(NORMAL, "建立正排和倒排索引成功...");            }            //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查找,建立index是忽略巨细写,以是搜索,关键字也需要                //ns_index::InvertedList inverted_list_all; //内部InvertedElem                std::vector<InvertedElemPrint> inverted_list_all;                std::unordered_map<uint64_t, InvertedElemPrint> tokens_map;                for(std::string word : words){                    boost::to_lower(word);                    ns_index::InvertedList *inverted_list = index->GetInvertedList(word);                    if(nullptr == inverted_list){                        continue;                    }                    //不完善的地方: 你/是/一个/好人 100                    //inverted_list_all.insert(inverted_list_all.end(), inverted_list->begin(), inverted_list->end());                    for(const auto &elem : *inverted_list){                        auto &item = tokens_map[elem.doc_id]; //[]:如果存在直接获取,如果不存在新建                        //item肯定是doc_id相同的print节点                        item.doc_id = elem.doc_id;                        item.weight += elem.weight;                        item.words.push_back(elem.word);                    }                }                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 ns_index::InvertedElem &e1, const ns_index::InvertedElem &e2){                //        return e1.weight > e2.weight;                //        });                  std::sort(inverted_list_all.begin(), inverted_list_all.end(),\                          [](const InvertedElemPrint &e1, const InvertedElemPrint &e2){                          return e1.weight > e2.weight;                          });                //4.[构建]:根据查找出来的结果,构建json串 -- jsoncpp --通过jsoncpp完成序列化&&反序列化                Json::Value root;                for(auto &item : inverted_list_all){                    ns_index::DocInfo * doc = index->GetForwardIndex(item.doc_id);                    if(nullptr == doc){                        continue;                    }                    Json::Value elem;                    elem["title"] = doc->title;                    elem["desc"] = GetDesc(doc->content, item.words[0]); //content是文档的去标签的结果,但是不是我们想要的,我们要的是一部门 TODO                    elem["url"]  = doc->url;                    //for deubg, for delete                    elem["id"] = (int)item.doc_id;                    elem["weight"] = item.weight; //int->string                    root.append(elem);                }                Json::StyledWriter writer;                //Json::FastWriter writer;                *json_string = writer.write(root);            }             std::string GetDesc(const std::string &html_content, const std::string &word)
  2.             {
  3.                 //找到word在html_content中的首次出现,然后往前找50字节(如果没有,从begin开始),往后找100字节(如果没有,到end就可以的)
  4.                 //截取出这部分内容
  5.                 const int prev_step = 50;
  6.                 const int next_step = 100;
  7.                 //1. 找到首次出现
  8.                 //不能使用find查找,可能因为大小写不匹配而报错
  9.                 auto iter = std::search(html_content.begin(), html_content.end(), word.begin(), word.end(), [](int x, int y){
  10.                         return (std::tolower(x) == std::tolower(y));
  11.                         });
  12.                 if(iter == html_content.end()){
  13.                     return "None1";
  14.                 }
  15.                 int pos = std::distance(html_content.begin(), iter);
  16.                 //2. 获取start,end , std::size_t 无符号整数
  17.                 int start = 0;
  18.                 int end = html_content.size() - 1;
  19.                 //如果之前有50+字符,就更新开始位置
  20.                 if(pos > start + prev_step) start = pos - prev_step;
  21.                 if(pos < end - next_step) end = pos + next_step;
  22.                 //3. 截取子串,return
  23.                 if(start >= end) return "None2";
  24.                 std::string desc = html_content.substr(start, end - start);
  25.                 desc += "...";
  26.                 return desc;
  27.             }            };}
复制代码
7.4.测试:

打出来的是不是按权值进行排序的呢?我们可以将weight打印出来看看

最大是16 ,最小是1,我们打开网站自己验证一下

这是16的,在文章内容中一共出现了16次,下面是1次的

一共出现1次精确!!!
8.编写 http_server 模块

我们这里不消自己去搭建轮子,直接用网上的cpp-httplib库即可搭建网络通信。
 httpserver的根本测试代码:
  1. #include"httplib.h"
  2. int main()
  3. {
  4.     httplib::Server svr;
  5.     svr.Get("/hi", [](const httplib::Request &req, httplib::Response &rsp){
  6.         rsp.set_content("你好,世界!", "text/plain; charset=utf-8");
  7.         });
  8.         svr.listen("0.0.0.0",8085);
  9.     return 0;
  10. }
复制代码

没问题!
以是我们只要会使用根本的接口即可
9.简单的日记系统

  1. #pragma once
  2. #include <iostream>
  3. #include <string>
  4. #include <ctime>
  5. #define NORMAL  1
  6. #define WARNING 2
  7. #define DEBUG   3
  8. #define FATAL   4
  9. #define LOG(LEVEL, MESSAGE) log(#LEVEL, MESSAGE, __FILE__, __LINE__)
  10. void log(std::string level, std::string message, std::string file, int line)
  11. {
  12.     std::cout << "[" << level << "]" << "[" << time(nullptr) << "]" << "[" << message << "]" << "[" << file << " : " << line << "]" << std::endl;
  13. }
复制代码
10.前端代码

由于我们的重点重要在于后端,以是前端的代码不讲解。
原码:
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7.     <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
  8.     <title>boost 搜索引擎</title>
  9.     <style>
  10.         /* 去掉网页中的所有的默认内外边距,html的盒子模型 */
  11.         * {
  12.             /* 设置外边距 */
  13.             margin: 0;
  14.             /* 设置内边距 */
  15.             padding: 0;
  16.         }
  17.         /* 将我们的body内的内容100%和html的呈现吻合 */
  18.         html,
  19.         body {
  20.             height: 100%;
  21.         }
  22.         /* 类选择器.container */
  23.         .container {
  24.             /* 设置div的宽度 */
  25.             width: 800px;
  26.             /* 通过设置外边距达到居中对齐的目的 */
  27.             margin: 0px auto;
  28.             /* 设置外边距的上边距,保持元素和网页的上部距离 */
  29.             margin-top: 15px;
  30.         }
  31.         /* 复合选择器,选中container 下的 search */
  32.         .container .search {
  33.             /* 宽度与父标签保持一致 */
  34.             width: 100%;
  35.             /* 高度设置为52px */
  36.             height: 52px;
  37.         }
  38.         /* 先选中input标签, 直接设置标签的属性,先要选中, input:标签选择器*/
  39.         /* input在进行高度设置的时候,没有考虑边框的问题 */
  40.         .container .search input {
  41.             /* 设置left浮动 */
  42.             float: left;
  43.             width: 600px;
  44.             height: 50px;
  45.             /* 设置边框属性:边框的宽度,样式,颜色 */
  46.             border: 1px solid black;
  47.             /* 去掉input输入框的有边框 */
  48.             border-right: none;
  49.             /* 设置内边距,默认文字不要和左侧边框紧挨着 */
  50.             padding-left: 10px;
  51.             /* 设置input内部的字体的颜色和样式 */
  52.             color: #CCC;
  53.             font-size: 14px;
  54.         }
  55.         /* 先选中button标签, 直接设置标签的属性,先要选中, button:标签选择器*/
  56.         .container .search button {
  57.             /* 设置left浮动 */
  58.             float: left;
  59.             width: 150px;
  60.             height: 52px;
  61.             /* 设置button的背景颜色,#4e6ef2 */
  62.             background-color: #4e6ef2;
  63.             /* 设置button中的字体颜色 */
  64.             color: #FFF;
  65.             /* 设置字体的大小 */
  66.             font-size: 19px;
  67.             font-family:Georgia, 'Times New Roman', Times, serif;
  68.         }
  69.         .container .result {
  70.             width: 100%;
  71.         }
  72.         .container .result .item {
  73.             margin-top: 15px;
  74.         }
  75.         .container .result .item a {
  76.             /* 设置为块级元素,单独站一行 */
  77.             display: block;
  78.             /* a标签的下划线去掉 */
  79.             text-decoration: none;
  80.             /* 设置a标签中的文字的字体大小 */
  81.             font-size: 20px;
  82.             /* 设置字体的颜色 */
  83.             color: #4e6ef2;
  84.         }
  85.         .container .result .item a:hover {
  86.             text-decoration: underline;
  87.         }
  88.         .container .result .item p {
  89.             margin-top: 5px;
  90.             font-size: 16px;
  91.             font-family:'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
  92.         }
  93.         .container .result .item i{
  94.             /* 设置为块级元素,单独站一行 */
  95.             display: block;
  96.             /* 取消斜体风格 */
  97.             font-style: normal;
  98.             color: green;
  99.         }
  100.     </style>
  101. </head>
  102. <body>
  103.     <div class="container">
  104.         <div class="search">
  105.             <input type="text" value="请输入搜索关键字">
  106.             <button onclick="Search()">搜索一下</button>
  107.         </div>
  108.         <div class="result">
  109.             <!-- 动态生成网页内容 -->
  110.             <!-- <div class="item">
  111.                 <a href="#">这是标题</a>
  112.                 <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p>
  113.                 <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
  114.             </div>
  115.             <div class="item">
  116.                 <a href="#">这是标题</a>
  117.                 <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p>
  118.                 <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
  119.             </div>
  120.             <div class="item">
  121.                 <a href="#">这是标题</a>
  122.                 <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p>
  123.                 <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
  124.             </div>
  125.             <div class="item">
  126.                 <a href="#">这是标题</a>
  127.                 <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p>
  128.                 <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
  129.             </div>
  130.             <div class="item">
  131.                 <a href="#">这是标题</a>
  132.                 <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p>
  133.                 <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
  134.             </div> -->
  135.         </div>
  136.     </div>
  137.     <script>
  138.         function Search(){
  139.             // 是浏览器的一个弹出框
  140.             // alert("hello js!");
  141.             // 1. 提取数据, $可以理解成就是JQuery的别称
  142.             let query = $(".container .search input").val();
  143.             console.log("query = " + query); //console是浏览器的对话框,可以用来进行查看js数据
  144.             //2. 发起http请求,ajax: 属于一个和后端进行数据交互的函数,JQuery中的
  145.             $.ajax({
  146.                 type: "GET",
  147.                 url: "/s?word=" + query,
  148.                 success: function(data){
  149.                     console.log(data);
  150.                     BuildHtml(data);
  151.                 }
  152.             });
  153.         }
  154.         function BuildHtml(data){
  155.             // 获取html中的result标签
  156.             let result_lable = $(".container .result");
  157.             // 清空历史搜索结果
  158.             result_lable.empty();
  159.             for( let elem of data){
  160.                 // console.log(elem.title);
  161.                 // console.log(elem.url);
  162.                 let a_lable = $("<a>", {
  163.                     text: elem.title,
  164.                     href: elem.url,
  165.                     // 跳转到新的页面
  166.                     target: "_blank"
  167.                 });
  168.                 let p_lable = $("<p>", {
  169.                     text: elem.desc
  170.                 });
  171.                 let i_lable = $("<i>", {
  172.                     text: elem.url
  173.                 });
  174.                 let div_lable = $("<div>", {
  175.                     class: "item"
  176.                 });
  177.                 a_lable.appendTo(div_lable);
  178.                 p_lable.appendTo(div_lable);
  179.                 i_lable.appendTo(div_lable);
  180.                 div_lable.appendTo(result_lable);
  181.             }
  182.         }
  183.     </script>
  184. </body>
  185. </html>
复制代码
11.终极的测试结果(成品展示)

首页:

当我们在搜索框搜索file system后,表现出来的搜索结果,由于之前已经验证其精确性,以是这里直接就展示了。


打开第一个网站,直接就跳转到boost库中相应的网页。

非常的完善!
12.个人问题汇总:

问题一:

首先就是项目本身存在的问题就是解决搜索结果出现重复文档的问题,在第7部门已经解决讲解完毕!
问题二:

单例模式加锁的时候,为什么是双判断
第一层判断是为了提效,后面的线程不需要再申请锁,进步效率;第二层判断是为了包管线程安全,包管了只创建1个对象



问题三:


如何使用lamdar表达式?
[](){}
记住方括号、圆括号、花括号
[capture-list] : 捕捉列表,该列表总是出如今lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。必写!
(parameters):参数列表。与平凡函数的参数列表同等,如果不需要参数传递,则可以连同()一起省略。
{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用全部捕获到的变量。必写,用来书写如何进行比较
问题四:


建立软链接的目的是什么?

  • 同一资源访问点:在同一的目录下创建软毗连,可以将分布在不同位置的资源会合管理,进步工作效率。例如,在开发过程中,可以将多个项目所需的共享库文件链接到同一的目录下,方便步调链接和使用。
  • 避免重复文件:通过软毗连,可以避免相同内容的多个副本,从而淘汰了存储空间的浪费。这在数据备份和迁徙过程中尤为重要,可以避免不须要的数据复制操作。

问题五:


index.hpp最后这两行到底有什么意义
静态成员变量在类界说中声明,并在类外部界说和初始化。
问题六:

Styledwriter和Fastwriter有什么区别


styledwriter输出有空行,更加雅观,方便测试职员测试;而Fastwriter直接正常输出
问题七:


这里范例肯定要多注意,不能使用size_t是无符号整型,会报错

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

雁过留声

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表