Boost搜索引擎

目录

一·功能和框架

二·技术栈与项目环境

三·具体代码结构

​编辑 四·项目背景

 五·项目宏观原理

 六·数据去标签模块

七·建立索引模块

建立正排索引

 建立倒排索引

倒排原理解析

 八·建立搜索模块

将index设置为单例模式

九·网络模块

十·前端模块


一·功能和框架

功能:实现boost文档站内搜索。通过输入关键字,将与关键字有关的网页按文档权值大小,罗列出来。前端显示包括,标题,摘要,网址~

框架:

Boost搜索引擎_第1张图片

二·技术栈与项目环境

 技术栈:

c/c++、c++11、STL、准标准库boost、Jsoncpp、cppjieba、cpp-httplib

项目环境:

Linux CentOS 7云服务器、vim/gcc(g++)/Makefile、vscode 

Boost搜索引擎_第2张图片 

三·具体代码结构

Boost搜索引擎_第3张图片 四·项目背景

        项目与日常搜索搜索网站不同,如百度,360,搜狗。此搜索引擎只支持boost库1.78版本站内搜索

boost库:

        Boost搜索引擎_第4张图片

 我们平时在网上搜索的内容都以三个内容返回给我们;标题,摘要,网页url

Boost搜索引擎_第5张图片

我们在数据清理阶段只需要对boost/html下的文件标题,摘要,url进行处理即可

 五·项目宏观原理

Boost搜索引擎_第6张图片

 六·数据去标签模块

我们下载到linux中的boost1.78文档中有好多东西是我们不需要的

Boost搜索引擎_第7张图片

 以上和划线的都是不需要的我们将这一些去掉并将这些文档内容按每个文档:

文档1:标题\3摘要\3/url\n

文档2:标题\3摘要\3/url\n

...

以此这样排列好放在一个特定目录下方便我们进行检索

在进行原始文件的处理时用到了boost库开发的工具就掠过吧~

去标签代码:parser.cc

//判断文件是否为html后缀文件,是的话就放在一个集合进行统一处理
bool EnumFile(const std::string &src_path, std::vector *files_list);
//解析文件将文件标题,目录,URl按照我们上面说的方式整理放在一个集合。
bool ParseHtml(const std::vector &files_list, std::vector *results);
//将处理好的html文件(已经处理好的去标签,数据清洗完的。统一放在output文件里。每个文件按\n分割)
bool SaveHtml(const std::vector &results, const std::string &output);
int main()
{
  std::vector files_list;
  //第一步: 递归式的把每个html文件名带路径,保存到files_list中,方便后期进行一个一个的文件进行读取
  if (!EnumFile(src_path, &files_list))
  {
    std::cerr << "enum file name error!" << std::endl;
    return 1;
  }
  // 第二步: 按照files_list读取每个文件的内容,并进行解析
  std::vector results;
  if (!ParseHtml(files_list, &results))
  {
    std::cerr << "parse html error" << std::endl;
    return 2;
  }

  // //第三步: 把解析完毕的各个文件内容,写入到output,按照\3作为每个文档的分割符
  if (!SaveHtml(results, output))
  {
    std::cerr << "sava html error" << std::endl;
    return 3;
  }

  return 0;
}

bool EnumFile(const std::string &src_path, std::vector *files_list)
{
  namespace fs = boost::filesystem;
  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++)
  {
    //判断文件是否是普通文件,html都是普通文件
    if (!fs::is_regular_file(*iter))
    {
      continue;
    }
    if (iter->path().extension() != ".html")
    { //判断文件路径名的后缀是否符合要求
      continue;
    }
    std::cout << "debug: " << iter->path().string() << std::endl;
    //当前的路径一定是一个合法的,以.html结束的普通网页文件
    files_list->push_back(iter->path().string()); //将所有带路径的html保存在files_list,方便后续进行文本分析
  }

  return true;
}

