搜索引擎项目

目录

一、项目前置知识

1、项目背景

2、搜索引擎的相关原理

3、正排索引(foward index)

4、倒排索引

二、项目正文

1.数据去标签与数据清洗

提取title

构建url

将结果写入到文件中

2.建立索引

建立正排

建立倒排

3.编写搜索模块

4.编写http搜索服务器模块

5.编写前端模块


一、项目前置知识

1、项目背景

我们上网会浏览很多网站,但是有些网站并没有搜索功能。因此就需要一个搜索引擎来改善网站用户体验、提高信息检索效率等。这里以boost库的网站为例,boost库没有搜索功能,可以自己写一个搜索引擎。

2、搜索引擎的相关原理

客户端通过发送http请求的方式进行搜索

服务器将结果构建成html返回

3、正排索引(foward index)

正排索引是文档ID与文档内容的映射。

文档 ---> 单词

基本结构可以理解为:

docID1 -> word1、word2

docID2-> word、word2、word3

4、倒排索引

倒排索引是文档关键字与文档ID的映射。

单词 ---> 文档

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

二、项目正文

1.数据去标签与数据清洗

html的标签对我们来说没有价值,所以去掉这些标签。

只保留标签的内容

代码结构:

#include <iostream>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
#include "util.hpp"
#include "Log.hpp"
// using namespace std;

// html路径
const std::string input = "data/input";
const std::string output = "data/output/raw.bin";

typedef struct HtmlInfo
{
  std::string title;
  std::string content;
  std::string url;

} HtmlInfo_t;

bool EnumFile(const std::string &input, std::vector<std::string> *files_list);

bool Parse(const std::vector<std::string> &files_list, std::vector<HtmlInfo_t> *results);

bool Save(const std::vector<HtmlInfo_t> &results, const std::string &output);

int main()
{
  // 文件带路径存到file_list
  std::vector<std::string> files_list;

  if (!EnumFile(input, &files_list))
  {
    //std::cerr << "enum file name err" << std::endl;
    lg(Error, "enum file name error");
    return 1;
  }

  // 解析file_list里面的内容
  std::vector<HtmlInfo> results;

  if (!Parse(files_list, &results))
  {
    //std::cerr << "parse err" << std::endl;
    lg(Error, "parse error");
    return 2;
  }

  // 解析完把结果存到output里面,使用\3作为分隔符
  if (!Save(results, output))
  {
    //std::cerr << "save err" << std::endl;
    lg(Error, "save error");
    return 3;
  }

  return 0;
}

提取title

<开头,>结尾的数据是标签全部去掉。

构建url

根据官网url拼接自己的url

例如:官网url:https://www.boost.org/doc/libs/1_84_0/doc/html/accumulators.html

url_head = "https://www.boost.org/doc/libs/1_84_0/doc/html";

url_tail = /accumulators.html(删除自己目录前缀)

url = url_head + url_tail ; 相当于形成了一个官网链接

将结果写入到文件中

使用'\3'分割数据的模块

使用'\n'分割数据

2.建立索引

代码结构

#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <mutex>
#include <unordered_map>
#include "util.hpp"
#include "Log.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;
    std::string keyword;
    int weight;
  };

  typedef std::vector<InvertedElem> Inverted;
  class index
  {

  public:
  
    static index* GetInstance()
    {
      if(nullptr == instance)
      {
        _lock.lock();
        if(nullptr == instance)
        {
          instance = new index();
        }
        _lock.unlock();
      }
      return instance;
    }

    DocInfo *GetForwardIndex(uint64_t doc_id)
    {
      
    }

    // 倒排拉链
    Inverted *GetInvertedList(const std::string &key)
    {
      
    }

    // 构建倒排正排索引
    bool BuildIndex(const std::string &input)
    {
      
    }

  private:
    DocInfo *BuildForwarIndex(const std::string &line)
    {
      
    }

   

  public:
    ~index()
    {}
    
  private:
    index()
    {}
    index(const index&) = delete;
    index& operator=(const index&) = delete;

  private:
    static index *instance;
    static std::mutex _lock;
    // 正排用数组,下标表示ID
    std::vector<DocInfo> forward_index;
    // 倒排HASH表,关键字和倒排拉链的映射关系
    std::unordered_map<std::string, Inverted> inverted_index;
  };

     index * index::instance = nullptr;
     std::mutex index::_lock;
}

建立正排

DocInfo *BuildForwarIndex(const std::string &line)
    {
      // split string
      std::string SEP = "\3";
      std::vector<std::string> results;
      util::StringUtil::SplitString(line, &results, SEP);
      if (results.size() != 3)
      {
        return nullptr;
      }

      // fill DocInfo
      DocInfo doc;
      doc.title = results[0];
      doc.content = results[1];
      doc.url = results[2];
      doc.doc_id = forward_index.size();

      // insert forward_index
      forward_index.emplace_back(std::move(doc));
      return &forward_index.back();
    }

建立倒排

