滁州seo网站推广方案,广州 网站建设模板,施工企业有没有制造费用,跨境电商如何做1.项目背景
对于Boost库来说#xff0c;它是没有搜索功能的#xff0c;所以我们可以实现一个Boost搜索引擎来实现一个简单的搜索功能#xff0c;可以更快速的实现Boost库的查找#xff0c;在这里#xff0c;我们实现的是站内搜索#xff0c;而不是全网搜索。
2.对于搜索…1.项目背景
对于Boost库来说它是没有搜索功能的所以我们可以实现一个Boost搜索引擎来实现一个简单的搜索功能可以更快速的实现Boost库的查找在这里我们实现的是站内搜索而不是全网搜索。
2.对于搜索引擎的相关宏观理解 3.搜索引擎技术栈及项目环境 技术栈c/c,c11,STL,Boost准标准库Jsoncppcppjiebacpp-httplib,html5,css,js, Ajax,jQuery不使用爬虫。 项目环境Centos 7云服务器vim/gcc(g)/Makefile , vs code 4.正排索引、倒排索引
正排索引通过文档ID寻找文档内容
比如文档1西游记有一只猴子 文档2西游记有一只六耳猕猴
文档1西游记有一只猴子文档2西游记有一只六耳猕猴
目标文档进行分词目的方便建立倒排索引和查找
文档1西游记/有/一只/猴子/
文档2西游记/有/一只/六耳/猕猴/ 注停止词了的吗athe一般我们在分词的时候可以不考虑 倒排索引根据文档内容分词整理不重复的各个关键字对应联系到文档ID的方案。
关键字文档ID, weight(权重)西游记文档1.文档2有文档1文档2一只文档1.文档2猴子文档1六耳文档2猕猴文档2
模拟一次查找的过程
用户输入西游记-倒排索引中查找-提取出文档id-根据正排索引-找到文档内容,通过文档标题内容URL对文档内容进行摘要-构建响应结果。
5.编写数据去标签与数据清洗的模块 Parser
5.1 什么是标签
我们既然要去标签那么就要知道什么是标签比如
//原始数据 - 去标签之后的数据
!DOCTYPE html PUBLIC -//W3C//DTD HTML 4.01 Transitional//EN
http://www.w3.org/TR/html4/loose.dtd
html !--这是一个标签--
head
meta http-equivContent-Type contenttext/html; charsetUTF-8
titleChapter 30. Boost.Process/title
link relstylesheet href../../doc/src/boostbook.css typetext/css
meta namegenerator contentDocBook XSL Stylesheets V1.79.1
link relhome hrefindex.html titleThe Boost C Libraries BoostBook Documentation
Subset
link relup hreflibraries.html titlePart I. The Boost C Libraries (BoostBook
Subset)
link relprev hrefpoly_collection/acknowledgments.html titleAcknowledgments
link relnext hrefboost_process/concepts.html titleConcepts
上述代码中!--这是一个标签--这就是一个标签这些标签对于我们的搜索来说是没有意义的所以我们需要去掉这些标签。标签分为两类一类是只有一个的,一类是有两个的,/,这些都是我们需要去掉的。
5.2 搜索内容
由于我们使用的是本地搜索即将Boost库下载下来通过本地搜索然后再通过建立网站实现站内搜索。所以第一步下载Boost库 Boost库官网Boost C Libraries Boost库下载Index of main/release/1.84.0/source 目前只需要boost库/doc/html目录下的html文件用它来进行建立索引不需要使用其他的 下载完成之后我们把他上传到云服务器上。之后建立一个保存*.html文件的文件夹我们之后需要使用它然后再创建一个保存去标签之后的*.html文件的文档方便我们搜索。
5.3 具体逻辑实现
const std::string src_path /home/SSS/data/input;//搜索路径
const std::string output /home/SSS/data/raw_html/raw.txt;//保存文档typedef struct DocInfo
{std::string title; // 文档标题std::string content; // 文档标题std::string url; // 文档url
} DocInfo_t;// : 输入
//*: 输出
//输入输出
bool EnumFile(const std::string src_path, std::vectorstd::string *files_list);
bool ParseHtml(const std::vectorstd::string files_list, std::vectorDocInfo_t *results);
bool SaveHtml(const std::vectorDocInfo_t results,const std::string output);int main()
{std::vectorstd::string files_list; //保存文件路径//递归式的把每个html文件名带路径保存到files_list中方便后期进行一个一个的文件进行读取if (!EnumFile(src_path, files_list)){std::cerr enum file name error1 std::endl;return 1;}// 按照files_list读取每个文件的内容并进行解析std::vectorDocInfo_t results;if (!ParseHtml(files_list, results)){//std::cout1std::endl;std::cerr enum file name error2 std::endl;return 2;}std::coutresults.size()std::endl;//把解析完毕的各个文件内容写入到output,按照\3作为每个文档的分割符 if (!SaveHtml(results,output)){std::cerr enum file name error3 std::endl;return 3;}return 0;
}
第一个函数通过对文件路径的处理将我们所需要的html文件保存到vector中方便我们后续查找。第二个函数进行对html文件的处理将html文件的标题内容url进行提取保存到数组中。第三个函数将处理好的内容放入到文件中方便后续进行建立索引。
三个函数的具体实现过程
第一步
bool EnumFile(const std::string src_path, std::vectorstd::string *files_list)
{namespace fs boost::filesystem;fs::path root_path(src_path);if (!fs::exists(root_path)) // 判断文件路径是否存在在这里我们使用Boost库中的函数判断文件路径是否存在{std::cerr root_path not exits std::endl;return false;}fs::recursive_directory_iterator end; // 递归的判断文件for (fs::recursive_directory_iterator iter(root_path); iter ! end; iter){if (!fs::is_regular_file(*iter)) // 文件是否为普通文件Boost库中判断文件是否为普通文件。{continue;}if (iter-path().extension() ! .html) // 文件是否为html文件Boost库函数{continue;}files_list-push_back(iter-path().string());//将所有带路径的html保存在files_list,方便后续进行文本分析}return true;
}
第二步
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())//c中文件处理函数{std::cerr open in errorstd::endl;return false;}std::string line;while(std::getline(in,line))//将文件内容写入到stringout中{*out line;//std::coutlinestd::endl;}in.close();//关闭文件return true;}};
}// 文档标题
static bool ParseTitle(const std::string file, std::string *title)
{std::size_t begin file.find(title);//对文档标题进行处理由于网页的标题保存在title标//签中以/title结尾所以中间的内容就是我们所要的标题//std::coutbeginstd::endl;if (begin std::string::npos){//std::cout1:beginstd::endl;return false;}std::size_t end file.find(/title);if (end std::string::npos){return false;}begin std::string(title).size();if (begin end){return false;}*title file.substr(begin, end - begin);//标题//std::cout*titlestd::endl;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_78_0/doc/html;std::string url_tail file_path.substr(src_path.size());*url url_head url_tail;//文档的urlreturn true;
}
// 对内容进行解析
bool ParseHtml(const std::vectorstd::string files_list, std::vectorDocInfo_t* results)
{for (const std::string file : files_list){std::string result; // 读取文件if (!ns_util::FileUtil::ReadFile(file, result)){continue;}DocInfo_t doc;// 解析文件提取titleif (!ParseTitle(result, doc.title)){//std::cout1std::endl;continue;}// 解析文件内容去标签if (!ParseContent(result, doc.content)){continue;}// 解析内容获得urlif (!ParseUrl(file, doc.url)){continue;}results-push_back(std::move(doc)); // 减少拷贝次数提高效率//std::coutresults-back()-titlestd::endl;}return true;
} ParseHtml()这个函数内部包含四个函数其中包括读取文件将文件标题内容url分别提取出来放入到数组中。 网站处理 官网URL样例 https /www.boost.org/doc/libs/1_79_0/doc/html/accumulators.html我们下载下来的url样例boost_1_79_0 / doc / html / accumulators.html我们拷贝到我们项目中的样例data / input / accumulators.htmlurl_head “https://www.boost.org/doc/libs/1_79_0/doc/html”;url_tail (data / input)(删除) / accumulators.html-url_tail / accumulators.htmlurl url_head url_tail; 相当于形成了一个官网链接。 5.4建立索引 实现原理 建立索引我们需要建立正排索引和倒排索引这需要我们建立一个struct,用来保存文档的title,content,url,id。因为正排索引是通过文档id来寻找文档内容的所以我们可以通过一个数组来实现id的编号倒排索引是通过关键字来锁定在哪一个文档id里面的所以我们可以通过建立一个哈希映射来完成通过关键字来寻找文档id。由于我们只需要建立一次索引所以我们可以通过创建一个单例模式来实现所以得初始化。 由于我们通过关键字可以映射到多个文档内容中所以我们可以通过建立相关性的方式来排序我们所查找得的文档内容。 相关性的建立由于相关性的问题涉及到的范围十分的大可能有相近词同义词不连续的关键词字符拆分的不确定性等等在这里我们使用关键字在文档中出现的频次来决定。由于关键字可以出现在标题中也可以出现在文档内容中所以我们通过简单粗暴的方式来解决即在标题中出现的关键在是10倍与在内容中出现的关键字。当然相关性的处理方式可以自己设定。 建立索引对象 //文档属性struct DocInfo{std::string _title;//文档标题std::string _contant;//文档内容std::string _url;//文档urluint64_t _id;//文档id};//倒排索引struct InvertedElem{std::string _word;//关键字uint64_t _id;//idint _weight;//权重InvertedElem():_weight(0){}};//倒排拉链typedef std::vectorInvertedElem InvertedList;
建立索引类 class index{private:index(){};index(const index)delete;index operator(const index)delete;public:static index* GetIndex()//初始化单例模式在这里我们使用懒汉模式来建立索引{if(nullptrinstance){mtx.lock();//多线程问题if(nullptrinstance){instancenew index();}mtx.nulock();}return instance;}public://正排索引DocInfo *GetForwardIndex(uint64_t _id)//根据文档id找文档内容{if(_id_forward_list.size())//数组下标从0开始{std::cerr doc_id out range, error! std::endl;return nullptr;}return _forward_list[_id];}//根据关键字获得文档倒排拉链倒排索引InvertedList *GetInvertedList(const std::string word){auto iter _inverted_index.find(word);//寻找关键字if(iter_inverted_index.end())//没找到{std::cerr _inverted_index out range, error! std::endl;return nullptr;}return (iter-second);}//根据去标签格式化之后的文档构建正排和倒排索引bool BuildIndex(const std::string input){std::ifstream in(input,std::ios::in|std::ios::binary);if(!in.is_open()){std::cerr open file errorstd::endl;}std::string line;int count0;while (std::getline(in, line))//读取内容{DocInfo *doc BuildForwardIndex(line);if (nullptr doc){std::cerr build line error std::endl; // for deubgcontinue;}BuildInvertedIndex(*doc);count;}return true;}private:DocInfo* BuildForwardIndex(const std::string line){//进行字符串切分std::vectorstd::string results;const std::string sep \3; //行内分隔符ns_util::StringUtil::Split(line, results, sep);//分词if(results.size()!3){std::coutsplit errorstd::endl;return nullptr;}//2. 字符串进行填充到DocIinfoDocInfo doc;doc._title results[0];//titledoc._content results[1];// contentdoc._url results[2];/// urldoc._id forward_index.size(); // 先进行保存id在插入对应的id就是当前doc在vector中的下标!// 3. 插入到正排索引的vector_forward_index.push_back(std::move(doc)); // doc,html文件内容return forward_index.back();}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_mapstd::string, word_cnt word_map; // 用来暂存词频的映射表// 对标题进行分词std::vectorstd::string title_words;ns_util::JiebaUtil::CutString(doc._title, title_words);//分词//计算权重for(auto e:title_words){boost::to_lower(s); // 需要统一转化成为小写word_map[s].title_cnt; // 如果存在就获取如果不存在就新建}//内容分词std::vectorstd::string contant_words;ns_util::JiebaUtil::CutString(doc._contant, contant_words);//分词for (std::string s : content_words){boost::to_lower(s);word_map[s].content_cnt;}//权重计算#define X 10#define Y 1for(auto e:word_map){InvertedElem item;item._id doc._id;item.word e.first;item.weight X * e.second.title_cnt Y * e.second.content_cnt; // 相关性InvertedList inverted_list inverted_index[e.first];_inverted_list.push_back(std::move(item));}}private:std::vectorDocInfo _forward_list;//正排//倒排索引一定是一个关键字和一组(个)InvertedElem对应[关键字和倒排拉链的映射关系]std::unordered_mapstd::string, InvertedList _inverted_index;static Index* instance;static std::mutex mtx;};index* index::instance nullptr;std::mutex index::mtx; 在创建索引的过程中我们需要对我们搜索的关键字进行分词而分词需要我们使用cppjieba分词工具通过使用分词工具来进行分词(当然有能力的同学可以自己实现一个分词程序)。
由于我们需要使用cppjieba分词工具我们就需要下载 cppjiaba下载地址git clone https://gitcode.com/yanyiwu/cppjieba-server.git 下载完成之后我们只需要使用 这个目录下的文件即可cppjieba/include/cppjieba 使用细节使用cppjieba需要注意我们需要自己执行cd cppjieba; cp -rf deps/limonp include/cppjieba/, 不然会编译报错。 我们可以建立软链接来使用cppjieba库。 对于cppjieba的使用来说我们可以到这个路径下查看使用情况:cppjieba/test. 对于使用cppjieba来说在这个项目中这需要掌握CutString()这个函数的使用其他的不用我们掌握太多当然如果你想要学习的更多可以学习其他函数的使用。 切分函数的实现
#pragma once
#include iostream
#include string
#include fstream
#include vector
#include mutex
#include unordered_map
#include boost/algorithm/string.hpp
#include cppjieba/Jieba.hppnamespace 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())//c中文件处理函数{std::cerr open in error2std::endl;return false;}std::string line;while(std::getline(in,line))//将文件内容写入到stringout中{*out line;//std::coutlinestd::endl;}in.close();//关闭文件return true;}};class StringUtil//切割字符串{public:static void Split(const std::string target, std::vectorstd::string *out, const std::string sep){//boost splitboost::split(*out, target, boost::is_any_of(sep), boost::token_compress_on);}};const char* const DICT_PATH /home/SSS/cppjieba/dict/jieba.dict.utf8;const char* const HMM_PATH /home/SSS/cppjieba/dict/hmm_model.utf8;const char* const USER_DICT_PATH /home/SSS/cppjieba/dict/user.dict.utf8;const char* const IDF_PATH /home/SSS/cppjieba/dict/idf.utf8;const char* const STOP_WORD_PATH /home/SSS/cppjieba/dict/stop_words.utf8;class JiebaUtil{private:static cppjieba::Jieba _jieba;//不去暂停词private://去暂停词//cppjieba::Jieba _jieba;//创建对象//std::unordered_mapstd::string, bool _stop_words;//哈希映射//static JiebaUtil* _instance;private://JiebaUtil():_jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH) {}//JiebaUtil(const JiebaUtil ) delete;//ns_util::JiebaUtil operaror(const JiebaUtil x)delete;public://去暂停词// static JiebaUtil* GetInstance()//初始化// {// static std::mutex _mtx;// if(nullptr_instance)// {// _mtx.lock();// if(nullptr_instance)// {// _instancenew JiebaUtil();// _instance-InitJiebaUtil();//加载文件// }// _mtx.unlock();// }// return _instance;// //return nullptr;// }// void InitJiebaUtil()// {// std::ifstream in(STOP_WORD_PATH);// if(!in.is_open()) // {// std::cerropen file errorstd::endl;// return;// }// std::string line;// while(std::getline(in,line))// {// _stop_words.insert({line, true});// }// in.close();// }// void CutStringHelper(const std::string src, std::vectorstd::string *out)//去暂停词// {// _jieba.CutForSearch(src, *out);//切分// for(auto iter out-begin(); iter ! out-end();)// {// auto it_stop_words.find(*iter);// if(it!_stop_words.end())// {// iter out-erase(iter);//去暂停词// }// else iter;// }// }public:static void CutString(const std::string src, std::vectorstd::string *out){//去暂停词如果云服务器配置高的情况下可以使用性能不高可能导致出现不必要的错误//ns_util::JiebaUtil::GetInstance()-CutStringHelper(src, out);//低配版不去暂停词_jieba.CutForSearch(src, *out);}};//不去暂停词cppjieba::Jieba JiebaUtil::_jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH);//去暂停词//JiebaUtil* _instancenullptr;
}由于我们使用的是cppjieba分词工具所以我们需要在使用的时建立分词路径防止出现错误在建立cppjieba类的时候我们也是创建的单例模式只需要建立一个对象即可不需要创建多个对象这和建立索引时候的功能是一样的。
对于切分字符来说也就是CutStringHelper()函数来说我们有两种方法实现它可以直接调用jieba分词工具直接使用生成关键字分词这样做的情况下可能会生成较多的暂停词搜索结果可能又不太准确的情况所以我们可以去掉暂停词这样可以减小关键词的数量减小查找次数二区掉暂停词依然需要调用Jieba分词库当中的函数。
对于去暂停词来说如果服务器配置不高的情况下不要去暂停词可能程序运行直接报错
5.5建立搜索
索引建议好之后我们进行搜索模块的建立对于搜索模块来说我们需要使用到的工具为Jsoncpp和cpp-httplib这两个工具使用这两个工具我们先下载下来。 Jsoncpp下载方式sudo yum install -y jsoncpp-devel json的功能是实现序列化和反序列化当然如果你可以使用其他序列化和反序列化的工具你也可是使用和其他的比如ProtoBuf,XML等。 cpp-httplib下载方式GitHub - yhirose/cpp-httplib: A C header-only HTTP/HTTPS server and client library 注意如果使用 centOS 环境yum源带的 g 最新版本是4.8.5发布于2015年年代久远。编译该项目会出现异常。将 gcc/g 升级为更高版本可解决问题。 # 升级参考https://juejin.cn/post/6844903873111392263 # 安装gcc 8版本 yum install -y devtoolset-8-gcc devtoolset-8-gcc-c # 启用版本 source /opt/rh/devtoolset-8/enable # 查看版本已经变成gcc 8.3.1 gcc -v # 启动 细节命令行启动只能在本会话有效 source /opt/rh/devtoolset-8/enable #可选如果想每次登陆的时候都是较新的gcc cat ~/.bash_profile # .bash_profile # Get the aliases and functions if [ -f ~/.bashrc ]; then . ~/.bashrc fi # User specific environment and startup programs PATH$PATH:$HOME/.local/bin:$HOME/bin export PATH #每次启动的时候都会执行这个命令 source /opt/rh/devtoolset-8/enable httplib使用· 测试代码 #include cpp-httplib/httplib.h
int main()
{httplib::Server svr;svr.Get(/hi, [](const httplib::Request req, httplib::Response rsp){rsp.set_content(你好,世界!, text/plain; charsetutf-8);});svr.listen(0.0.0.0, 8081);return 0;
} 搜索代码实现
建立搜索代码之前我们需要先建立索引这样才可以快速查找目标。所以我们先实现一个类包含我们所需要的内容因为我们是通过关键字找文档id,再通过文档id寻找文档内容在这期间我们需要通过权重来找到不同文档的先后顺序这样我们的类里面应该包含文档id权重关键字等。 struct InvertedElemPrint{uint64_t _id;int _weight;std::vectorstd::string _words;InvertedElemPrint() : _id(0), _weight(0) {}};
对于建立搜索得类 class Searcher{private:ns_index::index *_indexnullptr; // 建立索引进行查找public:Searcher() {}~Searcher() {}public:void InitSearch(const std::string input){// 获取index对象index ns_index::index::GetInstance();//根据index对象建立索引index-BulidIndex();}// query: 搜索关键字// json_string: 返回给用户浏览器的搜索结果void search(const std::string query, std::string *json_string){std::vectorstd::string words;ns_util::JiebaUtil::CutString(query, words);//2.[触发]:就是根据分词的各个词进行index查找,建立index是忽略大小写所以搜索关键字也需要//ns_index::InvertedList inverted_list_all; //内部InvertedElemstd::vectorInvertedElemPrint inverted_list_all;std::unordered_mapuint64_t, InvertedElemPrint tokens_map;for(auto word:words){boost::to_lower(word);ns_index::InvertedList *inverted_list index-GetInvertedList(word);if(invertednullptr) continue;for(const auto elem:*inverted_list){auto item tokens_map[elem._id]; //[]:如果存在直接获取如果不存在新建// item一定是doc_id相同的print节点item._id elem._id;item._weight elem._weight;item.words.push_back(elem._words);}}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 InvertedElemPrint e1, const InvertedElemPrint e2){return e1._weight e2._weight;});//序列化和反序列化Json::Value root;for(auto item : inverted_list_all){ns_index::DocInfo * doc index-GetForwardIndex(item._id);if(docnullptr) continue;Json::Value elem;elem[title]doc-_title;elem[desc] GetDesc(doc-content, item.words[0]); //content是文档的去标签的结果但是不是我们想要的我们要的是一部分 TODOelem[url]doc-url;elem[id] (int)item._id;elem[weight] item._weight; // int-stringroot.append(elem);}Json::FastWriter writer;*json_string writer.write(root);}//得到contantstd::string GetDesc(const std::string html_content, const std::string word){//找到word在html_content中的首次出现然后往前找50字节(如果没有从begin开始)往后找100字节(如果没有到end就可以的)//截取出这部分内容const int prev_step50;const int next_step150;//找到关键字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)); });if(iterhtml.content.end()) return NONE1;int pos std::distance(html_content.begin(), iter);int start0;int endhtml_content.size()-1;if(pos start prev_step) start pos - prev_step;if(pos end - next_step) end pos next_step;if(startend) return NONE2;std::string desc html_content.substr(start, end - start);desc ...;return desc;}};
} 对于搜索类来讲。我们通过实现三个函数来解决第一个函数为InitSearch(),实现初始化函数 完成获取对象和建立对象索引。 search()函数需要我们通过搜索关键字来返回给用户浏览器的搜索结果。这就需要通过序列化和反序列化来实现通过查找到的内容合并文档信息按照权重来进行排序。 GetDesc()函数通过对得到的信息进行打印具体实现看代码细节。 至此后端代码全部实现完成开始实现前端代码前端代码的实现需要html,css,js三种编程语言。
5.6 前端代码实现 html: 是网页的骨骼 -- 负责网页结构 css网页的皮肉 -- 负责网页美观的 jsjavascript网页的灵魂---负责动态效果和前后端交互 前端教程w3school 在线教程 前端代码的编写
!DOCTYPE html
html langen
headmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0script srchttp://code.jquery.com/jquery-2.1.1.min.js/scripttitleboost 搜索引擎/titlestyle/* 去掉网页中的所有的默认内外边距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: 14px;}/* 先选中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 {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
bodydiv classcontainerdiv classsearchinput typetext value请输入搜索关键字button onclickSearch()搜索一下/button/divdiv classresult!-- 动态生成网页内容 --!-- div classitema href#这是标题/ap这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要/pihttps://search.gitee.com/?skinrectyperepositoryqcpp-httplib/i/divdiv classitema href#这是标题/ap这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要/pihttps://search.gitee.com/?skinrectyperepositoryqcpp-httplib/i/divdiv classitema href#这是标题/ap这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要/pihttps://search.gitee.com/?skinrectyperepositoryqcpp-httplib/i/divdiv classitema href#这是标题/ap这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要/pihttps://search.gitee.com/?skinrectyperepositoryqcpp-httplib/i/divdiv classitema href#这是标题/ap这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要/pihttps://search.gitee.com/?skinrectyperepositoryqcpp-httplib/i/div --/div/divscriptfunction 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
/html5.7 添加日志
#pragma once
#include iostream
#include string
#include cstdio
#include cstdarg
#include ctime#define DEBUG 0
#define NORMAL 1
#define WARING 2
#define ERROR 3
#define FATAL 4
#define LOGFILE ./calculator.logconst char* gLevelMap[]{DEBUG,NORMAL,WARING,ERROR,FATAL};//完整的日志功能至少: 日志等级 时间 支持用户自定义(日志内容, 文件行文件名)可以将其写到文件中
void logMessage(int level,const char* format,...)
{//if(level0) printf(正确);char stdBuff[10024];time_t timestamp time(nullptr);//时间snprintf(stdBuff,sizeof(stdBuff),[%s] [%ld] , gLevelMap[level], timestamp);char logBuff[1024]; //自定义部分// va_list args;// va_start(args, format);// // vprintf(format, args);// vsnprintf(logBuffer, sizeof logBuffer, format, args);// va_end(args);snprintf(logBuff,sizeof(logBuff),[%s] [%ld] , gLevelMap[level], timestamp);FILE *fp fopen(LOGFILE, a);// printf(%s%s\n, stdBuffer, logBuffer);fprintf(fp, %s %s\n, stdBuff, logBuff);fclose(fp);
}日志分为5个等级每个等级对应不同的内容不同的等级需要不同的数字来对应。可以将其写到文件中将其保存。当然对于不同的级别日志可能会有不同的表现具体情况视情况而定。 5.8 结尾 最后需要将写好的程序部署到linux服务器上完成最后网站的建立。 nohup ./http_server log/log.txt 21 [1] 26890 项目扩展方向 1. 建立整站搜索 2. 设计一个在线更新的方案信号爬虫完成整个服务器的设计 3. 不使用组件而是自己设计一下对应的各种方案有时间有精力 4. 在我们的搜索引擎中添加竞价排名(强烈推荐) 5. 热次统计智能显示搜索关键词字典树优先级队列(比较推荐) 6. 设置登陆注册引入对mysql的使用(比较推荐的) 有兴趣的可以尝试做下。 项目完整代码网址
C-C项目: C/C项目 - Gitee.com