static bool ParseTitle(const std::string &file, std::string *title)
{
  std::size_t begin = file.find("");
  if (begin == std::string::npos)
  {
    return false;
  }
  std::size_t end = file.find("");
  if (end == std::string::npos)
  {
    return false;
  }

  begin += std::string("").size();

  if (begin > end)
  {
    return false;
  }
  *title = file.substr(begin, end - begin);
  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;
  return true;
}

// // for debug
static void ShowDoc(const DocInfo_t &doc)
{
  std::cout << "title: " << doc.title << std::endl;
  std::cout << "content: " << doc.content << std::endl;
  std::cout << "url: " << doc.url << std::endl;
}

bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo_t> *results)
{
  for (const std::string &file : files_list)
  {
    // 1. 读取文件,Read();
    std::string result;
    if (!ns_util::FileUtil::ReadFile(file, &result))
    {
      continue;
    }
    DocInfo_t doc;
    // 2. 解析指定的文件,提取title
    if (!ParseTitle(result, &doc.title))
    {
      continue;
    }
    // 3. 解析指定的文件,提取content,就是去标签
    if (!ParseContent(result, &doc.content))
    {
      continue;
    }
    // 4. 解析指定的文件路径,构建url
    if (!ParseUrl(file, &doc.url))
    {
      continue;
    }

    // done,一定是完成了解析任务,当前文档的相关结果都保存在了doc里面
    results->push_back(std::move(doc)); // bug:todo;细节,本质会发生拷贝,效率可能会比较低

    // for debug
    //  ShowDoc(doc);
    //   break;
  }
  return true;
}

bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output)
{
#define SEP '\3'
  //按照二进制方式进行写入
  std::ofstream out(output, std::ios::out | std::ios::binary);
  if (!out.is_open())
  {
    std::cerr << "open " << output << " failed!" << std::endl;
    return false;
  }

  //就可以进行文件内容的写入了
  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;
}
</code></pre> 
  </blockquote> 
  <h2 id="%E4%B8%83%C2%B7%E5%BB%BA%E7%AB%8B%E7%B4%A2%E5%BC%95%E6%A8%A1%E5%9D%97">七·建立索引模块</h2> 
  <blockquote> 
   <pre><code class="language-cpp">#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
