基于Boost库的在线搜索引擎

news2024/11/20 6:18:16

文章目录

  • Boost库搜索引擎
    • 1. 项目背景
    • 2. 宏观原理
    • 3. 搜索引擎技术栈和项目环境
    • 4.正排索引vs倒排索引(index.hpp)
          • 1. 正排索引:数组vector<>
          • 2. 目标文档进行分词(方便倒排索引和查找)
          • 3. 倒排索引:unordered_map<>
          • 模拟一次查找的过程
    • 5. 数据去标签和数据清洗模块 Parser
          • parser.cc 实现去标签的作用
          • 编写parser.cc
    • 6. 建立索引Index
          • 正排索引
          • 倒排索引
          • 获取正排索引 GetFordIndex()
          • 获取倒排拉链 GetInvertedList()
          • 构建索引 BuildIndex()
    • 7.编写searcher
        • 调试 debug处理信息打印出现错误
    • 8. httplib引入-让网页能够访问
          • 编写http_server. cc
          • 前端代码略
    • 9. 处理去重问题

Boost库搜索引擎

1. 项目背景

百度,搜狗,头条新闻客户端等搜索是全网搜索。而我做的是基于boost库的站内搜索,类似于cplusplus.com,这类搜索的特点是:搜索的数据更加垂直,范围中的关联性更强,数据量更小。

  • 网页返回的信息有三部分:

    网页的title,网页内容的摘要描述,即将跳转的网址url

  • boost的官网是没有站内搜索的。建立doc/库下的搜索引擎。

  • 获得数据源的方式是把库下下来(可升级为爬虫程序)

  • 访问方式浏览器输入IP,端口号8082

2. 宏观原理

在这里插入图片描述

  1. 去标签和数据处理。在全网当中通过爬虫程序取出广告,保留标题,内容和url。

  2. 建立索引信息,方便快速查找。正排索引(id->文档),倒排索引(文档关键字->所在文档的id)

  3. http请求的方式,进行搜索任务。通过GET的方式,上传搜索关键字。

  4. server检索索引,得到对应的标题,内容和网址,相关的html。

  5. 拼接多个网页的title+desc+url,构建一个网页,返回给用户。

    数据清理应该得到的文档信息:

    • 清理出来的文档信息应该包括几部分
    1. 标题
    2. 内容
    3. 网址
    4. 文档id

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

  • 技术栈:C/C++,C++11,STL,准标准库Boost,jsoncpp(CS端的数据交换的目的,序列化和反序列化交互),**cppjieba(进行词的切分),**cpp.httplib(将信息在网页上呈现)
  • 项目环境:Centos7云服务器,vim/g++,vscode(网页前端设计)

4.正排索引vs倒排索引(index.hpp)

1. 正排索引:数组vector<>

就是从文档ID找到文档内容(文档中的关键字),数组下标就是一种映射关系

2. 目标文档进行分词(方便倒排索引和查找)

搜索引擎停止词:了,的,吗,一般是在分词的时候先忽略,区分唯一性价值不大。

3. 倒排索引:unordered_map<>

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

模拟一次查找的过程

用户输入:大学生->倒排索引->提取文档ID(1,2)->根据正排索引找到文档内容->title+desc+url文档结果进行摘要->构建响应结果。

5. 数据去标签和数据清洗模块 Parser

下载Boost库:doc/html目录里面都是各种网页html充当数据源。
在这里插入图片描述

data/input下放的就是从doc目录下拷贝过来的数据源,建立索引,拼接url,构建跳转url。

parser.cc 实现去标签的作用

<>括里面的:html的标签,对我们进行搜索的操作是没用的,所以需要去掉这个标签,一般的标签是成对出现的。在这里插入图片描述

清洗之后的结果放到raw_html中,干净的文档放到input文件夹中。我们下载下来的一共有8171个。

在这里插入图片描述

目标:把每个文档都去标签清洗之后,然后写入到同一个文件中,每个文档内容之间不需要换行,只占一行!文档和文档之间用\3进行区分。控制字符属于不可显示字符中3是^C,不会污染原来的内容。