bool BuildInvertedIndex(const DocInfo &doc)
    {
      struct word_cnt
      {

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

        int title_cnt;
        int content_cnt;
      };

      std::unordered_map<std::string, word_cnt> word_map;
      
      //标题分词
      std::vector<std::string> title_words;
      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_cnt;
      util::JiebaUtil::CutString(doc.content, &content_cnt);
      for(auto s : content_cnt)
      {
        boost::to_lower(s);
        word_map[s].content_cnt++;
      }
      
#define W1 2
#define W2 1

      //填充
      for(auto &pair : word_map)
      {
        InvertedElem elem;
        elem.doc_id = doc.doc_id;
        elem.keyword = pair.first;
        elem.weight = pair.second.title_cnt * W1 + pair.second.content_cnt * W2; //权重
        inverted_index[pair.first].push_back(std::move(elem));
        
      }

      
      return true;
    }

3.编写搜索模块

主要完成:

1.把用户搜索的关键字分词

2.根据关键字查找索引

3.按照权重排序查找结果

4.把查找结果构建成json

#pragma once

#include "index.hpp"
#include "util.hpp"
#include <algorithm>
#include <jsoncpp/json/json.h>
namespace searcher
{
  struct UltInvertedElem
  {
    uint64_t doc_id;
    int weight;
    std::vector<std::string> keys;
    UltInvertedElem()
        : doc_id(0), weight(0)
    {
    }
  };

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

  public:
    void InitSearcher(const std::string &input)
    {
     
    }

    // seek用户搜索关键字,json_string给用户返回的结果
    void Search(const std::string &seek, std::string *json_string)
    {
      
    }
    //摘要
    std::string getdesc(const std::string &html_content, const std::string &key)
    {
      //可以自定义摘要
    }

  private:
    ns_index::index *index;
  };
};

4.编写http搜索服务器模块

使用cpp-httplib库

代码示例:

const std::string input = "data/output/raw.bin";
const std::string root_dir = "./rootdir";
int main()
{
  
  searcher::Searcher search;
  search.InitSearcher(input);

  httplib::Server svr;
  svr.set_base_dir(root_dir.c_str());
  svr.Get("/s", [&search](const httplib::Request &req, httplib::Response &res)
        {
        
        if(!req.has_param("word"))
        {
          
          res.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);
        res.set_content(json_string, "application/json"); 
        
        });
  svr.listen("0.0.0.0", 8080);
  return 0;
}

5.编写前端模块

这里前端代码使用chat-gpt 3.5编写完成。

html css效果

代码示例

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">
    <link rel="stylesheet" href="index.css">
    <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
    <script src="index.js"></script>

    <title>搜索</title>
</head>
<body>
    <div class="container">
        <div class="search">
            <input type="text" placeholder="请输入搜索关键字">
            <button onclick="Search()">搜索一下</button>
        </div>
        <div class="result">
        </div>
    </div>
    <script>
        
    </script>
</body>
</html>

CSS

@charset "utf-8";

* {
    padding: 0;
    margin: 0;
}

html,
body {
    font-family: Arial, sans-serif;
    background-color: #f2f2f2;
    height: 100%;
}


.container {

    max-width: 800px;
    margin: 50px auto;
    padding: 20px;
    background-color: #fff;
    border-radius: 8px;
    box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}


.container .search {

    position: relative;
    margin-bottom: 20px;
}


.container .search input {

    width: 780px;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
    font-size: 16px;
}


.container .search button {

    position: absolute;
    right: 0;
    top: 0;
    padding: 10px 20px;
    background-color: #1342c3;
    border: none;
    border-radius: 0 5px 5px 0;
    color: #fff;
    font-size: 16px;
    cursor: pointer;
}

.container .result {
    list-style-type: none;
    padding: 0;
    margin: 0;
}

.container .result .item {
    margin-bottom: 10px;
    padding: 10px;
    background-color: #f9f9f9;
    border-radius: 5px;
    border: 1px solid #ddd;
}

.container .result .item a {
    display: block;
    text-decoration: none;
    font-size: 20px;
    color: #4e6ef2;
    margin-bottom: 10px;
    padding: 10px;
    background-color: #f9f9f9;
    border-radius: 5px;
    border: 1px solid #ddd;
}

.container .result .item a:hover {
    text-decoration: underline;
    background-color: #e9e9e9;
}

.container .result .item p {
    display: inline-block;
    margin-top: 5px;
    font-size: 16px;
    font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
}

.container .result .item i {

    display: block;
    font-style: normal;
    color: green;
}

JS

function Search(){
 
    let query = $(".container .search input").val();
    console.log("query = " + query); 

  
    $.ajax({
        type: "GET",
        url: "/s?word=" + query,
        success: function(data){
            console.log(data);
            BuildHtml(data);
        }
    });
}

function BuildHtml(data){
    
    let result_lable = $(".container .result");
    result_lable.empty();

    for( let elem of data){
        let a_lable = $("<a>", {
            text: elem.title,
            href: elem.url,
            target: "_blank"
        });
        let p_lable = $("<p>", {
            text: elem.desc
        });
        let i_lable = $("<i>", {
            text: elem.url
        });
        let div_lable = $("<div>", {
            class: "item"
        });
        a_lable.appendTo(div_lable);
        p_lable.appendTo(div_lable);
        i_lable.appendTo(div_lable);
        div_lable.appendTo(result_lable);
    }
}

  • 21
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值