Boost搜索引擎

8 篇文章 0 订阅
6 篇文章 0 订阅

1.项目相关背景

百度,搜狐,360等搜索引擎;

boost的官网是没有站内搜索的。

2.搜索引擎的相关宏观原理

 爬虫程序我就不做了,受国家的法律法规的限制,我就通过正规的下载途径来做。

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

技术栈 : C/C++ C++11, STL, 准标准库 Boost Jsoncpp cppjieba cpp - httplib , html5 css js 、jQuery、 Ajax
项目环境: Centos 7 云服务器, vim/gcc(g++)/Makefile , vs2019 or vs code

4.正排序引VS倒排序引-搜索引擎具体原理

文档 1 : 雷军买了四斤小米
文档 2 : 雷军发布了小米手机
正排索引:就是从文档 ID 找到文档内容 ( 文档内的关键字 )

目标文档进行分词(目的:方便建立倒排索引和查找):

文档 1[ 雷军买了四斤小米 ]: 雷军 / / 四斤 / 小米 / 四斤小米
文档 2[ 雷军发布了小米手机 ] :雷军 / 发布 / 小米 / 小米手机
停止词:了,的,吗, a the,一般我们在分词的时候可以不考虑
倒排索引:根据文档内容,分词,整理不重复的各个关键字,对应联系到文档 ID 的方案
关键字(具有唯一性)文档,weight(权重)
雷军文档1,文档2
文档1
四斤文档1
小米        文档1,文档2
四斤小米文档1
发布文档2
小米手机文档2

模拟一次查找的过程:

用户输入:小米 -> 倒排索引中查找 -> 提取出文档 ID(1,2) -> 根据正排索引 -> 找到文档的内容 ->
title+conent (desc) +url 文档结果进行摘要 -> 构建响应结果

5.编写数据去标签与数据清洗的模块Parser

boost 官网: https : //www.boost.org/
// 目前只需要 boost_1_78_0/doc/html 目录下的 html 文件,用它来进行建立索引
去标签
[ whb@VM - 0 - 3 - centos boost_searcher ] $ touch parser . cc
// 原始数据 -> 去标签之后的数据
<! DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd" >
< html > <!-- 这是一个标签 -->
< head >
< meta http - equiv = "Content-Type" content = "text/html; charset=UTF-8" >
< title > Chapter 30. Boost . Process </ 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 = "poly_collection/acknowledgments.html" title = "Acknowledgments" >
< link rel = "next" href = "boost_process/concepts.html" title = "Concepts" >
</ head >
< body bgcolor = "white" text = "black" link = "#0000FF" vlink = "#840084" alink = "#0000FF" >
< table cellpadding = "2" width = "100%" >< tr >
< td valign = "top" >< img alt = "Boost C++ Libraries" width = "277" height = "86"
src = "../../boost.png" ></ td >
< td align = "center" >< a href = "../../index.html" > Home </ a ></ td >
< td align = "center" >< a href = "../../libs/libraries.htm" > Libraries </ a ></ td >
< td align = "center" >< a href = "http://www.boost.org/users/people.html" > People </ a ></ td >
< td align = "center" >< a href = "http://www.boost.org/users/faq.html" > FAQ </ a ></ td >
< td align = "center" >< a href = "../../more/index.htm" > More </ a ></ td >
</ tr ></ table >
.........
// <> : html 的标签,这个标签对我们进行搜索是没有价值的,需要去掉这些标签,一般标签都是成对出现的!
[ whb@VM - 0 - 3 - centos data ] $ mkdir raw_html
[ whb@VM - 0 - 3 - centos data ] $ ll
total 20
drwxrwxr - x 60 whb whb 16384 Mar 24 16 : 49 input // 这里放的是原始的 html 文档
drwxrwxr - x 2 whb whb 4096 Mar 24 16 : 56 raw_html // 这是放的是去标签之后的干净文档
[ whb@VM - 0 - 3 - centos input ] $ ls - Rl | grep - E '*.html' | wc - l
8141
目标:把每个文档都去标签,然后写入到同一个文件中!每个文档内容不需要任何 \n !文档和文档之间用 \3 区分
version1
类似: XXXXXXXXXXXXXXXXX\3YYYYYYYYYYYYYYYYYYYYY\3ZZZZZZZZZZZZZZZZZZZZZZZZZ\3
采用下面的方案:
version2 : 写入文件中,一定要考虑下一次在读取的时候,也要方便操作 !
类似: title\3content\3url \n title\3content\3url \n title\3content\3url \n ...
方便我们 getline ( ifsream , line ) ,直接获取文档的全部内容: title\3content\3url
编写 parser
#include<iostream>
#include<string>
#include<vector>
#include<boost/filesystem.hpp>
#include"util.hpp"