编写parser.cc

如果参数的类型是const& :输入,*:输出,&:输入输出

  • 将文档内容进行读取,放到vector<string>file_list中,EnumFile();

    引入boost库中的filesystem中的方法,boost库的安装。链接时报错,makefile中需要引入两个库,-lboost_system -lboostfilesystem

    首先需要用路径构建对象才能够调用接下来的方法。

    • 判断你给的起始路径是否存在,如果没有数据源就没有了

      boost::filesystem::exists();
      在这里插入图片描述

    • 判断路径中的文件是否是普通文件(不能是图片之类的)引入boost库中的is_regular_fille();
      在这里插入图片描述

    • 挑选后缀是.html的文件-> 迭代器对象中的iter->path().extension()=='.html';判断路径 名的后缀是否是.html
      在这里插入图片描述

    • 将符合条件的文件的路径插入到file_list中,因为我们用原目录构造了一个Boost库中的对象采用的上面的方法,但是插入的时候需要将路径对象转化为字符串类型才能插入,所以引入对象.string()的方法。在这里插入图片描述

  • 读取file_list中的内容,并对内容进行解析ParseHtml();放到DocInfo_t结构体对象中,最后形成了8000+个对象,放到vector<DocInfo_t>中。

    • 读取文件,util.hpp/FileUtil::ReadFile();打开文件失败就去找下一个文件

      采用std::ifstream对文件进行打开,然后使用getline()将文件中的内容按行读取出来。

    • 解析指定文件提取title-》ParseTitle();

      就是在文档中搜索<title></title>,中间就是title内容,查找关键字,然后前闭后开区间

    • 解析指定文件提取content-》ParseContent();

      • 去标签,凡是在标签内部的都去掉。要么是在遍历标签内容,要么就是合法内容所以引入状态机,两种状态进行标识内容是啥类型。
      • 字符形式遍历,只要碰到>就是标签结束的标志。后面大概率是CONTENT,但是也可能是另一个标签的开始,所以加一个特判就行了。细节:不想保留原始数据中的\n,因为想用\n作为html解析之后的文本分隔符。
    • 解析指定文件路径构建url-》ParseUrl();

      Boost库的官方文档,和我们下载的文档是有路径的关联的。

      下载文件的路径:在这里插入图片描述

      官网路径:在这里插入图片描述

      为了拼接网页url,所以只需要用https://www.boost.org/doc/libs/1_80_0/doc/html/替换data/input就行了。

  • 解析文件内容放到output中,按照\3为分隔符SaveHtml();

    • 同一个文档中区分出来的title content url部分之间用\3分割,不同文档之间用\n来分割,这样保存文件便于在下一次读取的时候方便用getline()依次读取到各个文档的完整内容。

      title\3content\3url\ntitle\3content\3url

    • 以二进制的方式写入文件,这样原文档是什么样写入的就是什么样。

6. 建立索引Index

正排索引

数据结构采用类似数组,数组下标天然是文档的id。vector<>

倒排索引

会存在多个结果对应一个关键字,一个关键字和一组InvertedElem对应。采用unordered_map对应关系,用vector 承装一组节点,给他整体typedef起个名字InvertedList,倒排拉链。InvertedElem结构体中成装的是对应文档的id,关键字,和权重(便于后续构建网页时的排序)。

构成了关键字和倒排拉链的关系。

获取正排索引 GetFordIndex()

根据docid获取文档内容。vector<>根据下标直接返回DocInfo* 。

获取倒排拉链 GetInvertedList()

根据关键字string unordered_map查找关键字返回拉链指针。

构建索引 BuildIndex()

