boost搜索引擎

news2024/11/16 5:40:02

文章目录

    • 1.项目介绍
    • 2.搜索引擎技术栈和项目环境
    • 3.正排索引和倒排索引 - 搜索引擎具体原理
    • 4.编写数据去标签和数据清洗模块Parser
    • 5.编写建立索引模块Index
    • 6.编写搜索引擎模块Searcher
    • 7.编写http_server
    • 8.效果展示

1.项目介绍

Boost官网没有对应的搜索引擎,不方便我们查看,本项目帮助我们搜索查阅Boost官方文档

2.搜索引擎技术栈和项目环境

技术栈:C/C++ C++11 STL 准标准库Boost 分词库cppjieba jsoncpp cpp-httplib库
项目环境:Centos7 云服务器,VsCode

3.正排索引和倒排索引 - 搜索引擎具体原理

文档1:雷军买了四斤小米
文档2:雷军发布了小米手机

正排索引:从文档ID找到文档内容

文档ID文档内容
1雷军买了四斤小米
2雷军发布了小米手机

倒排索引:根据关键字找到文档ID

关键字文档ID
雷军文档1,文档2
文档1
四斤文档1
小米文档1,文档2
发布文档2
小米手机文档2

例如:模拟一次搜索关键字
小米->倒排索引中查找->提取出文档ID(1,2)->根据正排索引->找到文档内容->返回相应的结果

4.编写数据去标签和数据清洗模块Parser

1.下载官方手册的网页在这里插入图片描述
下载解压后就是官方网页的信息
例:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Chapter 41. Boost.Typeof</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="boost_typeindex/acknowledgements.html" title="Acknowledgements">
<link rel="next" href="typeof/tuto.html" title="Tutorial">
</head>

2.去标签
<>:html的标签,我们需要去除的标签,一般为成对出对

在这里插入图片描述
将input中网页的信息,进行去标签后存入output中

目标:去标签后,文档之间用/3分割

整体代码框架

const std::string src_path = "data/input/html";
const std::string output = "data/output/raw.txt";
const std::string boost_root = "https://www.boost.org/doc/libs/1_81_0/doc/html";

struct DocInfo
{
    /* data */
    std::string title;   // 文档的标题
    std::string content; // 文档的主要内容
    std::string url;     // 文档在官网中URl
};
 // 在src_path路径下所有的文件名称
bool EnumFile(const std::string &src_path, std::vector<std::string> *file_list);
// 把file_list中所有文档的主要内容取出
bool ParseHtml(const std::vector<std::string> &file_list, std::vector<DocInfo> *results);
// 把解析完毕的文件内容写入到output ,以/3为分隔符
bool SaveHtml(const std::string &output, const std::vector<DocInfo> &results);

int main()
{
    // 在src_path路径下所有的文件名称
    std::vector<std::string> file_lists;

    if (EnumFile(src_path, &file_lists) == false)
    {
        std::cerr << "EnumFile error" << std::endl;
        exit(1);
    }
    // 把file_list中所有文档的主要内容取出
    std::vector<DocInfo> results;
    if (ParseHtml(file_lists, &results) == false)
    {
        std::cerr << "ParseHtml error" << std::endl;
        exit(1);
    }

    // 把解析完毕的文件内容写入到output ,以/3为分隔符
    if (SaveHtml(output, results) == false)
    {
        std::cerr << "SaveHtml error" << std::endl;
        exit(1);
    }

    return 0;
}

EnumFile实现

Boost库的安装不做过多介绍

static bool EnumFile(const std::string &src_path, std::vector<std::string> *file_list)
{
	//Boost官方提供的文件操作
    namespace fs = boost::filesystem;
    fs::path root_path(src_path);

    if (!fs::exists(root_path))
    {
        std::cerr << "fs::exists:" << src_path << std::endl;
        exit(1);
    }

    fs::recursive_directory_iterator end;
    for (fs::recursive_directory_iterator it(root_path); it != end; ++it)
    {
        if (!fs::is_regular_file(*it))
            continue;

        if (it->path().extension() != ".html")
            continue;

        // std::cout<<"debug:"<<it->path().string()<<std::endl;
        file_list->emplace_back(it->path().string());
    }

    return true;
}

