基于boost准标准库的搜索引擎项目
零 项目背景/原理/技术栈
1.介绍boost准标准库
2.项目实现效果
3.搜索引擎宏观架构图
这是一个基于Web的搜索服务架构
- 客户端-服务器模型:采用了经典的客户端-服务器模型,用户通过客户端与服务器交互,有助于集中管理和分散计算。
- 简单的用户界面:客户端似乎很简洁,用户通过简单的HTTP请求与服务端交互,易于用户操作。
- 搜索引擎功能:服务器端的搜索器能够接收查询请求,从数据存储中检索信息,这是Web搜索服务的核心功能。
- 数据存储:有专门的存储系统用于存放数据文件(如HTML文件),有助于维护数据的完整性和持久性。
- 模块分离:搜索器、存储和处理请求的模块被分开,这有助于各模块独立更新和维护.
4.搜索过程的原理~正排,倒排索引
5.技术栈和项目环境,工具
技术栈:C/C++ C++11 STL boost准标准库 JsonCPP cppjieba cpp-httplib
html css js jQuery Ajax
项目环境:Centos7 华为云服务器 gcc/g++/makefile Vscode
一 Paser数据清洗,获取数据源模块
const std::string src_path = "data/input/";
const std::string output_file = "data/output/dest.txt";
class DocInfo
{
public:std::string _title;std::string _content;std::string _url;
};
Paser模块主逻辑
int main()
{std::vector<std::string> files_list;// 第一步 把搜索范围src_path内的所有html的路径+文件名放到 files_list中if (!EnumFileName(src_path, &files_list)){lg(_Error,"%s","enum filename err!");exit(EnumFileNameErr);}// 第二步 将files_list中的文件打开,读取并解析为DocInfo后放到 web_documents中std::vector<DocInfo> html_documents;if (!ParseHtml(files_list, &html_documents)){lg(_Error,"%s","parse html err!");exit(ParseHtmlErr);}// 第三步 将web_documents的信息写入到 output_file文件中, 以\3为每个文档的分隔符if (!SaveHtml(html_documents, output_file)){lg(_Error,"%s","save html err!");exit(SaveHtmlErr);}
}
-
枚举文件:从给定的源路径(
src_path
)中枚举所有HTML文件,并将它们的路径和文件名放入files_list
中。 -
解析HTML:读取
files_list
中的每个文件,解析它们为DocInfo
对象(可能包含标题、URL、正文等元素),然后存储到html_documents
向量中。 -
保存文档:将
html_documents
中的文档信息写入到指定的输出文件output_file
中,文档之间用\3
(ASCII码中的End-of-Text字符)分隔。
EnumFileName
bool EnumFileName(const std::string &src_path, std::vector<std::string> *files_list)
{namespace fs = boost::filesystem;fs::path root_path(src_path);if (!fs::exists(root_path)) // 判断路径是否存在{lg(_Fatal,"%s%s",src_path.c_str()," is not exist");return false;}// 定义一个空迭代器,用来判断递归是否结束fs::recursive_directory_iterator end;// 递归式遍历文件for (fs::recursive_directory_iterator it(src_path); it != end; it++){if (!fs::is_regular(*it))continue; // 保证是普通文件if (it->path().extension() != ".html")continue; // 保证是.html文件files_list->push_back(it->path().string()); // 插入的都是合法 路径+.html文件名}return true;
}
ParseHtml
bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo> *html_documents)
{for (const std::string &html_file_path : files_list){// 第一步 遍历files_list,根据路径+文件名,读取html文件内容std::string html_file;if (!ns_util::FileUtil::ReadFile(html_file_path, &html_file)){lg(_Error,"%s","ReadFile err!");continue;}DocInfo doc_info;// 第二步 解析html文件,提取titleif (!ParseTitle(html_file, &doc_info._title)){lg(_Error,"%s%s","ParseTitle err! ",html_file_path.c_str());continue;}// 第三步 解析html文件,提取content(去标签)if (!ParseContent(html_file, &doc_info._content)){lg(_Error,"%s","ParseContent err!");continue;}// 第四步 解析html文件,构建urlif (!ParseUrl(html_file_path, &doc_info._url)){lg(_Error,"%s","ParseUrl err!");continue;}// 解析html文件完毕,结果都保存到了doc_info中// ShowDcoinfo(doc_info);html_documents->push_back(std::move(doc_info)); // 尾插会拷贝,效率不高,使用move}lg(_Info,"%s","ParseHtml success!");return true;
}
1.ReadFile
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()){lg(_Fatal,"%s%s%s","ReadFile:",file_path.c_str()," open err!");return false;}std::string line;while (std::getline(in, line)){*out += line;}in.close();return true;}};
2.ParseTitle
static bool ParseTitle(const std::string &html_file, std::string *title)
{size_t left = html_file.find("<title>");if (left == std::string::npos)return false;size_t right = html_file.find("</title>");if (right == std::string::npos)return false;int begin = left + std::string("<title>").size();int end = right;// 截取[begin,end-1]内的子串就是标题内容if (end-begin<0){lg(_Error,"%s%s%s","ParseTitle:",output_file.c_str(),"has no title");return false;}std::string str = html_file.substr(begin, end - begin);*title = str;return true;
}
3.ParseContent
static bool ParseContent(const std::string &html_file, std::string *content)
{// 利用简单状态机完成去标签工作enum Status{Lable,Content};Status status = Lable;for (char ch : html_file){switch (status){case Lable:if (ch == '>')status = Content;break;case Content:if (ch == '<')status = Lable;else{// 不保留html文本中自带的\n,防止后续发生冲突if (ch == '\n')ch = ' ';content->push_back(ch);}break;default:break;}}return true;
}
4.ParseUrl
static bool ParseUrl(const std::string &html_file_path, std::string *url)
{std::string url_head = "https://www.boost.org/doc/libs/1_84_0/doc/html";std::string url_tail = html_file_path.substr(src_path.size());*url = url_head + "/" + url_tail;return true;
}
SaveHtml
doc_info内部用\3分隔,doc_info之间用\n分隔
//doc_info内部用\3分隔,doc_info之间用\n分隔
bool SaveHtml(const std::vector<DocInfo> &html_documents, const std::string &output_file)
{const char sep = '\3';std::ofstream out(output_file, std::ios::out | std::ios::binary|std::ios::trunc);if (!out.is_open()){lg(_Fatal,"%s%s%s","SaveHtml:",output_file.c_str()," open err!");return false;}for(auto &doc_info:html_documents){std::string outstr;outstr += doc_info._title;outstr += sep;outstr += doc_info._content;outstr += sep;outstr+= doc_info._url;outstr+='\n';out.write(outstr.c_str(),outstr.size());}out.close();lg(_Info,"%s","SaveHtml success!");return true;
}
二 Index建立索引模块
索引的相关结构
class DocInfo // 解析后的html文档的相关信息{public:std::string _title;std::string _content;std::string _url;uint64_t _doc_id;};class InvertedElem{public:uint64_t _doc_id;std::string _word;int _weight; // 关键词word在该文档内的权重,方便后续查找时按顺序显示};
-
私有化构造函数和析构函数:通过将构造函数和析构函数设为私有,禁止了外部通过常规方式创建
Index
类的实例。 -
禁用拷贝构造函数和拷贝赋值操作符:通过将拷贝构造函数和赋值操作符标记为
delete
,防止了类的拷贝,确保了单例的唯一性。 -
静态实例和互斥锁:用静态成员变量
instance
来存储这个类的唯一实例,并使用静态互斥锁_mutex
来保证在多线程环境下的线程安全。 -
GetInstance方法:这是一个静态方法,用于获取
Index
类的唯一实例。如果instance
为空,则实例化一个新的Index
对象。这个方法在创建实例之前和之后都有一次判断实例是否为空的逻辑,这是“双重检查锁定”模式,它可以减少每次调用GetInstance
方法时所需的锁定操作,从而提高性能。 -
正向索引和倒排索引的存储结构:类中定义了两个私有成员变量来存储正向索引
_forward_index
和倒排索引_inverted_index
。正向索引是一个vector
,存储文档信息DocInfo
对象,而倒排索引是一个unordered_map
,它映射一个字符串(关键词)到一个InvertedList
(vector<InvertedElem>
)。 -
构建索引的方法:类提供了两个方法
BuildForwardIndex
和BuildInvertedIndex
,分别用于构建正向索引和倒排索引。这两个方法的具体实现在这个代码片段中没有给出。 -
检索功能的方法:
BuildIndex
方法可能用于建立索引,GetForwardIndex
和GetInvertedList
方法分别用于获取正向索引和倒排索引中的数据。
BuildIndex
bool BuildIndex(const std::string &input_path) // 构建索引{std::fstream in(input_path, std::ios::in | std::ios::binary);if (!in.is_open()){lg(_Fatal,"%s%s%s","BuildIndex fail! ",input_path.c_str()," cannot open");return false;}std::string html_line; // 每个html的的DocInfo以\n间隔int cnt=1; //debugwhile (std::getline(in, html_line)){DocInfo *doc_info = BuildForwardIndex(html_line);if (doc_info == nullptr){lg(_Error,"%s%s%s%s","BuildForwardIndex fail! ","who? ",html_line.c_str()," continue next html");continue;}if (!BuildInvertedIndex(*doc_info)){lg(_Error,"%s%s%d","BuildInvertedIndex fail! ","id: ",doc_info->_doc_id);continue;}++cnt;if(cnt%100 == 0)std::cout<<"cnt:"<<cnt<<std::endl; }lg(_Info,"%s%d","BuildIndex over cnt:",cnt);in.close();return true;}
字符串切分
class StringUtil{public:static void SplitString(const std::string &str, std::vector<std::string> *ret_strs, const std::string &sep){boost::split(*ret_strs, str, boost::is_any_of(sep), boost::token_compress_on);}};const char *const DICT_PATH = "./dict/jieba.dict.utf8";const char *const HMM_PATH = "./dict/hmm_model.utf8";const char *const USER_DICT_PATH = "./dict/user.dict.utf8";const char *const IDF_PATH = "./dict/idf.utf8";const char *const STOP_WORD_PATH = "./dict/stop_words.utf8";class JiebaUtil{public:static void CutString(const std::string &src,std::vector<std::string> *ret){_jieba.CutForSearch(src,*ret);}private:static cppjieba::Jieba _jieba;};cppjieba::Jieba JiebaUtil::_jieba(DICT_PATH,HMM_PATH,USER_DICT_PATH,IDF_PATH,STOP_WORD_PATH);
这段代码展示了两个C++工具类StringUtil
和JiebaUtil
,它们都包含静态方法,用于处理字符串分割和中文分词功能。
-
StringUtil
类:- 这个类提供了一个静态方法
SplitString
,它使用Boost库的split
函数来将字符串str
依据分隔符sep
分割,并将结果存储在传入的向量ret_strs
中。 boost::token_compress_on
参数指定如果分隔符在字符串中连续出现,那么多个分隔符将被视作一个。
- 这个类提供了一个静态方法
-
JiebaUtil
类:- 这个类提供了一个静态方法
CutString
,它用于中文的分词。方法接受一个源字符串src
和一个用于存储分词结果的向量ret
。 - 类包含一个私有静态成员
_jieba
,它是cppjieba::Jieba
类的一个实例。cppjieba::Jieba
是一个中文分词库的C++实现。 - 类在底部使用
_jieba
成员的静态初始化语法来初始化这个Jieba分词器实例。
- 这个类提供了一个静态方法
常量路径定义: 代码中还定义了一些指向分词所需字典文件的路径常量:
DICT_PATH
:指向基础字典文件。HMM_PATH
:指向用于HMM(隐马尔可夫模型)的模型文件。USER_DICT_PATH
:指向用户自定义的词典文件。IDF_PATH
:指向逆文档频率(IDF)字典文件。STOP_WORD_PATH
:指向停用词字典文件。
BuildForwardIndex
DocInfo *BuildForwardIndex(const std::string &html_line){// 1~ 切分字符串std::vector<std::string> ret_strs;const std::string sep = "\3";ns_util::StringUtil::SplitString(html_line, &ret_strs, sep);if (ret_strs.size() < 3)return nullptr;// 2~ 填充doc_infoDocInfo doc_info;doc_info._title = ret_strs[0];doc_info._content = ret_strs[1];doc_info._url = ret_strs[2];doc_info._doc_id = _forward_index.size(); // 插入第一个时id== size ==0// 3~ 插入到正排索引_forward_index_forward_index.push_back(std::move(doc_info));return &_forward_index.back();}
BuildInvertedIndex
bool BuildInvertedIndex(const DocInfo &doc_info){struct words_cnt{int title_cnt = 0;int content_title = 0;};// 1~ 对doc_info的title和content进行分词std::unordered_map<std::string, words_cnt> words_frequency;std::vector<std::string> words_title;//保存title分词后的结果std::vector<std::string> words_content;//保存content分词后的结果ns_util::JiebaUtil::CutString(doc_info._title, &words_title);ns_util::JiebaUtil::CutString(doc_info._content, &words_content);// 2~ 统计词频填充words_frequencyfor (auto &word : words_title)//to_lower转换不能是const修饰{boost::to_lower(word); // 需要统一转化成为小写,因为搜索时不区分大小写//boost::to_lower_copy(word);words_frequency[word].title_cnt++;}for (auto &word : words_content){boost::to_lower(word); // 需要统一转化成为小写,因为搜索时不区分大小写//boost::to_lower_copy(word);words_frequency[word].content_title++;}// 3~ 自定义权重 title:content = 10:1static const int title_weight = 10;static const int content_weight = 1;// 4~ 对words_frequency内的每个关键词创建InvertedElem并填充for (const auto &kv : words_frequency){InvertedElem inverted_ele;inverted_ele._doc_id = doc_info._doc_id;inverted_ele._word = kv.first;inverted_ele._weight =title_weight * kv.second.title_cnt +content_weight * kv.second.content_title;// 5~ 将该文档的所有InvertedElem分别插入到倒排索引 _inverted_index中InvertedList &inverted_list = _inverted_index[kv.first];inverted_list.push_back(std::move(inverted_ele));//_inverted_index[kv.first].push_back(std::move(inverted_ele));}return true;}
三 Searcher搜索模块
InitSearcher
Search
-
分词处理: 用户输入的查询字符串
query
通过ns_util::JiebaUtil::CutString
函数进行分词,分词结果存储在key_words
向量中。 -
搜索和去重: 遍历分词后的关键词。对每个关键词,都先将其转换为小写以实现大小写不敏感的搜索,然后获取对应的倒排索引链(
InvertedList
)。如果倒排索引链存在,遍历链中的每个元素,并在tokens_map
中以文档ID为键聚合数据,合并权重和关键词,实现对同一文档的去重。 -
排序: 将
tokens_map
中聚合的结果转移到一个向量inverted_ele_all
中,并根据权重对其进行降序排序,这样权重高的(更相关的)文档会排在前面。 -
构建JSON结果: 遍历排序后的
inverted_ele_all
向量,对于每个元素,使用它的文档ID去查询正向索引获取文档的详细信息,如标题、内容和URL。将这些信息构建成一个JSON对象,并添加到一个Json::Value
类型的ret
数组中。函数最后使用Json::FastWriter
将ret
转换成JSON格式的字符串并存储在json_str
指针指向的字符串中。
// query是用户输入的搜索关键字// json_str是返回给用户浏览器的搜索结果void Search(const std::string &query, std::string *json_str){// 1~对query进行分词std::vector<std::string> key_words;ns_util::JiebaUtil::CutString(query, &key_words);std::unordered_map<uint64_t,InvertedElemDedup> tokens_map;//去重id后的结果for (auto &key_word : key_words){// 查询的关键词全部转换为小写,提取出来的信息不区分大小写boost::to_lower(key_word);// 2~对分词结果 分别进行搜索ns_index::Index::InvertedList *inverted_list =_index->GetInvertedList(key_word);if (inverted_list == nullptr){continue; // 这个词没能找到 对应的倒排拉链}for(auto &elem: *inverted_list){auto& dedup_ele = tokens_map[elem._doc_id];dedup_ele._doc_id = elem._doc_id;dedup_ele._weight += elem._weight;dedup_ele._words.push_back(elem._word);}}// 优化点:对所有的ele合并后指向的doc_id进行去重 这里只关心weight和idstd::vector<InvertedElemDedup> inverted_ele_all;for(auto &kv:tokens_map){inverted_ele_all.push_back(std::move(kv.second));}// 3~对所有的inverted_element按照wegiht排序sort(inverted_ele_all.begin(), inverted_ele_all.end(),[](InvertedElemDedup& left,InvertedElemDedup& right){return left._weight > right._weight;});// 4~序列化,构建json串返回给用户 -- 使用jsoncppJson::Value ret;int cnt = 0; // debugfor (auto &ele : inverted_ele_all){ns_index::DocInfo *doc_info = _index->GetForwardIndex(ele._doc_id);if (doc_info == nullptr)continue;Json::Value element;element["title"] = doc_info->_title;// 搜索时需要摘要,不是所有的content,后面优化element["desc"] = GetDesc(doc_info->_content, ele._words[0]);element["url"] = doc_info->_url;// element["weight"] = ele._weight;// element["word"] = ele._words[0];// element["id"] = (int)ele._doc_id; // json自动将int转化为stringret.append(element);}//Json::StyledWriter writer;Json::FastWriter writer;*json_str = writer.write(ret);}private:std::string GetDesc(const std::string &html_content, const std::string &word){// 找到word在content中首次出现的位置,向前截取prev_stepbyte,向后截取next_stepbyte// 向前<prev_step则从content开头开始,向后不足next_step则到content结尾// 1~ 找到word首次出现的位置std::cout << word << std::endl; // debugauto iter = std::search(html_content.begin(), html_content.end(), word.begin(), word.end(),[](int l, int r){return std::tolower(l) == std::tolower(r);});if (iter == html_content.end()){lg(_Error,"%s","content里面没找到word");return "None1";}// 找到了int pos = std::distance(iter, html_content.end());const int prev_step = 50;const int next_step = 50;// 2~ 确定begin和end位置int begin = pos >= prev_step ? pos - prev_step : 0;int end = (pos + next_step) < html_content.size() ? pos + next_step : html_content.size();// 3~ 截取描述子串[begin,end)并返回if (begin >= end) // end一定大于begin{lg(_Error,"%s","begin > end 越界了");return "None2";}std::string desc = html_content.substr(begin, end - begin);desc += "...";return desc;}};
四 http_server模块
const std::string input = "data/output/dest.txt";//从input里读取数据构建索引
const std::string root_path = "./wwwroot";
int main()
{std::unique_ptr<ns_searcher::Searcher> searcher(new ns_searcher::Searcher());searcher->SearcherInit(input);httplib::Server svr;svr.set_base_dir(root_path.c_str()); // 设置根目录// 重定向到首页svr.Get("/", [](const httplib::Request &, httplib::Response &rsp){ rsp.set_redirect("/home/LZF/boost_searcher_project/wwwroot/index.html"); });svr.Get("/s",[&searcher](const httplib::Request &req,httplib::Response &rsp){if(!req.has_param("word")){rsp.set_content("无搜索关键字!","test/plain,charset=utf-8");return;}std::string json_str;std::string query = req.get_param_value("word");std::cout<<"用户正在搜索: "<<query<<std::endl;searcher->Search(query,&json_str);rsp.set_content(json_str,"application/json");});svr.listen("0.0.0.0", 8800);
}
-
初始化: 定义了
input
和root_path
两个字符串常量,分别表示索引文件的路径和服务器的根目录。 -
创建搜索对象: 使用
std::unique_ptr
创建了Searcher
类的一个实例,并通过SearcherInit
方法初始化,以从指定的input
文件中构建索引。 -
创建和配置服务器: 使用
httplib::Server
类创建了一个HTTP服务器实例,设置了服务器的根目录为root_path
。 -
首页重定向: 服务器对根路径
/
的GET请求进行处理,通过set_redirect
方法将请求重定向到指定的HTML页面路径。 -
搜索请求处理: 对路径
/s
的GET请求进行处理,这是搜索功能的实现部分。服务器检查请求中是否包含名为word
的参数:- 如果请求中没有
word
参数,则返回错误信息。 - 如果有,它将提取
word
参数的值,打印出查询的内容,并调用Searcher
实例的Search
方法来进行搜索。搜索的结果是一个JSON字符串,它会设置为响应体的内容。
- 如果请求中没有
-
启动服务器: 使用
svr.listen
方法监听0.0.0.0
上的8800
端口,使服务器开始接受连接和处理请求。
httplib::Server
是 httplib
库中用于创建和管理HTTP服务器的类。
五 前端模块
<!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: 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>
<body><div class="container"><div class="search"><input type="text" value="请输入搜索关键字"><button onclick="Search()">搜索一下</button></div><div class="result"></div></div><script>function Search(){let query = $(".container .search input").val();console.log("query = " + query);$.get("/s", {word: query}, 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);}}</script></body>
</html>
HTML结构
-
搜索栏 (
div.search
):- 包含一个文本输入框,用户可以在其中输入搜索关键字。
- 包含一个按钮,当点击时会调用
Search()
JavaScript函数。
-
搜索结果显示区域 (
div.result
):- 这是一个空的
div
,将来用来动态显示搜索结果。
- 这是一个空的
样式
- 通过CSS设置了页面和元素的样式,包括输入框、按钮、搜索结果等。
JavaScript功能
-
Search() 函数:
- 从输入框中获取用户输入的查询词。
- 使用
jQuery
的$.get()
函数异步向服务器的/s
路径发送一个GET请求,并将用户的查询词作为参数传递。 - 当收到响应时,调用
BuildHtml()
函数处理数据并构建结果HTML。
-
BuildHtml() 函数:
- 清空结果显示区域,为新的搜索结果做准备。
- 遍历响应数据中的每个搜索结果,并为每个结果创建包含标题、描述和URL的HTML元素。
- 将创建的HTML元素附加到结果显示区域。
用户交互
- 当用户输入查询词并点击搜索按钮时,页面将不会进行重新加载,而是通过JavaScript异步请求后端服务,并将结果动态地插入到页面中。
jQuery
- 页面通过CDN引用了
jQuery
库,以简化DOM操作和Ajax请求。
相关文章:
基于boost准标准库的搜索引擎项目
零 项目背景/原理/技术栈 1.介绍boost准标准库 2.项目实现效果 3.搜索引擎宏观架构图 这是一个基于Web的搜索服务架构 客户端-服务器模型:采用了经典的客户端-服务器模型,用户通过客户端与服务器交互,有助于集中管理和分散计算。简单的用户…...
语言模型进化史(下)
由于篇幅原因,本文分为上下两篇,上篇主要讲解语言模型从朴素语言模型到基于神经网络的语言模型,下篇主要讲解现代大语言模型以及基于指令微调的LLM。文章来源是:https://www.numind.ai/blog/what-are-large-language-models 四、现…...
设计模式之旅:工厂模式全方位解析
简介 设计模式中与工厂模式相关的主要有三种,它们分别是: 简单工厂模式(Simple Factory):这不是GoF(四人帮,设计模式的开创者)定义的标准模式,但被广泛认为是工厂模式的…...
大数据时代的生物信息学:挖掘生命数据,揭示生命奥秘
在当今科技日新月异的时代,大数据如同一座蕴藏无尽宝藏的矿山,而生物信息学则是那把锐利的探矿锤,精准有力地敲击着这座“生命之矿”,揭示出隐藏在其深处的生命奥秘。随着基因测序技术的飞速进步与广泛应用,生物医学领…...
微信小程序开发【从入门到精通】——页面导航
👨💻个人主页:开发者-曼亿点 👨💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨💻 本文由 曼亿点 原创 👨💻 收录于专栏:…...
嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记15:PWM输出
系列文章目录 嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记01:赛事介绍与硬件平台 嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记02:开发环境安装 嵌入式|蓝桥杯STM32G431(…...
SQLite中的隔离(八)
返回:SQLite—系列文章目录 上一篇:SQLite版本3中的文件锁定和并发(七) 下一篇:SQLite 查询优化器概述(九) 数据库的“isolation”属性确定何时对 一个操作的数据库对其他并发操作可见。 数据库连接之…...
Zabbix6 - Centos7部署Grafana可视化图形监控系统配置手册手册
Zabbix6 - Centos7部署Grafana可视化图形监控系统配置手册手册 概述: Grafana是一个开源的数据可视化和监控平台。其特点: 1)丰富的可视化显示插件,包括热图、折线图、饼图,表格等; 2)支持多数据…...
Electron无边框自定义窗口拖动
最近使用了electron框架,发现如果自定义拖动是比较实用的;特别是定制化比较高的项目,如果单纯的使用-webkit-app-region: drag;会让鼠标事件无法触发; 过程中发现问题: 1.windows缩放不是100%后设置偏移界面会缩放,感觉像吹起的气…...
vue3+echarts:echarts地图打点显示的样式
colorStops是打点的颜色和呼吸灯、label为show是打点是否显示数据、rich里cnNum是自定义的过滤模板用来改写显示数据的样式 series: [{type: "effectScatter",coordinateSystem: "geo",rippleEffect: {brushType: "stroke",},showEffectOn: &quo…...
vue3从精通到入门7:ref系列
Vue 3 的 Ref 是一个集合,包括多个与响应式引用相关的功能,这些功能共同构成了 Vue 3 响应式系统的重要组成部分。以下是更全面的介绍: 1.ref ref 接受一个内部值并返回一个响应式且可变的 ref 对象。这个对象具有一个 .value 属性…...
灵动翻译音频文件字幕提取及翻译;剪映视频添加字幕
参考:视频音频下载工具 https://tuberipper.com/21/save/mp3 1、灵动翻译音频文件字幕提取及翻译 灵动翻译可以直接chorme浏览器插件安装: 点击使用,可以上传音频文件 上传后自动翻译,然后点击译文即可翻译成中文,…...
在Gitee上创建新仓库
1. 登录到你的Gitee账户。 2. 在Gitee首页或仓库页面,点击“新建仓库”按钮。 3. 填写仓库名称、描述(可选)、选择仓库是否公开等信息。 4. 点击“创建仓库”按钮完成创建。 2. 本地代码连接到远程仓库 假设你已经在本地有一个项目&#…...
linux 配置NFS
1、NFS简介 NFS 是Network File System的缩写,即⽹络⽂件系统。NFS 的基本原则是“容许不同的客户 端及服务端通过⼀组RPC分享相同的⽂件系统”,它是独⽴于操作系统,容许不同硬件及操作 系统的系统共同进⾏⽂件的分享。 NFS在⽂件传送或信息…...
大疆御Pro(一代)更换晓spark摄像头评测
御Pro是17年的老机器,除了摄像头有点拉跨,续航、抗风、操作性在大疆民用系列里面算是数得上的。 机缘巧合,手头有几个御的空镜头(里面的芯片已经去掉了),还有几个晓的摄像头(只有芯片࿰…...
【小技巧】gitlab怎么在每次git push的时候不用输入账号密码?使用 SSH 密钥 的原理是什么?
1. gitlab怎么在每次git push的时候不用输入账号密码? 要在每次执行 git push 时避免输入 GitLab 的账号和密码,你可以通过以下几种方法实现: 使用 SSH 密钥:这是最常用的方法,通过生成 SSH 密钥并将其添加到 GitLab …...
笔记: JavaSE day15 笔记
第十五天课堂笔记 数组 可变长参数★★★ 方法 : 返回值类型 方法名(参数类型 参数名 , 参数类型 … 可变长参数名){}方法体 : 变长参数 相当于一个数组一个数组最多只能有一个可变长参数, 并放到列表的最后parameter : 方法参数 数组相关算法★★ 冒泡排序 由小到大: 从前…...
【Golang星辰图】数据处理的航海家:征服数据海洋的航行工具
数据处理的建筑师:用Go语言中构建稳固的数据分析建筑物 前言 数据处理和分析是现代计算机科学中的关键任务之一,而Go语言作为一门现代化的编程语言,也需要强大的数据处理和分析库来支持其在这一领域的应用。本文将介绍几款优秀的数据处理和…...
容器网络测试关键问题
资料问题 主要影响客户体验, 低级问题. 类似于单词拼写错误, 用词有歧义,等。 另一点是,我们的用户文档,主要偏向于技术向的描述,各种参数功能罗列。友商有比较好的最佳实践操作说明。我们后面也会都增加这样的最佳实践。golang o…...
6、Cocos Creator 2D 渲染组件:Sprite 组件
Sprite 组件 Sprite(精灵)是 2D/3D 游戏最常见的显示图像的方式,在节点上添加 Sprite 组件,就可以在场景中显示项目资源中的图片。 属性功能说明Type渲染模式,包括普通(Simple)、九宫格&#x…...
算法沉淀——动态规划篇(子数组系列问题(上))
算法沉淀——动态规划篇(子数组系列问题(上)) 前言一、最大子数组和二、环形子数组的最大和三、乘积最大子数组四、乘积为正数的最长子数组长度 前言 几乎所有的动态规划问题大致可分为以下5个步骤,后续所有问题分析都…...
通知中心架构:打造高效沟通平台,提升信息传递效率
随着信息技术的快速发展,通知中心架构作为一种关键的沟通工具,正逐渐成为各类应用和系统中必不可少的组成部分。本文将深入探讨通知中心架构的意义、设计原则以及在实际场景中的应用。 ### 什么是通知中心架构? 通知中心架构是指通过集中管…...
【Arduino使用SNR9816TTS模块教程】
【Arduino使用SNR9816TTS模块教程】 1.前言2. 硬件连接3. Arduino代码3.1 环境配置3.2 Arduino源码 4. 调试步骤5. 总结 1.前言 在今天的教程中,我们将详细介绍如何使用Arduino IDE开发ESP32C3与汕头新纳捷科技有限公司生产的SNR9816TTS中文人声语音合成模块进行交…...
牛客练习赛123(A,B,C,D)
牛客挑战赛,练习赛和小白月赛周赛不是一种东西。这玩意跟CF的div12差不多难度。而且找不到题解。所以决定不等题解补题了,直接写题解了。 比赛链接 光速签到下班,rk。感觉E可能能补掉,看情况补吧。 B题感觉之前考了两次&#x…...
docker部署-RabbitMq
1. 参考 RabbitMq官网 docker官网 2. 拉取镜像 这里改为自己需要的版本即可,下面容器也需要同理修改 docker pull rabbitmq:3.12-management3. 运行容器 docker run \ --namemy-rabbitmq-01 \ -p 5672:5672 \ -p 15672:15672 \ -d \ --restart always \ -…...
【智能算法】蜜獾算法(HBA)原理及实现
目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2021年,FA Hashim等人受到自然界中蜜獾狩猎行为启发,提出了蜜獾算法((Honey Badger Algorithm,HBA)。 2.算法原理 2.1算法思想 蜜獾以其…...
9、鸿蒙学习-开发及引用静态共享包(API 9)
HAR(Harmony Archive)是静态共享包,可以包含代码、C库、资源和配置文件。通过HAR可以实现多个模块或多个工程共享ArkUI组件、资源等相关代码。HAR不同于HAP,不能独立安装运行在设备上,只能作为应用模块的依赖项被引用。…...
[Pytorch]:PyTorch中张量乘法大全
在 PyTorch 中,有多种方法可以执行张量之间的乘法。这里列出了一些常见的乘法操作: 总结: 逐元素乘法:*ortorch.mul()矩阵乘法:ortorch.mm()ortorch.matmul()点积:torch.Tensor.dot()批量矩阵乘法ÿ…...
【软考】防火墙技术
目录 1. 概念2. 包过滤防火墙3. 应用代理网关防火墙4. 状态检测技术防火墙 1. 概念 1.防火墙(Firewall)是建立在内外网络边界上的过滤封锁机制,它认为内部网络是安全和可信赖的,而外部网络是不安全和不可信赖的。2.防火墙的作用是防止不希望的、未经授权…...
OpenHarmony实战:Makefile方式组织编译的库移植
以yxml库为例,其移植过程如下文所示。 源码获取 从仓库获取yxml源码,其目录结构如下表: 表1 源码目录结构 名称描述yxml/bench/benchmark相关代码yxml/test/测试输入输出文件,及测试脚本yxml/Makefile编译组织文件yxml/.gitat…...
礼嘉网络推广/西安做seo的公司
2019独角兽企业重金招聘Python工程师标准>>> ORACLE日期时间函数大全 (二) 2010-05-11 13:36 13.年月日的处理 select older_date, newer_date, years, months, abs( trunc( newer_date- add_months( older_date,years*12months ) ) ) daysfrom ( select trunc(mont…...
西宁网站建设最好的公司哪家好/学百度推广培训
配置tensorflow-gpu环境 首先,配置tensorflow-gpu环境。(不使用GPU环境运行出错) 我的环境配置: python3.7.9 cuda_10.0.130_411.31_win10 cudnn-10.0-windows10-x64-v7.5.0.56 配置步骤 #codingutf-8import tensorflow as tf i…...
彩票网站做一级代理犯法吗/网络推广公司名字
挂载 NFS 远程目录备份 Oracle 数据库(第13天) ->返回总目录<- 前面讲了如何在 Oracle 本地定时备份数据库,但是这种方式用的人比较少,因为如果本地磁盘坏了就会导致数据库和备份同时丢失,无法找回数据,风险也比较大。 针对这种情况,比较常用的方式是通过存储挂…...
a站为什么不火了/搜索推广平台有哪些
https://zhuanlan.zhihu.com/p/23269598?referhalfstack https://blog.csdn.net/boyupeng/article/details/47011383 https://blog.csdn.net/liluo2013/article/details/53673163 https://segmentfault.com/a/1190000007997113...
好的手机端网站模板下载软件/目前推广软件
1.Number.EPSILON是JS表示的最小精度 function add(a,b){if(Math.abs(a-b)<Number.EPSILON){return true;}else{return false} } console.log(add(0.10.2,0.3)) //true 2.Number.isFinite检测一个数值是否为有限数 3.Number.isNaN检测一个数值是否为NaN 4.Number.parse…...
网站伪静态/西安企业网站seo
Activity、Service和线程应该是Android编程中最常见的几种类了,几乎大多数应用程序都会涉及到这几个类的编程,自然而然的,也就会涉及到三者之间的相互通信,本文就试图简单地介绍一下这三者通信的方式。想…...