需要parse处理完毕的数据的路径给我,构建正排倒排索引

  • 读取文件内容,按行读取文件

  • 建立正排索引,返回根据读取的line填充好的DocInfo信息结构体

    • 解析Line,字符串切分Util.hpp/StringUtil::Split();返回值是vector<>

      ->Boost库中的split()方法

    • 字符串进行填充DocInfo中

      doc_id其实就是数组下标,我第一次插,之前的大小是0,我差的位置也是0。所以先更新id再插入。

      doc.doc_id=forward_index.size();

    • 插入到正排索引的vector中

      返回最后一个元素的地址DocInfo*,采用vector<>.back();

  • 建立倒排索引

    //我们拿到的文档内容
    struct DocInfo
      {
        std::string title;    //文档标题
        std::string content;  //文档对应的去标签之后的内容
        std::string url;      //官网文档url
        uint64_t doc_id;      //文档的id
      };
    //拉链节点
    struct InvertedElem
      {
          uint64_t doc_id;
          int weight;
          std::string word;
      };
    //根据文档内容,形成一个或者多个文档描述结构体INvertedElem。
    //一个文档包含很多词,都应该对应到当前的dic_id
    1. 对title&content进行分词--使用cppjieba分词
    2. 词频统计来充当相关性(标题中出现相关性更高一点)
    遍历内容,词频++。
    unordered_map<string ,word_cnt> count;
    class word_cnt
    {
        title_cnt;
        word_cnt;
    };
    3. 自定义相关性
    遍历word_cnt,拿到每一个词,由map得到具体一个词的和文档的对应关系,当有多个不同的词指向同一个文档,由相关性决定优先显示哪一个。
    
    • 分词

    获取jieba,然后git clone即可。词库在dict软连接下,头文件在inc软连接下。将cppjieba/deps下的limonp拷贝到include/cppjieba 中,各种头文件.为了让demo.cpp能够编译通过。

    • 编写到倒排索引

      • 对标题内容进行分词和词频统计,word_map(unordered_map)用来暂存词频的映射表.[]如果存在就操作,如果不存在就新建添加。

      • 对文档中内容进行分词和词频统计,大小写不区分,所以都转换成小写再进行统计。使用boost::tolower();因为不对原来词汇进行修改,所以for()遍历时不加&,都是拷贝。呈现上不修改原文档,算法上统一用小写实现了忽略大小写的目的。

      • 建立倒排索引-因为我们是按个文档走下来的,所以只需要遍历词频表,计算当前文档和分出来的那些词之间的关系,然后放在InvertedElem结构体中,构建一个倒排拉链的节点就行。

        然后将词依次插入到倒排索引的unorered_map中,映射的是一个倒排拉链。[]的优势就是如果有就是拉链的引用,如果没有就新建一个。然后因为倒排拉链是vector所以只需要将填充好的item插入就行了。

在这里插入图片描述

- 随后会建立各种分词和此文档之间的关系,即倒排拉链倒排索引。

7.编写searcher

  • 初始化InitSeacher():

    • 获取创建index对象-单例模式(最好只有一份index)

      • index的拷贝构造和赋值构造都禁用,设置static Index* instance;初始化:Index* Index::instance=nullptr;

      • 编写获取单例的函数GetInstance();会存在线程安全问题,引入c++中的mutex。

        两层判断是否是第一次建立索引,减少很多加锁和申请锁的过程。

    • 根据Index对象建立索引

      index->BuildIndex();

  • 搜索服务的接口Seacher():搜索关键字qurey,返回给用户浏览器的json_string串。

    • [分词]:我们的搜索关键字query也要在服务端进行分词方便查找

      util.hpp/JiebaUtil::Cutstring();

    • [触发]:就是根据分词的各个词进行index查找

      ->对关键字的每一个分词进行遍历,

      ->查找每个词的倒排拉链,index->GetIndexList();

      ->将所有的拉链整合到vector<>数组中,inverted_list_all.insert(inverted_list_all.end(),inverted_list->begin(),inverted_list->end());

在这里插入图片描述

帮助理解图:
在这里插入图片描述