namespace ns_index{
struct DocInfo{
std::string title; //文档的标题
std::string content; //文档对应的去标签之后的内容
std::string url; //官网文档url
uint64_t doc_id; //文档的ID,暂时先不做过多理解
};
struct InvertedElem{
uint64_t doc_id;
std::string word;
int weight;
};
//倒排拉链
typedef std::vector<InvertedElem> InvertedList;
class Index{
private:
//正排索引的数据结构用数组,数组的下标天然是文档的ID
std::vector<DocInfo> forward_index; //正排索引
//倒排索引一定是一个关键字和一组(个)InvertedElem对应[关键字和倒排拉链的映射关系]
std::unordered_map<std::string, InvertedList> inverted_index;
public:
Index(){}
~Index(){}
public:
//根据doc_id找到找到文档内容
DocInfo *GetForwardIndex(uint64_t doc_id)
{
return nullptr;
}
//根据关键字string,获得倒排拉链
InvertedList *GetInvertedList(const std::string &word)
{
return nullptr;
}
//根据去标签,格式化之后的文档,构建正排和倒排索引
//data/raw_html/raw.txt
bool BuildIndex(const std::string &input) //parse处理完毕的数据交给我
{
return true;
}
};
}</code></pre> 
  </blockquote> 
  <h3 id="%E5%BB%BA%E7%AB%8B%E6%AD%A3%E6%8E%92%E7%B4%A2%E5%BC%95">建立正排索引</h3> 
  <blockquote> 
   <p>我们怎么理解正派索引?</p> 
   <p>用户输入了搜索关键字。我们怎么去文档查找这个关键字?</p> 
   <p>将文档一一按编号穿起来放到一个数组里面。每个文件都标记自己是第几个文件</p> 
   <p>输入:<strong>我在郑州上学</strong></p> 
   <p>输入:<strong>我是一名学生</strong></p> 
   <table border="1" style="width:500px;"> 
    <tbody> 
     <tr> 
      <td>文档ID</td> 
      <td>关键字</td> 
     </tr> 
     <tr> 
      <td>1</td> 
      <td>我在郑州上学</td> 
     </tr> 
     <tr> 
      <td>2</td> 
      <td>我是一名学生</td> 
     </tr> 
    </tbody> 
   </table> 
   <p>我们之前将处理好的文档内容,都放在了一个结构体里面将这些结构体放在一个集合,那么它们的ID就是它们自己的下标</p> 
   <p><a href="http://img.e-com-net.com/image/info8/05c7cc9a19e6441cad95a67c3cfcdcad.jpg" target="_blank"><img alt="Boost搜索引擎_第8张图片" height="178" src="http://img.e-com-net.com/image/info8/05c7cc9a19e6441cad95a67c3cfcdcad.jpg" width="650" style="border:1px solid black;"></a></p> 
   <p><strong> 正排索引代码:</strong></p> 
   <pre><code class="language-cpp">DocInfo *BuildForwardIndex(const std::string &line)
        {
            // 1.解析line,字符串切分
            // line->3 string,title,content,url;
            const std::string sep = "\3";
            std::vector<std::string> results; //行内分割符
            ns_util::StringUtil::Split(line, &results, sep);
            // ns_util::StringUtil::CutString(line,&results,sep);
            if (results.size() != 3)
            {
                return nullptr;
            }
            // 2.字符串进行填充到Docinfo
            DocInfo doc;
            doc.title = results[0]; // title
            doc.content = results[1];
            doc.url = results[2];
            doc.doc_id = forward_index.size(); //先进行保存id,再插入,对应的id就是当前doc再vector中的下标!
            // 3.插入到正派索引的vector
            forward_index.push_back(std::move(doc));
            return &forward_index.back();
        }</code></pre> 
  </blockquote> 
  <h3 id="%C2%A0%E5%BB%BA%E7%AB%8B%E5%80%92%E6%8E%92%E7%B4%A2%E5%BC%95"> 建立倒排索引</h3> 
  <blockquote> 
   <p>什么是倒排索引呢?</p> 
   <p>可以先阐述一下正排索引的效率高么?</p> 
   <p>很明显单单使用正派索引完成搜索任务显然是不可行的</p> 
   <p>每次都通过文档ID一一去看这个文档里面有咩有出现关键字无疑大费周折</p> 
   <p>要是你搜索的内容在好几个文档中都出现过呢?</p> 
   <p>我们可以通过关键字查询这几个关键字都在哪几个文档中出现过。</p> 
   <p>(这里我们要对输入的关键字进行切分。使用的是split函数)</p> 
   <p>文档1:我在郑州上大学。我/在/郑州/上/大学</p> 
   <p>文档2:我是一名大学生。我/是/一名/大学/生</p> 
   <table border="1" style="width:500px;"> 
    <tbody> 
     <tr> 
      <td>关键字</td> 
      <td>文档ID</td> 
     </tr> 
     <tr> 
      <td>我</td> 
      <td>1   2</td> 
     </tr> 
     <tr> 
      <td>在</td> 
      <td>1</td> 
     </tr> 
     <tr> 
      <td>郑州</td> 
      <td>1</td> 
     </tr> 
     <tr> 
      <td>一名</td> 
      <td>2</td> 
     </tr> 
     <tr> 
      <td>大学</td> 
      <td>1    2</td> 
     </tr> 
    </tbody> 
   </table> 
   <p>搜索过程:输入-》大学-》倒排索引查找,找到文档ID-》根据正排索引-》找到文档内容-》</p> 
   <p>title+content+url-》构建响应结果。</p> 
   <h4 id="%E5%80%92%E6%8E%92%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90">倒排原理解析</h4> 
   <p>通过关键字去找文档的ID是大致意义上的理解,怎么实现这么一个功能呢?</p> 
   <p>找的文档应该都有一些什么属性呢?</p> 
   <p>用一个结构体来表示文档的id。weight(权值)。关键字</p> 
   <p>通过关键字对拟文档属性进行修改。主要是权值统一建立一个集合将它们放进去我们称之为倒排拉链。</p> 
   <p>一个关键字可能对应多个倒排拉链</p> 
   <pre><code class="language-cpp">//倒排原理
    struct InvertedElem
    {
        int doc_id;
        std::string word;
        int weight;
    };
    //倒排拉链
    typedef std::vector<InvertedElem> InvertedList;</code></pre> 
   <p><a href="http://img.e-com-net.com/image/info8/529fc6ea20474a4abb562179b6a903cb.jpg" target="_blank"><img alt="Boost搜索引擎_第9张图片" height="278" src="http://img.e-com-net.com/image/info8/529fc6ea20474a4abb562179b6a903cb.jpg" width="650" style="border:1px solid black;"></a></p> 
   <p> 这里建立倒排索引的时候需要对文档的标题啊,摘要等进行分词。这样让我们在建立倒排索引更精确这里使用的是jieba分词工具。放在util里面。</p> 
   <p>倒排查找模块</p> 
   <pre><code class="language-cpp"> bool BulidInvertedIndex(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 &s:title_words)
            {
                boost::to_lower(s);
                word_map[s].title_cnt++;
            }
            
            // if(doc.doc_id == 1572){
            //     for(auto &s : title_words){
            //         std::cout << "title: " << s << std::endl;
            //     }
            // }


            //对文档内容进行分词
            std::vector<std::string> content_words;
            ns_util::JiebaUtil::CutString(doc.content, &content_words);


            //对内容进行词频统计
            for (auto &s : content_words)
            {
                boost::to_lower(s);
                word_map[s].content_cnt++;
            }

