BoostSearch搜索引擎

                                             BoostSearch搜索引擎_第1张图片

        今天讲的项目是基于C++的Boost库的站内搜索引擎。因为Boost库内没有搜索关键字功能,所以在这里我们来手动实现一个这样的搜索引擎。当用户在输入框输入要查询的关键字后,就会快速查询出相关的 boost 库中的文档,弥补 boost 在线文档没有搜索功能的缺陷。

目录

项目介绍

开发环境

项目流程

搜索引擎的相关宏观原理

项目代码

parser.cc

index.hpp

searcher.hpp

log.hpp

util.hpp

Makefile

debug.cc

http_server.cc

wwwroot/index.html

项目演示

界面

搜索测试


项目介绍

开发环境

CentOS7、vim、g++、Makefile、vscode

项目流程

1、编写数据去标签与数据清洗模块,将原 html 文档解析成一个行文本文件。

2、读取处理好的行文本进行分词,权重计算等操作,在内存中构建出正排索引和倒排索引。
3、对查询词进行分词、触发,依据查询权重值对结果对结果进行排序,并以 Json 格式序列化为字符串返回。

4、通过 HTTP 服务器搭建搜索页面,为外部提供服务。

搜索引擎的相关宏观原理

BoostSearch搜索引擎_第2张图片

        客户端把要搜索的关键字通过GET传参方式交给服务端的searcher模块,searcher根据提前建立好的索引,通过用户关键字查找到对应的内容,构建成网页返回给用户。

项目代码

parser.cc

#include 
#include 
#include 
#include 
#include 
#include "util.hpp"

//是一个目录,下面放的是源未处理的所有html网页
const std::string src_path = "data/input";
const std::string output = "data/raw_html/raw.txt"; //放处理后的网页内容

typedef struct DocInfo
{
    std::string title;
    std::string content;
    std::string url;
}DocInfo_t;

//const & 输入
//* 输出
//& 输入输出
bool EnumFile(const std::string& src_path, std::vector* file_list);
bool ParselfHtml(const std::vector& file_list, std::vector* results);
bool SaveHtml(const std::vector& results, const std::string& output);

int main()
{
    //第一步: 递归式的把每个html文件名带路径,保存到file_list中, 方便后期一个一个的文件读取
    std::vector files_list;
    if(!EnumFile(src_path, &files_list))
    {
        std::cerr << "enum file error!" << std::endl;
        return 1;
    }

    //第二步: 按照file_list读取的每个文件的内容, 并进行操作
    std::vector results;
    if(!ParselfHtml(files_list, &results))
    {
        std::cerr << "parsr html error!" << std::endl;
        return 2;
    }

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

    return 0;
}