- 存在问题:可能不同的两个词的倒排索引是一样的,所以就要去重。(最后处理)
  • [合并排序]:汇总查找结果,按照相关性降序排列sort();

    根据相关性倒排,所以需要写回调函数用lambda表达式,每两个倒排拉链之间要进行排序,也就是不同关键词分词在文档中的相关性进行大小排序。

  • [构建]:根据查找结果构建json_string(jsoncpp)

    • 根据拉链中元素的doc_id进行正排索引获取到DocInfo*=index->GetForwordList();

    • 将拉链中每一个节点的doc结构体中的属性填充给Json::Value::item,title,content并不要全部只要一部分就行,url。

      • string GetDesc();找到在文档中首次出现的位置,往前一部分,往后一部分截取出来。
        在这里插入图片描述

      • 判断边界时,都用加法实现都是正整数。一部分随意决定字符个数,如果前面没有50个就从开头来,同理结尾。

        int start =0;
              int end =html_content.size()-1;
               //如果之前没有50字节,就从头开始
               //如果有50字节,就从倒数第50字节开始
               //加法全变成正整数
                if(pos>start+prev_step ) start = pos-prev_step;
                if(pos <end-next_step) end=pos+next_step;
                if(start>=end) return "None2";
                std::string desc = html_content.substr(start,end-start);
                desc+="...";
                return desc;
        

        检查条件:url是否正确,排序是否是按照权重排序的。

    • 填充进Json::Value::root中,然后整体写入到*json_string中返回。

调试 debug处理信息打印出现错误

  1. cin是以空格为分隔符的,遇到空格就会停止读取,只会读取到其中的一部分,所以选择fgets();会将输入的内容全部读取出来,包括换行符号,在打印的时候都会打印出来。

在这里插入图片描述

  1. 搜索结果desc的时候出现空值的情况,也就是索引有问题或者是摘要有问题。

    • 获取摘要的时候size_t 也就是无符号的坑,相减之后小于0仍然被当成一个很大的值造成报错。全设置为Int。
    • 在html_content文档中查找关键字首次出现的位置的问题时,采用find是区分大小写的,也就是我们在原文档中(大小写都有)搜索已经在建立倒排拉链的时候全部转化为小写的关键字word,索引里面的都是已经转化好的小写内容,但是在获取摘要的时候找的是原文档,就会出现大小不匹配的情况造成搜索不到word的位置造成空值的情况。所以采用C++中忽略大小写的函数search();,在文档中搜索字符串首次出现的位置,两个形参是两个字符,用仿函数或者lambda表达式专门比较两个字符是否相等,比较的时候都转化为小写。在这里插入图片描述
  2. 排序是否是按照权重排序的?权值和预期结果不太匹配

    • 分词的时候和网页的分词搜索不太一样。
    • 对整个文档去标签,标签中可能就存在分词的存在。
    • 如果一个词在title中出现,一定会被当标题和当内容分别被统计一次。

8. httplib引入-让网页能够访问

  • cpp-libhttplib需要更高版本的gcc编译器。gcc -v查看编译器版本信息。配置bash/profile文件使得每次都是较为新版本的gcc。

  • 安装cpp-httplib

    最新的版本会使得不是较新的gcc编译之后在运行的时候出现问题。

  • 只需要httplib.h文件引入到项目中就行。

  • 编译时需要引入-lpthread

  1. 设置默认根目录wwwroot,里面整网页信息index.html
编写http_server. cc
  1. 首先需要初始化Search,也就是需要创建单例,获取对象索引信息。

  2. content_type对照表

在这里插入图片描述

  1. 对外服务Search()函数
前端代码略

9. 处理去重问题

  1. 去重,也就是很多分词都是同一个文档,导致收到同一个内容。

    struct InvertedElemPrint{}; tokes_map;

    去重去掉的就是相同的Inverted_Elem,如果同一个doc_id之下的文档的很多分词,每一个分词分别获取自己的倒排拉链,遍历这些个分词的拉链节点,doc_id相同的就重叠处理,然后权值相加,并把它们放到一个数组当中,整体处理统一返回。将重新处理之后的Elem插入到InvertedListall链表之中(之前是在遍历关键字切分数组的时候直接将链表插入InvertedListall链表)。

    此时获取摘要的时候item 就不止是一个单词了,而是一个填充了很多docid相同的词的数组 words[]数组,那么我只需要取其中一个获取摘要就行了。

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

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

相关文章

反向传播不香了?解读 Hinton 大佬的 Forward-Forward 算法