#define X 10
#define Y 1
            // Hello,hello,HELLO
            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;
        }</code></pre> 
  </blockquote> 
  <h2 id="%C2%A0%E5%85%AB%C2%B7%E5%BB%BA%E7%AB%8B%E6%90%9C%E7%B4%A2%E6%A8%A1%E5%9D%97"> 八·建立搜索模块</h2> 
  <blockquote> 
   <p> </p> 
   <pre><code class="language-cpp">#pragma once
#include"index.hpp"

namespace ns_searcher
{
    class Searcher
    {
    private:
        ns_index::Index *index;//系统查找的索引

    public:
        Searcher(){}
        ~Searcher(){}

        //初始化
        void InitSearcher(std::string &input)
        {
            //1.获取/创建index对象
            //2.根据index对象建立索引
        }

        //提供搜索服务
        //query:搜索关键字;json_string:搜索结果
        void Search(const std::string &query,std::string *json_string)
        {
            //1.query分词
            //2.根据分“词”进行index查找
            //3.根据查找结果,根据权重进行降序排序
            //4.构建json_string
        }
    };
}</code></pre> 
   <h3 id="%E5%B0%86index%E8%AE%BE%E7%BD%AE%E4%B8%BA%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F">将index设置为单例模式</h3> 
   <pre><code class="language-cpp">  private:
    Index() {}
    Index(const Index &) = delete;
    Index &operator=(const Index &) = delete;

    static Index *instance;
    static std::mutex mtx;

  public:
    ~Index() {}

    static Index *GetInstance()
    {
      if (nullptr == instance)
      {
        mtx.lock();
        if (nullptr == instance)
          instance = new Index();
      }
      mtx.unlock();
      return instance;
    }
</code></pre> 
   <p>由于单例模式存在线程安全问题,我们对其进行加锁</p> 
   <p>用户输入的搜索内容我们也需要对其进行分词。然后去倒排索引中查找。这里使用jesoncpp对其进行序列化,再反序列化</p> 
   <p>jsoncpp</p> 
   <pre><code class="language-cpp">#include <iostream>