const std::string src_path = "data/input/";   //所有的html
const std::string output = "data/raw_html/raw.txt"; //解析所有完的html

typedef  struct DocInfo54
{
    std::string title;    //文档的标题
    std::string content;  //文档的内容
    std::string url;      //该文档在官网的url
}DocInfo_t;

bool EnumFile(const std::string &src_path,std::vector<std::string> *file_list);
bool ParseHtml(std::vector<std::string>& file_list,std::vector<DocInfo_t> *results);
bool SaveHtml(std::vector<DocInfo_t>& results,const std::string &output);



static bool ParseTitle(const std::string &result,std::string *title) 
{
    size_t begin = result.find("<title>");
    if(begin == std::string::npos)
    {
        return false;
    }
    size_t end = result.find("</title>");
    if(end == std::string::npos)
    {
        return false;
    }
    begin += std::string("<title>").size();
    if(begin > end)
    {
        return false;
    }
    *title = result.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(auto e :file)
    {
        switch (s)
        {
        case LABLE:
            if(e == '>')  //代表结束
                s = CONTENT;
            /* code */
            break;
        case CONTENT:
            if(e == '<') //代表开始
                s = LABLE;
            else
            {
                if(e == '\n') e = ' '; 
                *content += e;
            }
        break;
        default:
            break;
        }
    }
    return true;
}
static bool ParseUrl(const std::string &file,std::string *url)
{
    std::string url_head = "https://www.boost.org/doc/libs/1_79_0/doc/html/";
    std::string url_tail = file.substr(src_path.size());
    *url = url_head + url_tail;
    return true;
}

int main()
{
    //第一步拿到所有文件名
    std::vector<std::string> files_list;
    if(!EnumFile(src_path,&files_list))
    {
        std::cerr<<"enum file name error"<<std::endl;
        return 1;
    }
    //第二步解析文件
    std::vector<DocInfo_t> results;
    if(!ParseHtml(files_list,&results))
    {
        std::cerr<<"parse is error"<<std::endl;
        return 2;
    }
    //第三步,把解析完毕的各个文件内容,写入output,按照\3作为每个文档的分隔符
    if(!SaveHtml(results,output))
    {
        std::cerr<<"save html error"<<std::endl;
        return 3;
    }
    return 0;
}

bool EnumFile(const std::string &src_path,std::vector<std::string> *file_list)  //拿到所有html文件名
{
    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 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->push_back(it->path().string());
    }

    return true;
}
void ShowInfo(const DocInfo_t &doc)
{
    std::cout<<doc.title<<std::endl;
    std::cout<<doc.content<<std::endl;
    std::cout<<doc.url<<std::endl;
}
bool ParseHtml(std::vector<std::string>& file_list,std::vector<DocInfo_t> *results)//拿到所有html的标题,内容,url
{
    for(const auto  file : file_list)
    {
        //1读取文件
        std::string result;
        if(!ns_util::FileUtil::ReadFile(file,&result))
        {
            //文件读取失败
            continue;
        }
        DocInfo_t doc;
        //2提取标签
        if(!ParseTitle(result,&doc.title))
        {
            continue;;
        }
        //3提取内容
        if(!ParseContent(result,&doc.content))
        {
            continue;
        }
        //4提取url
        if(!ParseUrl(file,&doc.url))
        {
            continue;
        }
        //将结果出入到vector,这里有拷贝问题,以后在优化
        results->push_back(std::move(doc)); //采用右值,资源转移
        //for debug
        //ShowInfo(doc);
        //break;
    }
    return true;
}
bool SaveHtml(std::vector<DocInfo_t>& results,const std::string &output)
{
#define SEP '\3'
    std::ofstream of(output,std::ios::out | std::ios::binary);
    if(!of.is_open())
    {
        std::cerr<<"open"<<output<<"fail"<<std::endl;
        return false;
    }
    //写入文件
    for(const auto &item : results)
    {
        std::string  out_result;
        out_result = item.title;
        out_result += SEP;
        out_result += item.content;
        out_result += SEP;
        out_result += item.url;
        out_result += '\n';

        of.write(out_result.c_str(),out_result.size());
    }
    of.close();
    return true;
}
boost 开发库的安装
s udo yum install - y boost - devel // boost 开发库
提取 title

 

 在进行遍历的时候,只要碰到了 > ,就意味着,当前的标签被处理完毕. 只要碰到了 < 意味着新的标签开始了