今天解读一篇Hinton大佬最近分享的论文。 在最近的NeurIPS2022会议上&#xff0c;图灵奖得主Hinton作为演讲嘉宾&#xff0c;分享了一个题为《The Forward-Forward Algoritm: Some Preliminary Investigations》的论文。 该论文提出了一种取代反向传播的前向-前向传播的训练方…

华为机试 - 比较两个版本号的大小

目录 题目描述 输入描述 输出描述 用例 题目解析 算法源码 题目描述 输入两个版本号 version1 和 version2&#xff0c;每个版本号由多个子版本号组成。 子版本号之间由 “.” 隔开&#xff0c;由大小写字母、数字组成&#xff0c;并且至少有一个字符。 按从左到右的顺…

spirng boot 打包,胖fat包和瘦thin包

一、打胖包fat 打胖包采用的是spring的标准来执行&#xff0c;所以使用的是spring boot提供的打包插件 参考来源&#xff1a;打包Spring Boot应用 - 廖雪峰的官方网站 步骤 pom配置 <project ...>...<build><plugins><plugin><groupId>org.s…

Python 奇淫技巧,助你更好的摸鱼

作为一个数据分析者&#xff0c;日常工作几乎离不 python。一路走来&#xff0c;积累了不少有用的技巧和 tips&#xff0c;现在就将这些技巧分享给大家。这些技巧将根据其首字母按 A-Z 的顺序进行展示。 ALL OR ANY Python 之所以成为这么一门受欢迎的语言一个原因是它的可读…

(一)整合管理范围管理

爬虫组件分析目录概述需求&#xff1a;设计思路实现思路分析1.指定项目章程2.项目管理计划3.指导4。管理项目知识4.5.监控项目工作4.6 实施整体变更控制4.7 项目收尾合同5。范围管理收集需求定义范围创建WBS确认范围&#xff1b;控制范围Survive by day and develop by night. …

Redis内存耗尽后会发生什么?

Redis内存耗尽后会发生什么?前言设置有效期过期策略8 种淘汰策略LRU 算法Redis 如何管理热度数据LFU 算法访问频次递增访问频次递减前言 作为一台服务器来说&#xff0c;内存并不是无限的&#xff0c;所以总会存在内存耗尽的情况&#xff0c;那么当 Redis 服务器的内存耗尽后…

什么是Spring的AOP功能?

如果说 IOC 是 Spring 的核心&#xff0c;那么面向切面编程AOP就是 Spring 另外一个最为重要的核心。 AOP的定义 AOP &#xff08;Aspect Orient Programming&#xff09;,直译过来就是 面向切面编程,AOP 是一种编程思想&#xff0c;是面向对象编程&#xff08;OOP&#xff0…

学会这3个小技巧,轻松去图片水印

有些小伙伴在浏览个别平台时&#xff0c;会看到一些心水的图片&#xff0c;就想保存下来&#xff0c;拿来当头像壁纸&#xff0c;或是发朋友圈时用来配图。但是有些图片下载后会发现自带着平台水印&#xff0c;虽然理解它们是为了保护自身权益&#xff0c;但我们并不是进行商用…

【Java实战】大厂都是怎样进行单元测试的

目录 一、前言 二、单元测试 1.【强制】好的单元测试必须遵守 AIR 原则。 2.【强制】单元测试应该是全自动执行的&#xff0c;并且非交互式的。测试用例通常是被定期执行的&#xff0c;执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。不…

微服务的灰度发布就该这样设计

实际生产中如有需求变更&#xff0c;并不会直接更新线上服务&#xff0c;最通常的做法便是&#xff1a;切出线上的小部分流量进行体验测试&#xff0c;经过测试后无问题则全面的上线。 这样做的好处也是非常明显&#xff0c;一旦出现了BUG&#xff0c;能够保证大部分的客户端正…

Hygieia (Devops)开源-搭建步骤(一)