ParseHtml实现

读取文件操作

namespace ns_util
{
    class FileUtil
    {
    public:
        static bool ReadFile(const std::string& file,std::string* content)
        {
            std::ifstream ifs(file.c_str());
            
            std::string tmp;
            while(std::getline(ifs,tmp))
            {
                *content+=tmp;
            }

            return true;
        }
    };
 }
bool ParseHtml(const std::vector<std::string> &file_lists, std::vector<DocInfo> *results)
{
    for (const std::string &file_list : file_lists)
    {
        // 读取每个文档所有的内容
        std::string content;
        ns_util::FileUtil::ReadFile(file_list, &content);

        DocInfo doc;

        // 取出标题
        if (ParseTitle(content, &doc) == false)
        {
            std::cerr << "ParseTitle error" << std::endl;
        }

        // 取出主要内容
        if (ParseContent(content, &doc) == false)
        {
            std::cerr << "ParseContent error" << std::endl;
        }

        // 取出url
        if (ParseUrl(file_list, &doc) == false)
        {
            std::cerr << "ParseContent error" << std::endl;
        }
        
        results->emplace_back(doc);
    }

取出标题

static bool ParseTitle(const std::string &content, DocInfo *doc)
{
    size_t begin = content.find("<title>");
    if (begin == std::string::npos)
    {
        return false;
    }
    size_t end = content.find("</title>");
    if (end == std::string::npos)
    {
        return false;
    }

    std::string title;
    size_t len = strlen("<title>");
    title = content.substr(begin + len, end - begin-len);
    doc->title.swap(title);

    return true;
}

取出主要内容

static bool ParseContent(const std::string &content, DocInfo *doc)
{
    enum status
    {
        LABLE,
        CONTENT
    };
    enum status s = LABLE;
    std::string con;
    for (auto c : content)
    {
        switch (s)
        {
            case LABLE:
            {
                if(c == '>')
                    s = CONTENT;
                break;
            }
            case CONTENT:
            {
                if(c == '<')
                {
                    s = LABLE;
                    continue;
                }

                if(c=='\n')
                    c=' ';

                con+=c;
                break;
            }
            default:
            {
                break;
            }
        }
    }
    doc->content.swap(con);

    return true;
}

取出url
官网的url:https://www.boost.org/doc/libs/1_81_0/libs/beast/doc/html/index.html
当前路径:data/input/html/index.html
只要加粗的地方相加就能得到官网的url

const std::string boost_root = "https://www.boost.org/doc/libs/1_81_0/doc/html"
static bool ParseUrl(const std::string &file_list, DocInfo *doc)
{
    size_t len = src_path.size();
    std::string url;
    url+=boost_root;
    url+=file_list.substr(len);
    doc->url.swap(url);
    return true;
}

把解析完毕的文件内容写入到output ,以/3为分隔符

bool SaveHtml(const std::string &output, const std::vector<DocInfo> &results)
{
#define SEP '\3'
    std::ofstream out(output,std::ios::out | std::ios::binary);
    if(out.is_open() == false)
    {
        std::cerr<<"out.is_open"<<std::endl;
        return false;
    }

    for(auto & result:results)
    {
        std::string out_string;
        out_string+=result.title;
        out_string+=SEP;
        out_string+=result.content;
        out_string+=SEP;
        out_string+=result.url;
        out_string+='\n';

        out.write(out_string.c_str(),out_string.size());
    }
    out.close();
    
    return true;
}

5.编写建立索引模块Index

namespace ns_index
{
    struct DocInfo
    {
        std::string title; // 文档的标题
        std::string content; // 文档的主要内容
        std::string url; // 文档的url
        u_int64_t doc_id; //文档的id
    };

    struct InvertedElem
    {
        u_int64_t doc_id; // 文档id
        std::string word; // 词的内容
        int weight; // 权值
    };
    typedef std::vector<InvertedElem> InvertedList;

    class Index
    {
    private:
        Index(){}
        Index(const Index&) = delete;
        Index operator=(const Index&) = delete;
    public:
        //单利模式
        static Index* GetInstance()
        {
            if(instance == nullptr)
            {
                mtx.lock();
                if(instance == nullptr)
                {
                    instance = new Index();
                }
                mtx.unlock();
            }

            return instance;
        }



		//根据正排索引找文档
        DocInfo* GetForwardIndex(u_int64_t doc_id)
        {
            if(doc_id > forward_index.size())
            {
                std::cerr<<"doc_id is too big"<<std::endl;                           
                return nullptr;

            }
            return &forward_index[doc_id];
        }

        //根据倒排索引找doc
        InvertedList* GetBackwardIndex(const std::string& word)
        {
            auto it = backward_index.find(word);
            if(it == backward_index.end())
            {
                std::cerr<<"no word"<<std::endl;                           
                return nullptr;

            }

            return &it->second;
        }
        
        //建立正排和倒排索引
        bool BuildIndex(const std::string& input)
        {
        }

        private:
        std::vector<DocInfo> forward_index; // 正排索引
        std::unordered_map<std::string,InvertedList> backward_index; //倒排索引
        static Index* instance;
        static std::mutex mtx; //锁
        
    };
    Index* Index::instance = nullptr;
    std::mutex Index::mtx;
}

建立正排和倒排索引

bool BuildIndex(const std::string& input)
 {
     std::ifstream in(input,std::ios::in| std::ios::binary);
     if(in.is_open() == false)
     {
         std::cerr<<"fail to open"<<input<<std::endl;
         return false;
     }
     //打开成功了文件
     std::string line;
     while(std::getline(in,line))
     {
         //建立正排索引
         DocInfo* info = BuildForwardIndex(line);
         if(info == nullptr)
         {
             std::cerr<<"fail to BuildForwardIndex"<<std::endl;
             continue;
         }
         //建立倒排索引
         if(BuildBackwardIndex(*info) == false)
         {
             std::cerr<<"fail to BuildBackwardIndex"<<std::endl;
             continue;
         }
     }
     return true;

 }

//建立正排索引
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::cerr<<"results.size()!=3"<<std::endl;
        return nullptr;
    }   
    
    DocInfo doc;
    doc.title=results[0];
    doc.content=results[1];
    doc.url=results[2];
    doc.doc_id=forward_index.size();

    forward_index.emplace_back(doc);
    
    return &forward_index.back();
}