//处理该路径下的所有文件, 一般的读文件操作处理这种大批的不适用,所以借用boos库中的
bool EnumFile(const std::string& src_path, std::vector* files_list)
{
    // bool exists(const path& p); //path特是一个命名空间中的类型
    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
        {
            continue;
        }
        if(iter->path().extension() != ".html") //判断文件路径名后缀是否符合要求
        {
            continue;
        }

        //当前带路径一定是一个合法的, 以.html结束的普通文件
        //将所有带路径的html保存在file_list, 方便后续进行文本分析
        files_list->push_back(iter->path().string());
    }
    return true;
}
static bool ParseTitle(const std::string file, std::string* title)
{
    size_t begin = file.find("");
    if(begin == std::string::npos)
    {
        return false;
    }

    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_82_0/doc/html";
    std::string url_tail = file_path.substr(src_path.size());
    *url = url_head + url_tail; //形成新的官网链接
    return true;
}
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 ParselfHtml(const std::vector<std::string>& files_list, std::vector<DocInfo_t>* results)
{
    for(const std::string& file : files_list)
    {
        // 1、读取文件
        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
        // html中自己有跳转,但是自己没有url,需要自己拼接
        if(!ParseUrl(file, &doc.url)) //file是当前文本路径内容
        {
            continue;
        }

        //完成了解析任务
        results->push_back(std::move(doc)); //move提高效率,move以后变成右值了->移动构造

        // 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> 
  <h3 id="index.hpp">index.hpp</h3> 
  <pre><code class="language-cpp">#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<unordered_map>
#include<mutex>
#include<fstream>
#include"log.hpp"
#include"util.hpp"

namespace ns_index
{
    struct DocInfo
    {
        std::string title; //文档标题
        std::string content; //文档对应的去标签之后的内容
        std::string url; //官网文档url
        int doc_id; //文档的ID, 暂时不做过多理解
    };
    struct InvertedElem
    {
        uint64_t doc_id; //文档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;
    private:
        Index() //设置成单例模式
        {}
        Index(const Index& ) = delete;
        Index& operator=(const Index&) = delete; //传过来的Index是否为const都会被禁止
        static Index* instance; //单例指针
        static std::mutex mtx;
    public:
        static Index* GetInstance() //对外提供获取单例对象方法
        {
            if(instance == nullptr)
            {
                mtx.lock();
                if(instance == nullptr)
                {
                    instance = new Index(); //返回创建好对象的地址
                }
                mtx.unlock();
            }
            return instance;
        }

        //根据doc_id找到文档内容
        DocInfo* GetForwardIndex(uint64_t doc_id)
        {
            if(doc_id >= forward_index.size())
            {
                std::cerr << "doc_id out range, error!" << std::endl;
                return nullptr;
            }
            return &forward_index[doc_id];
        }

        //根据关键字string, 获得倒排拉链
        InvertedList* GetInvertedList(const std::string& word)
        {
            //std::unordered_map<std::string, InvertedList>::iterator
            auto iter = inverted_index.find(word);
            if(iter == inverted_index.end())
            {
                std::cerr << word << " have no InvertedList" << std::endl;
                return nullptr;
            }
            return &(iter->second);
        }

        //根据去标签, 格式化之后的文档, 构建正排和倒排索引
        // data/raw_html/raw.txt
        bool BuildIndex(const std::string& input) //prase处理完毕的数据交给我
        {
            std::ifstream in(input, std::ios::in | std::ios::binary);
            if(!in.is_open())
            {
                std::cerr << "sorry, " << input << " open error" << std::endl;
                return false;
            }
            std::string line;
            int count = 0;
            while(std::getline(in, line))
            {
                DocInfo* doc = BuildForwardIndex(line);
                if(doc == nullptr)
                {
                    std::cerr << "build " << line << " error" << std::endl;
                    continue;
                }
                BuildInvertedIndex(*doc);
                count++;
                if(count % 50 == 0)
                {
                    //std::cout << "当前已经建立的索引文档: " << count << std::endl;
                    LOG(NORMAL, "当前已经建立的搜索文档: " + std::to_string(count));
                }
            }
            in.close();
            return true;
        }
    private:
        //编写正排索引
        DocInfo* BuildForwardIndex(const std::string& line)
        {
            // 1、解析line, 字符串切分
            //line -> 3 string title, content, url
            std::vector<std::string> results;
            std::string sep = "\3";
            ns_util::StringUtil::Split(line, &results, sep);
            if(results.size() != 3)
            {
                return nullptr;
            }

            //2、 字符串进行填充到DocInfo
            DocInfo doc;
            doc.title = results[0];
            doc.content = results[1];
            doc.url = results[2];
            //先进行保存id, 再插入, 对应的id就是当前doc在vector中的下标
            doc.doc_id = forward_index.size();

            //3、插入到正排索引的vector
            forward_index.push_back(std::move(doc)); //doc.html文件文件内容比较大, move可以提高效率
            return &forward_index.back(); //返回插入doc的地址, 因为是拷贝过去的, 不能返回&doc!!
        }

        //编写倒排索引
        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 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(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; //相关性
                //typedef std::vector<InvertedElem> InvertedList;
                //一个string对应一批关键字和倒排拉链的关系, 返回的是一个vector
                //每次不同的html文档遍历时, 每一个词对应插入进去: 
                // 后面的文档有相同的词时,就直接在数组中插入文档id;没有相同的词,就当第一次出现的词插入
                InvertedList& inverted_list = inverted_index[word_pair.first]; 
                inverted_list.push_back(item);
            }
            return true;
        }
    };
    Index* Index::instance = nullptr; //静态成员变量类外初始化
    std::mutex Index::mtx;
}

//统计词频时, 标题出现的词,在正文中也会匹配到的话,会被当做content中的词多被统计一次
//实际页面又写了一遍标题(有的有有的html没有),当做content</code></pre> 
  <h3 id="searcher.hpp">searcher.hpp</h3> 
  <pre><code class="language-cpp">#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<unordered_map>
#include<boost/algorithm/string.hpp>
#include<algorithm>
#include<jsoncpp/json/json.h>
#include"index.hpp"
#include"util.hpp"
#include"log.hpp"

namespace ns_searcher
{
    struct InvertedElemPrint
    {
        uint64_t doc_id;
        int weight;
        std::vector<std::string> words; //多个词对应一个文档id
        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差找
            //倒排拉链
            // typedef std::vector<InverterElem> InvertedList;
            // 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(inverted_list == nullptr)
                {
                    continue;
                }
                //不完美的地方: 文档ID会有重复
                //你/是/一个/好人/ 不同的词会对应各自的文档ID,这些词的ID就会重复显示
                //所以内容也会重复,在这里要进行去重
                //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; //保证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
            Json::Value root;
            for(auto& item : inverted_list_all)
            {
                ns_index::DocInfo* doc = index->GetForwardIndex(item.doc_id); //对应文档基本信息找到
                if(doc == nullptr)
                {
                    continue;
                }
                Json::Value elem;
                elem["title"] = doc->title;
                //item.word是搜索关键字
                //content是去标签的一部分,但是不是我们想要的(摘要)
                //elem["desc"] = GetDesc(doc->content, item.word); 

                //结合去重优化的更新
                elem["desc"] = GetDesc(doc->content, item.words[0]); //[0]肯定存在  根据分的第一个词获取摘要
                elem["url"] = doc->url;
                //for debug, for delete --- 用户不需要这两行
                elem["id"] = (int)item.doc_id;
                elem["weight"] = item.weight;

                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)
        {
            //找到word在html_content中的首次出现, 然后往前找50字节(如果没有, 从begin开始)
            //然后往后找100字节(如果没有, 到end就可以), 截出这部分
            const int prev_step = 50;
            const int next_step = 100;
            //1、找到首次出现
            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); //tolower/toupper参数是int
            });
            if(iter == html_content.end())
            {
                return"None1";
            }
            //拿到first(开头)到iter迭代器间的个数 --->下标位置
            int pos = std::distance(html_content.begin(), iter); //获取下标位置

            //word是处理过的小写词,再到原网页中找,可能有匹配不到大写的!!
            //这里是不能使用find,这是有坑的!!
            // size_t pos = html_content.find(word); //find是精准匹配
            // if(pos == std::string::npos)
            // {
            //     return "None1";
            // }

            //2、获取start, end
            int start = 0;
            int end = html_content.size()-1;
            //如果之前有50+字符, 就更新开始位置
            if(pos > start + prev_step) //这里有一个大坑, size_t是一个无符号整数,所以换成int更好解决繁琐问题
            {
                start = pos - prev_step;
            }
            if(pos < end - next_step)
            {
                end = pos + next_step;
            }

            // if(pos-prev_step > start) //这里有一个大坑,size_t是一个无符号整数
            // {
            //     start = pos-prev_step;
            // }
            // if(pos+next_step < end)
            // {
            //     end = pos+next_step;
            // }


            //3、截取子串
            if(start >= end)
            {
                return "None2";
            }
            //摘要部分加最后
            std::string desc = html_content.substr(start, end-start);
            desc += "...";
            return desc;
        }
    };
}</code></pre> 
  <h3 id="log.hpp">log.hpp</h3> 
  <pre><code class="language-cpp">#pragma once