#include <string>
#include <vector>
#include <jsoncpp/json/json.h>

int main()
{
  Json::Value root;
  Json::Value item1;
  item1["key1"]="Value1";
  item1["key2"]="Value2";

  Json::Value item2;
  item2["key1"]="Value1";
  item2["key2"]="Value2";

  root.append(item1);
  root.append(item2);

  Json::StyledWriter writer;
  std::string s = writer.write(root);

  std::cout<<s<<std::endl;
  return 0;
}</code></pre> 
   <p>Search模块:</p> 
   <pre><code class="language-cpp">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查找
            ns_index::InvertedList inverted_list_all;
            for(std::string word:words)
            {
                boost::to_lower(word);
                ns_index::InvertedList*inverted_list=index->GetInvertedList(word);
                if(nullptr==inverted_list)
                {
                    continue;
                }
                inverted_list_all.insert(inverted_list_all.end(),inverted_list->begin(),inverted_list->end());
            }
            //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;
            });   
            //4.[构建]:根据查出来的结果,构建json串。
            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.word);
                elem["url"]=doc->url;
                elem["id"]=(int)item.doc_id;
                elem["weight"]=item.weight;
                root.append(elem);
            }
            Json::StyledWriter writer;
            *json_string=writer.write(root);
        }</code></pre> 
   <p>我们在建立好搜索模块之后而且已经触发了文档内容,这时候的文档摘要是需要进行处理的。不然不美观</p> 
   <pre><code class="language-cpp"> std::string GetDesc(const std::string&html_content,const std::string&word)
        {
            //找到word在html中的位置,返回摘要为关键字往前50个,往后100个,如果没有就从begin到end;
            const int prev_step=50;
            const int next_step=100;

            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));
            });
            int pos=std::distance(html_content.begin(),iter);
            int start=0;
            int end=html_content.size()-1;
            //如果之前有50+个字符,就更新开始位置
            if(pos-prev_step>start)
            {
                start=pos-prev_step;
            }
            if(pos+next_step<end)
            {
                end=pos+next_step;
            }
            //3截取字符串return
           std::string desc=html_content.substr(start,end-start);
           desc+="...";
           return desc;
        }</code></pre> 
  </blockquote> 
  <h2 id="%E4%B9%9D%C2%B7%E7%BD%91%E7%BB%9C%E6%A8%A1%E5%9D%97">九·网络模块</h2> 
  <blockquote> 
   <p>这里网络模块我们使用了cpp-httplib这样就不要再手动写一个http协议</p> 
   <p>安装cpp-httplib</p> 
   <pre><code class="language-cpp">cpp-httplib安装路径:
https://gitee.com/zhangkt1995/cpp-httplib?_from=gitee_search</code></pre> 
   <pre><code class="language-cpp">#include"search.hpp"
#include"cpp-httplib/httplib.h"
const std::string input="./data/rm_html/raw.txt";
const std::string root_path="./wwwroot";
int main()
{
    ns_searcher::Searcher search;
    search.InitSearcher(input);

    httplib::Server svr;
    svr.set_base_dir(root_path.c_str());
    // svr.Get("/hi",[](const httplib::Request &req,httplib::Response& rsp){
    //     rsp.set_content("hello,world!","text/plain: charset=utf-8");
    // });
    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");
       std::cout<<"用户正在搜索: "<<word<<std::endl;
       std::string json_string;
       search.Search(word,&json_string);
       rsp.set_content(json_string,"application/json"); 
    });
    svr.listen("0.0.0.0",8081);
    return 0;
}
</code></pre> 
   <p></p> 
  </blockquote> 
  <h2 id="%E5%8D%81%C2%B7%E5%89%8D%E7%AB%AF%E6%A8%A1%E5%9D%97">十·前端模块</h2> 
  <blockquote> 
   <p>前端模块就不解释</p> 
   <pre><code class="language-cpp"><!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 搜索引擎
    



    

你可能感兴趣的:(linux,搜索引擎)