构建URL

boost 库的官方文档,和我们下载下来的文档,是有路径的对应关系的
官网 URL 样例: https://www.boost.org/doc/libs/1_78_0/doc/html/accumulators.html
我们下载下来的 url 样例: boost_1_78_0/doc/html/accumulators.html
我们拷贝到我们项目中的样例: data/input/accumulators.html // 我们把下载下来的 boost doc/html/* copy
data/input/
url_head = "https://www.boost.org/doc/libs/1_78_0/doc/html";
url_tail = [data/input]( 删除 ) /accumulators.html -> url_tail = /accumulators.html
url = url_head + url_tail ; 相当于形成了一个官网链接

 将解析内容写入文件中 

// 见代码
采用下面的方案:
version2 : 写入文件中,一定要考虑下一次在读取的时候,也要方便操作 !
类似: title\3content\3url \n title\3content\3url \n title\3content\3url \n ...
方便我们 getline ( ifsream , line ) ,直接获取文档的全部内容: title\3content\3url

6.编写建立索引的模块Index

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include "util.hpp"
#include <mutex>
#include"log.hpp"
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 weigth;
    };

    //倒排拉链
    typedef std::vector<InvertedElem> InvertedList;

    class Index
    {
    private:
        std::vector<DocInfo> forward_index;                           //正排索引
        std::unordered_map<std::string, InvertedList> inverted_index; //倒排索引
        static Index *Instance;
        static std::mutex mtx;

    private:
        Index() = default;
        Index(const Index &) = delete;
        Index &operator=(const Index &) = delete;

    public:
        ~Index() = default;
        static Index *GetInstance()
        {
            if (nullptr == Instance)
            {
                mtx.lock();
                if (nullptr == Instance)
                {
                    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 is error" << std::endl;
                return nullptr;
            }
            return &forward_index[doc_id];
        }
        //根据关键字string,获得倒排拉链
        InvertedList *GetInvertedList(const std::string &word)
        {
            auto it = inverted_index.find(word);
            if (it == inverted_index.end())
            {
                std::cerr << word << "have no InvertedList" << std::endl;
                return nullptr;
            }
            return &(it->second);
        }
        //根据去标签,格式化之后的文档,构建正排索引和倒排索引
        // data/raw_html/raw.txt
        bool BuildIndex(const std::string &input)
        {
            std::ifstream in(input, std::ios::in | std::ios::binary);
            if (!in.is_open())
            {
                std::cerr << "sorry" << input << "open sorry" << 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 << std::endl; // for debug
                    continue;
                }
                BuildInvertedIndex(*doc);
                count++;
                if(count % 50 == 0) //std::cout<<"当前已经建立的索引文档:"<<count<<std::endl;
                LOG(NORMAL, "当前的已经建立的索引文档: " + std::to_string(count));
            }
            return true;
        }

    private:
        DocInfo *BuildForwardIndex(const std::string &line)
        {
            // 1进行字符串切分
            std::vector<std::string> results;
            const std::string seq = "\3";
            ns_util::StringUtil::Split(line, &results, seq);
            if (results.size() != 3)
            {
                return nullptr;
            }
            // 2将字符串进行填充到DocInfo
            DocInfo doc;
            doc.title = results[0];
            doc.content = results[1];
            doc.url = results[2];
            doc.doc_id = forward_index.size();
            // 3插入到正排索引的vector中
            forward_index.push_back(std::move(doc));
            return &forward_index.back();
        }
        bool BuildInvertedIndex(const DocInfo &doc)
        {
            // word 倒排拉链
            struct word_cnt
            {
                /* data */
                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::CurString(doc.title, &title_words);
            for (auto s : title_words)
            {
                boost::to_lower(s); //转化成小写
                word_map[s].title_cnt++;
            }
            //对文档内容进行分词
            std::vector<std::string> contnet_word;
            ns_util::JiebaUtil::CurString(doc.content, &contnet_word);
            for (auto s : contnet_word)
            {
                boost::to_lower(s); //转化成小写
                word_map[s].content_cnt++;
            }
#define X 10
#define Y 1
            for (auto &word_pair : word_map)
            {
                InvertedElem item;
                item.doc_id = doc.doc_id;
                item.word = word_pair.first;
                //相关性
                item.weigth = 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;    
        }
    };
    Index* Index::Instance = nullptr;
    std::mutex Index::mtx;
}
//jieba 的使用 --cppjieba
获取链接: git clone https : //gitcode.net/mirrors/yanyiwu/cppjieba.git
如何使用:注意细节,我们需要自己执行: cd cppjieba ; cp - rf deps / limonp include / cppjieba / , 不然会编 译报错

 

7.编写搜索引擎模块Searcher

#pragma once
#include "index.hpp"
#include <algorithm>
#include <jsoncpp/json/json.h>
// struct Com
// {
//     bool operator>(const InvertedElem& e1,const InvertedElem& e2)
//     {
//         return e1.weigth > e2.weigth;
//     }
// }
struct InvertedElemPrint
{
    uint64_t doc_id;
    int weight;
    std::vector<std::string> words;
    InvertedElemPrint() : doc_id(0), weight(0) {}
};
namespace ns_searcher
{
    class Searcher
    {
    private:
        ns_index::Index *index;

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

        std::string GetDesc(const std::string &html_src, const std::string &word)
        {
            const int prev_step = 50;
            const int next_step = 100;
            //找到首次出现的位置
            // std::size_t pos = html_src.find(word);  //错误原文档没有忽略大小写
            auto it = std::search(html_src.begin(), html_src.end(), word.begin(), word.end(),
                                  [](int a, int b)
                                  { return std::tolower(a) == std::tolower(b); });
            int pos = std::distance(html_src.begin(), it);
            if (pos == std::string::npos)
            {
                return "None1"; //不存在这种情况
            }
            // 2获取start end
            int start = 0;
            int end = html_src.size() - 1;

            if (pos > start + prev_step)
                start = pos - prev_step;
            if (pos < end - next_step)
                end = pos + next_step;

            if (start >= end)
                return "None2";
            return html_src.substr(start, end - start) + "...";
        }
        // query:搜索关键字
        // josn_string:返回给用户的搜索结果
        void Search(const std::string &query, std::string *json_string)
        {
            // 1.[分词]:对我们的query进行按照searcher的要求进行分词
            std::vector<std::string> words;
            ns_util::JiebaUtil::CurString(query, &words);
            // 2.[触发]:就是根据分词的各个“词”,进行Index查找
            // ns_index::InvertedList inverted_list_all;
            std::vector<InvertedElemPrint> inverted_list_all;

            std::unordered_map<uint64_t, InvertedElemPrint> tokens_map;
            for (auto &e : words)
            {
                boost::to_lower(e);
                ns_index::InvertedList *inverted_list = index->GetInvertedList(e);
                if (inverted_list == nullptr)
                    continue;
                //不完美的地方,可能有重复的文档
                //  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;
                    item.weight += elem.weigth;
                    item.words.push_back(elem.word);
                }
                for (const auto &elem : *inverted_list)
                {
                    auto &item = tokens_map[elem.doc_id]; //[]:如果存在直接获取,如果不存在新建
                    // item一定是doc_id相同的print节点
                    item.doc_id = elem.doc_id;
                    item.weight += elem.weigth;
                    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(inve rted_list_all.begin(), inverted_list_all.end(),\
            //               []( const ns_index::InvertedElem e1,  const ns_index::InvertedElem e2){
            //                return e1.weigth > e2.weigth;
            //                });
            //  std::sort(inverted_list_all.begin(),inverted_list_all.end(),Com());

            std::sort(inverted_list_all.begin(), inverted_list_all.end(),
                      [](const InvertedElemPrint &e1, const InvertedElemPrint &e2)
                      {
                          return e1.weight > e2.weight;
                      });
            // 4.[构建]:根据查找出来的结果,构建jsonc串 -----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;
                elem["desc"] = GetDesc(doc->content, item.words[0]); // content是文档的去标签的结果,但是不是我们想要的,我们要的是一部分 TODO
                elem["url"] = doc->url;
                // for deubg, for delete
                elem["id"] = (int)item.doc_id;
                elem["weight"] = item.weight; // int->string

                root.append(elem);
            }

            Json::StyledWriter writer;
            *json_string = writer.write(root);
        }
    };
}

 

搜索:雷军小米 -> 雷军、小米->查倒排->两个倒排拉链(文档1,文档2,文档1、文档2 

安装 jsoncpp  

sudo yum install - y jsoncpp - devel
获取摘要

关于调试  

把整个文件读到内存
先拿到标题,取到了标题。
对整个文件进行去标签,其中是包括标签的!!!!
实际如果一个词在 title 中出现,一定会被当标题 和 当内容分别被统计一次!!!

8.编写http server模块

cpp - httplib 库: https : //gitee.com/zhangkt1995/cpp-httplib?_from=gitee_search
注意: cpp - httplib 在使用的时候需要使用较新版本的 gcc centos 7 下默认 gcc 4.8.5
sudo yum install centos - release - scl scl - utils - build
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils # 安装gcc和g++
scl enable devtoolset - 7 bash
ls / opt / rh /   // 启动: 细节,命令行启动只能在本会话有效
// 可选:如果想每次登陆的时候,都是较新的 gcc
永久更新 vim ~ / . bash_profile 在文本末尾添加 scl enable devtoolset - 7 bash
安装 cpp - httplib
最新的 cpp - httplib 在使用的时候,如果 gcc 不是特别新的话有可能会有运行时错误的问题
建议: cpp - httplib 0.7.15
下载 zip 安装包,上传到服务器即可 或者直接get clone也行,在gitee上有项目,直接搜索cpp-hpplib
#include "cpp-httplib/httplib.h"
#include "searcher.hpp"
const std::string root_path = "./wwwroot";
const std::string input ="data/raw_html/raw.txt";
int main()
{ 
    ns_searcher::Searcher searcher;
    searcher.InitSearcher(input);
    httplib::Server svr; 
    svr.set_base_dir(root_path.c_str()); 
    svr.Get("/s", [&searcher](const httplib::Request &req, httplib::Response &rsp){
        if(!req.has_param("word"))
        {
            rsp.set_content("必须要有搜索关键字!","text/plain: chatset=utf-8");
            return;
        }
        std::string word = req.get_param_value("word");
       // std::cout<<"用户正在搜索:"<<word<<std::endl;
        LOG(NORMAL, "用户搜索的: " + word);
        std::string json_string;
        searcher.Search(word,&json_string);
        rsp.set_content(json_string,"application/json");
        });
        //rsp.set_content("你好,世界!", "text/plain; charset=utf-8"); 
        LOG(NORMAL, "服务器启动成功...");
        svr.listen("0.0.0.0", 8081); return 0;
}

9.编写前段模块

<!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 搜索引擎</title>
    <style>
        /* 去掉网页中的所有的默认内外边距,html的盒子模型 */
        * {
            /* 设置外边距 */
            margin: 0;
            /* 设置内边距 */
            padding: 0;
        }

        /* 将我们的body内的内容100%和html的呈现吻合 */
        html,
        body {
            height: 100%;
        }

        /* 类选择器.container */
        .container {
            /* 设置div的宽度 */
            width: 800px;
            /* 通过设置外边距达到居中对齐的目的 */
            margin: 0px auto;
            /* 设置外边距的上边距,保持元素和网页的上部距离 */
            margin-top: 15px;
        }

        /* 复合选择器,选中container 下的 search */
        .container .search {
            /* 宽度与父标签保持一致 */
            width: 100%;
            /* 高度设置为52px */
            height: 52px;
        }

        /* 先选中input标签, 直接设置标签的属性,先要选中, input:标签选择器*/
        /* input在进行高度设置的时候,没有考虑边框的问题 */
        .container .search input {
            /* 设置left浮动 */
            float: left;
            width: 600px;
            height: 50px;
            /* 设置边框属性:边框的宽度,样式,颜色 */
            border: 1px solid black;
            /* 去掉input输入框的有边框 */
            border-right: none;
            /* 设置内边距,默认文字不要和左侧边框紧挨着 */
            padding-left: 10px;
            /* 设置input内部的字体的颜色和样式 */
            color: #CCC;
            font-size: 15px;
        }

        /* 先选中button标签, 直接设置标签的属性,先要选中, button:标签选择器*/
        .container .search button {

            /* 设置left浮动 */
            float: left;
            width: 150px;
            height: 52px;
            /* 设置button的背景颜色,#4e6ef2 */
            background-color: #4e6ef2;
            /* 设置button中的字体颜色 */
            color: #FFF;
            /* 设置字体的大小 */
            font-size: 19px;
            font-family: Georgia, 'Times New Roman', Times, serif;
        }

        .container .result {
            width: 100%;
        }

        .container .result .item {
            margin-top: 15px;
        }

        .container .result .item a {
            /* 设置为块级元素,单独站一行 */
            display: block;
            /* a标签的下划线去掉 */
            text-decoration: none;
            /* 设置a标签中的文字的字体大小 */
            font-size: 20px;
            /* 设置字体的颜色 */
            color: #4e6ef2;
        }

        .container .result .item a:hover {
            /*设置鼠标放在a之上的动态效果*/
            text-decoration: underline;
        }

        .container .result .item p {
            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;
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="search"> <input type="text" value="输入搜索关键字..."> 
            <button onclick="Search()">搜索一下</button>
         </div>
        <div class="result">
            <!-- <div class="item"> <a href="#">这是标题</a>
                <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘 要这是摘要这是摘要这是摘要这是摘要</p>
                <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
            </div>
            <div class="item"> <a href="#">这是标题</a>
                <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘 要这是摘要这是摘要这是摘要这是摘要</p>
                <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
            </div>
            <div class="item"> <a href="#">这是标题</a>
                <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘 要这是摘要这是摘要这是摘要这是摘要</p>
                <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
            </div>
            <div class="item"> <a href="#">这是标题</a>
                <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘 要这是摘要这是摘要这是摘要这是摘要</p>
                <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
            </div>
            <div class="item"> <a href="#">这是标题</a>
                <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘 要这是摘要这是摘要这是摘要这是摘要</p>
                <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
            </div> -->
        </div>
    </div>
    <script>
        function Search() { // 是浏览器的一个弹出框 
            // alert("hello js!"); 
            //1. 提取数据, $可以理解成就是JQuery的别称
            let query = $(".container .search input").val();
            console.log("query = " + query);
            //console是浏览器的对话框,可以用来进行查看js数据 
            //2. 发起http请求,ajax: 属于一个和后端进行数据交互的函数,JQuery中的 
            $.ajax({
                type: "GET",
                url: "/s?word=" + query,
                success: function (data) {
                    console.log(data);
                    BuildHtml(data);
                }
            });
        }
        function BuildHtml(data) {
            // 获取html中的result标签
            let result_lable = $(".container .result");
            // 清空历史搜索结果
            result_lable.empty();
            for (let elem of data) {
                // console.log(elem.title); 
                // console.log(elem.url);
                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);
            }
        }
    </script>
</body>

</html>

makefile

Parser=parser
DUG=debug
HTTP_SEARCHER=http_searcher
cc=g++

.PHONY:all
all:$(Parser) $(DUG) $(HTTP_SEARCHER)
$(Parser):parser.cc
	$(cc) -o $@ $^ -lboost_system -lboost_filesystem -std=c++11
$(DUG):debug.cc
	$(cc) -o $@ $^  -ljsoncpp -std=c++11
$(HTTP_SEARCHER):http_searcher.cc
	$(cc) -o $@ $^  -ljsoncpp -lpthread -std=c++11

	
.PHONY:clean
clean:
	rm -rf $(Parser) $(DUG) $(HTTP_SEARCHER)

 

10.项目效果

 

 11.添加日志

#pragma once

#include <iostream>
#include <string>
#include <ctime>

#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;
}
结项总结
项目扩展方向
1. 建立整站搜索
2. 设计一个在线更新的方案,信号,爬虫,完成整个服务器的设计
3. 不使用组件,而是自己设计一下对应的各种方案(有时间,有精力)
4. 在我们的搜索引擎中,添加竞价排名 ( 强烈推荐 )
5. 热次统计,智能显示搜索关键词(字典树,优先级队列) ( 比较推荐 )
6. 设置登陆注册,引入对 mysql 的使用 ( 比较推荐的 )
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值