什么是Hygieia Hygieia出现在两个独立的仪表板中-一个用于工程师&#xff0c;另一个用于高管-直观地描绘了CICD管道。本质上&#xff0c;Hygieia本身是一个聚合器&#xff0c;可从团队在其CICD管道中使用的各种DevOps工具中提取数据&#xff0c;从而使其易于在仪表板视图中进行…

Docker搭建私有镜像仓库及推送、拉取私服镜像

1.搭建私有镜像仓库 搭建镜像仓库可以基于Docker官方提供的DockerRegistry来实现。 1.1 简化版镜像仓库 Docker官方的Docker Registry是一个基础版本的Docker镜像仓库&#xff0c;具备仓库管理的完整功能&#xff0c;但是没有图形化界面。 搭建方式比较简单&#xff0c;命令…

【配准:泛锐化】

SIPSA-Net: Shift-Invariant Pan Sharpening with Moving Object Alignment for Satellite Imagery &#xff08;SIPSA-Net&#xff1a;卫星影像平移不变平移锐化与运动目标对齐&#xff09; 全色锐化是合并高分辨率&#xff08;HR&#xff09;全色&#xff08;PAN&#xff0…

SpringBoot+Mybatis-plus整合easyExcel批量导入Excel到数据库+导出Excel

一、前言 今天小编带大家一起整合一下easyExcel&#xff0c;之所以用这个&#xff0c;是因为easyExcel性能比较好&#xff0c;不会报OOM&#xff01; 市面上常见的导入导出Excel分为三种&#xff1a; hutooleasyExcelpoi hutool和easyExcel都是对poi的封装&#xff0c;使用…

(12)点云数据处理学习——表面重建

1、主要参考 &#xff08;1&#xff09;官网的介绍 Surface reconstruction — Open3D 0.16.0 documentation &#xff08;2&#xff09;大佬的blog 三维点云重建 — open3d python_Coding的叶子的博客-CSDN博客_三维点云重建 &#xff08;3&#xff09;视频 Surface Rec…

一种隐私保护边云协同训练

研究背景 在边缘AI领域&#xff0c;联邦学习概念最先由google提出并在学界和业界得到广泛的讨论和发展。对于边缘AI&#xff0c;数据异构和数据隐私是两个主要的挑战&#xff0c;而将联邦学习应用在边缘计算中&#xff0c;可以协助解决这些挑战。FedAvg通过主动选择每一轮参与训…

Linux MySQL下载安装

介绍 查询当前系统中安装的所有软件 rpm -qa1. mysql的下载 1.1 查询当前系统中安装的名称带mysql的软件 rpm -qa | grep mysql如下所示没有输出说明是没有安装mysql 1.2 查询当前系统中安装的名称带mariadb的软件(如果有则卸载) rpm -qa | grep mariadb如下所示表示有ma…

Flink系列之Flink中StateBackend深入剖析和应用

title: Flink系列 一、Flink StateBackend 深入剖析和应用 ​ StateBackend 定义了状态是如何存储的&#xff0c;不同的 State Backend 会采用不同的方式来存储状态&#xff0c;核心入口是&#xff1a; StateBackend&#xff0c; Flink 提供了三种不同形式的存储后端&#xff…

Antd React UMI 配置优化 - 优化打包体积,提高打包速度

随着项目越来越大&#xff0c;打包后的体积很大&#xff0c;打包时间也很长&#xff0c;部署一次基本上要等十分钟左右。那么如何来优化打包体积和提高打包速度呢&#xff1f;一起来看看吧&#xff5e; 优化前打包体积分析 优化前个模块体积&#xff1a; 通过脚本命令&#x…

年产2万吨山楂酒工厂的设计—原料前处理工段及车间的设计(lunwen+cad图纸)

目 录 1前 言 1 1.1设计背景及目的 1 1.1.1山楂酒的介绍及功效 1 1.1.2目标人群 1 1.1.3发展前景 2 1.2设计依据 2 1.3设计内容 2 1.4原材料的选择 3 2工艺流程设计 5 2.1工艺流程的确定 5 2.2分选 6 2.3清洁消毒 8 2.4去核 9 2.4.1去核机的介绍及选择 9 2.4.2山楂核的处理 10 …