jieba分词的使用可参考这篇文章本项目仅仅做一点简单使用
jieba库

class JiebaUtil
{
public:
    static void CurString(const std::string&s,std::vector<std::string>* words)
    {
        jieba.CutForSearch(s,*words);
    }
private:
    static cppjieba::Jieba jieba; //jieba对象
};
cppjieba::Jieba JiebaUtil::jieba(DICT_PATH,HMM_PATH,USER_DICT_PATH,IDF_PATH,STOP_WORD_PATH);

//建立倒排索引
bool BuildBackwardIndex(const DocInfo& info)
{
    struct word_cnt
    {
        int title_cnt; //标题某个词出先的次数
        int content_cnt;//内容某个词出现的次数

        word_cnt():title_cnt(0) ,content_cnt(0){}
    };

    //切分字符串
    std::vector<std::string> title_words;
    ns_util::JiebaUtil::CurString(info.title,&title_words);
    std::vector<std::string> content_words;
    ns_util::JiebaUtil::CurString(info.content,&content_words);


    std::unordered_map<std::string,word_cnt> word_map;

    //对每个单词计数
    for(auto word:title_words)
    {
        boost::to_lower(word);
        word_map[word].title_cnt++;
    }
    for(auto word:content_words)
    {
        boost::to_lower(word);
        word_map[word].content_cnt++;
    }

    for(auto& word_pair:word_map)
    {
        InvertedElem elem;
        elem.doc_id = info.doc_id;
        elem.weight = 10*word_pair.second.title_cnt +word_pair.second.content_cnt;
        elem.word = word_pair.first;
        InvertedList& inverted_list = backward_index[word_pair.first];
        inverted_list.emplace_back(elem);

    }

    return true;
}