#include<iostream>
#include<string>
#include<time.h>

#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;
}</code></pre> 
  <h3 id="util.hpp">util.hpp</h3> 
  <pre><code class="language-cpp">#pragma once
#include<fstream>
#include<vector>
#include<string>
#include<iostream>
#include<boost/algorithm/string.hpp>
#include"cppjieba/include/cppjieba/Jieba.hpp"
#include<mutex>
#include<unordered_map>
#include"log.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())
            {
                std::cerr << "open file " << file_path << " error" << std::endl;
                return false;
            }

            std::string line;
            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, const std::string& sep)
        {
            // boost::split 不建议使用strtok
            // aaa\3bbb\3\3\3ccc
            boost::split(*out, target, boost::is_any_of(sep), boost::token_compress_on);
        }
    };

    //按照demo.cpp写
    //词库路径
    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";

    // load这个文件本身就是需要load一次的行为, 所以最好设计成单例模式
    // class JiebaUtil
    // {
    // private:
    //     cppjieba::Jieba jieba;
    //     std::unordered_map<std::string, bool> stop_words;
    //     static JiebaUtil* instance;
    //     static std::mutex mtx;
        
    // private:
    //     JiebaUtil() //构造函数初始化
    //         :jieba(DICT_PATH,HMM_PATH,USER_DICT_PATH,IDF_PATH,STOP_WORD_PATH)
    //     {}
    //     JiebaUtil(const JiebaUtil&) = delete;
    //     JiebaUtil operator=(const JiebaUtil&) = delete;

    // public:
    //     static JiebaUtil* get_instance()
    //     {
    //         if(instance == nullptr)
    //         {
    //             mtx.lock();
    //             if(instance == nullptr)
    //             {
    //                 instance = new JiebaUtil(); //堆上开辟对象
    //                 instance->InitJiebaUtil();
    //             }
    //             mtx.unlock();
    //         }
    //         return instance;
    //     }
    //     void InitJiebaUtil() //这个instance(静态指针)指向的对象不是静态的,所以这里可以调用
    //     {
    //         std::ifstream in(STOP_WORD_PATH);
    //         if(!in.is_open())
    //         {
    //             //std::cout << "load stop words file error" << std::endl;
    //             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(); ) //不适合用下标遍历,不能一味的iter++,要考虑迭代器失效问题
    //         {
    //             auto it = stop_words.find(*iter);
    //             if(it != stop_words.end())
    //             {
    //                 //说明当前的string是暂停词, 需要去掉
    //                 //注意迭代器失效的问题
    //                 iter = out->erase(iter);
    //             }
    //             else
    //             {
    //                 iter++;
    //             }
    //         }
    //     }
    //     static void CutString(const std::string& src, std::vector<std::string>* out)
    //     {
    //         get_instance()->CutStringHelper(src, out);//静态成员函数调用静态..
    //     }
    // };
    // JiebaUtil* JiebaUtil::instance = nullptr;
    // std::mutex JiebaUtil::mtx;


    //静态对象初始化
    //cppjieba::Jieba JiebaUtil::jieba(DICT_PATH,HMM_PATH,USER_DICT_PATH,IDF_PATH,STOP_WORD_PATH);

    //
    //不去掉暂停词的版本
    //load这个文件本身就是需要load一次的行为,所以最好设计成单例模式
    class JiebaUtil
    {
    private:
        static cppjieba::Jieba jieba;
    public:
        static void CutString(const std::string& src, std::vector<std::string>* out)
        {
            jieba.CutForSearch(src, *out);
        }
    };
    //静态对象初始化
    cppjieba::Jieba JiebaUtil::jieba(DICT_PATH,HMM_PATH,USER_DICT_PATH,IDF_PATH,STOP_WORD_PATH);
}</code></pre> 
  <h3 id="Makefile">Makefile</h3> 
  <pre><code class="language-bash">.PHONY:all
all:parser debug http_server

parser:parser.cc
	g++ -o $@ $^ -std=c++11 -lboost_system -lboost_filesystem
debug:debug.cc
	g++ -o $@ $^ -std=c++11 -ljsoncpp 
http_server:http_server.cc
	g++ -o $@ $^ -std=c++11 -lpthread -ljsoncpp

.PHONY:clean
clean:
	rm -rf parser debug http_server
</code></pre> 
  <h3 id="debug.cc">debug.cc</h3> 
  <pre><code class="language-cpp">#include<iostream>
#include<string>
#include<string.h>
#include"searcher.hpp"


const std::string input = "data/raw_html/raw.txt"; //处理完的文本的路径
int main()
{
    // for test
    ns_searcher::Searcher* search = new ns_searcher::Searcher();
    search->InitSearcher(input);

    char buffer[1024];
    std::string query;
    std::string json_string;
    while(true)
    {
        std::cout << "Plase Enter You Search Query# ";
        //std::cin >> query; //有bug, 读到空格或者换行符就不连续了
        fgets(buffer, sizeof(buffer)-1, stdin); //-1是为了预留\0位置
        buffer[strlen(buffer)-1] = 0; //strlen计算到\0就停止,取消换行符
        query = buffer;
        search->Search(query, &json_string);
        std::cout << json_string << std::endl;
    }

    return 0;
}</code></pre> 
  <h3 id="http_server.cc">http_server.cc</h3> 
  <pre><code class="language-cpp">#include"searcher.hpp"
#include"cpp-httplib-v0.7.15/httplib.h"
#include<iostream>
#include<string>
#include"log.hpp"


const std::string root_path = "./wwwroot";
const std::string input = "data/raw_html/raw.txt";

int main()
{
    ns_searcher::Searcher search;
    search.InitSearcher(input);

    httplib::Server svr;
    svr.set_base_dir(root_path.c_str()); //指明外部根目录在这里

    // "/s?"后面跟参数
    svr.Get("/s", [&search](const httplib::Request& req, httplib::Response& rep)
    {
        //rep.set_content("hello world", "text/plain; charset=utf-8");
        //根据浏览器get传参特性
        if(!req.has_param("word")) // "word="后面跟关键字, has_param判断是否有该关键字
        {
            rep.set_content("必须要有关键字", "text/plain; charset=utf-8");
            return;
        }
        std::string word = req.get_param_value("word"); //提取请求参数
        //std::cout << "用户在搜索: " << word << std::endl;
        LOG(NORMAL, "用户搜索的: " + word);
        std::string json_string;
        search.Search(word, &json_string);
        rep.set_content(json_string, "application/json");
    });
    LOG(NORMAL, "服务器启动成功...");
    svr.listen("0.0.0.0", 7781); //"0.0.0.0" 接收任意ip
    return 0;
}</code></pre> 
  <h3 id="wwwroot%2Findex.html">wwwroot/index.html</h3> 
  <pre><code class="language-html"><!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 搜索引擎
    


    

项目演示

项目链接:http://119.23.208.209:7781/

界面

BoostSearch搜索引擎_第3张图片

搜索测试

BoostSearch搜索引擎_第4张图片

点击跳转 

BoostSearch搜索引擎_第5张图片

看到这里,给博主点个赞吧~ 

                  BoostSearch搜索引擎_第6张图片

你可能感兴趣的:(C++项目,搜索引擎)