6.编写搜索引擎模块Searcher

搜索逻辑:

1.根据关键字进行分词
2.根据分词结果查找索引
3.根据返回结果进行排序
4.序列化json_string

class Searcher
    {
    public:
        Searcher(){}
        ~Searcher(){}
        
        void InitSearch(const std::string& input)
        {
            //1. 获取index对象
            index = ns_index::Index::GetInstance();
            std::cout<<"获取index单例成功.."<<std::endl;
            //2. 根据index对象建立索引
            index->BuildIndex(input);
            std::cout<<"建立正排和倒排索引成功"<<std::endl;

        }
        //query :搜索关键字
        //json_string: 返回给用户的序列化的字符串
        void Search(const std::string& query,std::string* json_string)
        {
            // 1.进行分词
            // 2.根据分词结果查找索引
            // 3.根据返回结果进行排序
            // 4.序列化json_string
        }


    private:
        ns_index::Index* index;
    };
        //query :搜索关键字
        //json_string: 返回给用户的序列化的字符串
        void Search(const std::string& query,std::string* json_string)
        {
            // 1.根据关键字进行分词
            std::vector<std::string> words;
            ns_util::JiebaUtil::CurString(query,&words);
            // 2.根据分词结果查找索引
            ns_index::InvertedList inverted_list_all;
            for(auto word:words)
            {
            	//不区分大小写
                boost::to_lower(word);
                ns_index::InvertedList* inverted_list = index->GetBackwardIndex(word);
                if(inverted_list == nullptr)
                {
                    continue;
                }

                inverted_list_all.insert(inverted_list_all.end(),inverted_list->begin(),inverted_list->end());


            }

            // 3.根据返回结果进行排序
            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_string
            Json::Value root;

            for(auto& elem:inverted_list_all)
            {
                ns_index::DocInfo* doc =  index->GetForwardIndex(elem.doc_id);
                if(doc == nullptr)
                {
                    continue;
                }
                Json::Value val;
                val["title"] = doc->title;
                // 网页主要内容太大,截取部分关键摘要
                val["desc"] = GetDesc(doc->content,elem.word);
                val["url"] = doc->url;
                
                root.append(val);
            }

            Json::StyledWriter writer;
            *json_string = writer.write(root);
        }
    private:
    	// 网页主要内容太大,截取部分关键摘要
        std::string GetDesc(const std::string & content,const std::string& word)
        {
            std::string desc;
            const size_t prev_step = 50;
            const size_t next_step = 100;

            //找到首次出现的位置

            auto it = std::search(content.begin(),content.end(),word.begin(),word.end(),
            [](char c1,char c2){ return std::tolower(c1) == std::tolower(c2);});
            if(it == content.end())
            {
                return "None";
            }
            size_t pos = std::distance(content.begin(),it);


            size_t start = 0;
            size_t end = content.size();

            if(pos  > start + prev_step)
            {
                start = pos - prev_step;
            }

            if(pos + next_step < end)
            {
                end = pos + next_step;
            }

            if(start>end)
                return "None";

            return content.substr(start,end-start);
        }

7.编写http_server

注:cpp-httplib 需要较新的C++编译器	
const std::string input ="data/output/raw.txt";

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

    httplib::Server svr;
    svr.set_base_dir(root_path.c_str());
    svr.Get("/s", [&search](const httplib::Request &req, httplib::Response &rsp){
            if(!req.has_param("word")){
                rsp.set_content("必须要有搜索关键字!", "text/plain; charset=utf-8");
                return;
            }
            std::string word = req.get_param_value("word");
            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;
}

8.效果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

04 frameset-iframe【尚硅谷JavaWeb教程】

04 frameset-iframe【尚硅谷JavaWeb教程】 frameset、iframe这个标签基本上已经不用了。 frameset标签 一个大的网页由很多个小的网页组成&#xff0c;会用到frameset。 frameset 表示页面框架&#xff0c;这个标签已经淘汰&#xff0c;了解&#xff0c;不需要掌握。 frame表…

Element ui Avatar头像管理组件 实现当用户没有头像时 以名称最后一个字为头像

el-avatar是一个比较方便的头像管理组件 src控制他的图片展示 <el-avatarclass "avatar":src"item.images" ></el-avatar>样式的话 可以用avatar控制 <style> .avatar{width: 18px;height: 18px;line-height: 18px; } .avatar img{b…

【可解释性机器学习】TextExplainer: 调试黑盒文本分类器

TextExplainer: 调试黑盒文本分类器示例问题&#xff1a;20个新闻组数据集的LSA SVM模型TextExplainer文本解释器的工作原理我们应该相信这个解释吗&#xff1f;让它们犯错吧让它们再次犯错吧自定义TextExplainer: 采样过程自定义TextExplainer&#xff1a;分类器参考资料尽管…

记录每日LeetCode 237.删除链表中的节点 Java实现

题目描述&#xff1a; 有一个单链表的 head&#xff0c;我们想删除它其中的一个节点 node。 给你一个需要删除的节点 node 。你将 无法访问 第一个节点 head。 链表的所有值都是 唯一的&#xff0c;并且保证给定的节点 node 不是链表中的最后一个节点。 删除给定的节点。注…

Kotlin之使用DSL构建专有的语法结构

DSL的全称是领域特定语言(Domain Specific Language)&#xff0c;它是编程语言赋予开发者的一种特殊能力&#xff0c;通过它我们可以编写出一些看似脱离其原始语法结构的代码&#xff0c;从而构建出一种专有的特殊结构。 Kotlin也是支持DSL的&#xff0c;并且在Kotlin中实现DSL…

CF——1766C - Hamiltonian Wall

题目链接 1766C - Hamiltonian Wall Rating&#xff1a;1300 题目描述 Sir Monocarp Hamilton is planning to paint his wall. The wall can be represented as a grid, consisting of 2 rows and m columns. Initially, the wall is completely white. Monocarp wants to p…

Leetcode力扣秋招刷题路-0101

从0开始的秋招刷题路&#xff0c;记录下所刷每道题的题解&#xff0c;帮助自己回顾总结 101. 对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1…

[LeetCode周赛复盘] 第 330 场周赛20230129

[LeetCode周赛复盘] 第 330 场周赛20230129 一、本周周赛总结二、 [Easy] 6337. 统计桌面上的不同数字1. 题目描述2. 思路分析3. 代码实现三、[Medium] 6338. 猴子碰撞的方法数1. 题目描述2. 思路分析3. 代码实现四、[Hard] 6339. 将珠子放入背包中1. 题目描述2. 思路分析3. 代…

过年了,给网站加个灯笼+飘雪效果!

过年了&#xff0c;下面分享一个网站的特效&#xff0c;给网站添加一个新春灯笼和飘雪的效果&#xff0c;过年期间多一点年味。灯笼特效下面是css样式&#xff0c;可以放在公共样式中&#xff1a;.deng-box{position:fixed;top:-40px;right:150px;z-index:9999;pointer-events:…

【音视频工具】前端屏幕录制工具 + 录制<video>标签内容

一、录制的实现思路 1.开始录制、停止录制、下载视频 2.Blob介绍 3.概念 var mediaRecord //用于录制视频 var mediaStream //视频流 var videoBuffer [] //保存的视频数据二、屏幕录制工具 下载地址&#xff1a; https://chrome.google.com/webstore/detail/tampermonkey…

k8s之POD资源限制和健康监测

写在前面 本文一起看下POD的资源限制配置和健康监测的相关内容。 1&#xff1a;资源限制 如果是不对POD设置资源限制的话&#xff0c;若任由其占用系统资源&#xff0c;可能会造成非常严重的后果&#xff0c;所以我们需要根据具体情况来设置资源限制&#xff0c;如使用多少内…

怎样把截图转换成文字?三分钟教会你如何截图转文字

在日常的学习中&#xff0c;当你在网上看到一篇文章&#xff0c;而当中的某一段话很适合拿来用在自己的写作上&#xff0c;但是你却无法直接将其复制粘贴下来&#xff0c;只能先截图下来再手动输入&#xff0c;这种方法虽然可行&#xff0c;但比较消耗时间和精力&#xff0c;那…

详细图解LeetCode经典链表算法题

文章目录链表类型算法题一、链表介绍本文使用的Java中链表类&#xff1a;二、链表基础题1、数组转链表代码&#xff1a;测试&#xff1a;2、单链表翻转题目&#xff1a;代码&#xff1a;解析&#xff1a;测试&#xff1a;补充&#xff1a;3、合并两个有序链表题目&#xff1a;解…

顺应信创发展,君子签电子签章方案全面适配信创环境

信创产业作为战略性新兴产业&#xff0c;近年来&#xff0c;国家不断出台相关政策对行业的发展进行支持。 2018年我国首次将信创纳入国家战略&#xff0c;并提出了 “28N”应用体系&#xff0c;信创发展步入“快车道”&#xff1b;2020年起&#xff0c;信创产业由党政逐渐向其…

垃圾收集器必问系列—ZGC

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 人的一切痛苦&#xff0c;本质上都是对自己的无能的愤怒。——王小波 文章目录Region布局读屏障染色指针染色指针的优势运作过程ZGC的优缺点ZGC有人称它为Zero GC&#xff0c;其实“Z”并非什么专业名词的缩写…

vue前端框架课程笔记(二)

目录计算属性问题引入插值语法实现methods配置属性实现computed配置属性一些问题getter与setter注意监视属性问题引入computed和methods实现watch属性watch属性简介computed与watch的比较注意本博客参考尚硅谷官方课程&#xff0c;详细请参考 【尚硅谷bilibili官方】 本博客以…

千峰网络安全笔记(前三讲)

典中典 《c语言从研发到脱发》 《C从入门到放弃》 《Java从跨平台到跨行业》 《Ios开发从入门到下架》 《Android开发大全——从开始到转行》 《PHP由初学至搬砖》 《黑客攻防:从入门到入狱》 《Mysql从删库到跑路》 《服务器运维管理从网络异常到硬盘全红》 《服务器运维管理…

CentOS 8 中dnf管理器如何仅下载不安装软件

在某些情况下&#xff0c;我们希望从命令行下载特定或一组 RPM 包而不安装它。虽然我们可以使用 wget 命令下载&#xff0c;但 wget 不会下载安装包的依赖项。在 CentOS 8 中DNF&#xff08;或 yum&#xff09;是一个命令行包管理工具。使用 DNF我们可以安装、更新和删除 rpm 包…

HTTP协议学习

一、http请求协议1、常见请求头accept:浏览器通过这个头告诉服务器&#xff0c;它所支持的数据类型Accept-Charset: 浏览器通过这个头告诉服务器&#xff0c;它支持哪种字符集Accept-Encoding&#xff1a;浏览器通过这个头告诉服务器&#xff0c;支持的压缩格式Accept-Language…

114.简单的动态切换app的图标

1.第一步 通过activity-alias别名实现&#xff0c;manifest 这里写的是一个默认的图标Default和一个需要切换的图标Test&#xff0c;以及一个默认的首页面HomeActivity&#xff1a; <!-- 默认的图标--> <activity-aliasandroid:name".activity.Default"and…