前引:这是一个聚焦基础搜索引擎核心工作流的实操项目,基于 C/C++ 技术生态落地:从全网爬虫抓取网页资源,到服务器端完成 “去标签 - 数据清洗 - 索引构建” 的预处理,再通过 HTTP 服务接收客户端请求、检索索引并拼接结果页返回 —— 完整覆盖了轻量级搜索引擎的端到端逻辑。项目采用 C++11、STL、Boost 等核心技术栈,搭配 CentOS 7 云服务器 + GCC 编译环境(或 VS 系列开发工具)部署,既适配后端工程的性能需求,也能通过可选的前端技术(HTML5/JS 等)优化用户交互,是理解搜索引擎底层原理与 C++ 工程实践的典型案例!
目录
【一】调用正/倒排索引(class Searcher)
【二】完成JSON格式的输出(class Searcher)
【三】对指定内容完成摘要
【四】引用 cpp httplib库
【五】基本搜索演示
(1)完成首页
(2)调用cpp httplib
(3)演示
【六】优化
(1)首页优化1
(2)效果展示
(2)首页优化2
(3)效果展示
【七】BOOST搜索引擎项目梳理
【一】调用正/倒排索引(class Searcher)
设计一个类,调用正/倒排索引类中 Index 中的单例模式指针:
//索引对象 ns_index::Index* index;void Init_index(const std::string& input) { //实例化索引对象 index=ns_index::Index::handle(); //建立索引 index->Buildindex(input); }我们调用测试代码打印索引构建情况,看看正排和倒排输出咋样:
【二】完成JSON格式的输出(class Searcher)
对用户输入的提示词调用Jieba进行分词,然后在倒排中进行匹配,由正排找到对应的文档,完成JSON序列化:
//利用正排和倒排构建JSON void searcher(const std::string& input,std::string& json_output) { //存储分词的结果 std::vector<std::string> S; //使用分词工具对input进行分词 ns_index::JiebaUtil jieba; S=jieba.Tokenize(input); //根据获取的关键字利用倒排获取关联的ID,返回的是一个个std::vector<Invertedindex>==Stock_Inverted std::vector<ns_index::Index::Stock_Inverted> V; for(std::string& word :S) { ns_index::Index::Stock_Inverted* pc = (index->GetInverted_index(word)); //如果不存在 if(pc==nullptr)continue; //如果存在,存储对应涉及到的vector<Invertedindex> V.push_back(*pc); } //扁平化,获取input涉及的所有ID std::vector<ns_index::Invertedindex> all_item; for(auto& e:V)//e拿到了每个vector<Invertedindex> { for(auto& it:e)//it拿到了每个Invertedindex { all_item.push_back(it); } } //对结果安装权重排序 std::sort(all_item.begin(),all_item.end(),Compare()); //根据id拿到每个文档完成JSON的序列化 nlohmann::json result_json; for(auto& it:all_item) { ns_index::Forwardindex* pc = (index->GetForward_index(it.doc_id)); if(pc==nullptr)continue; nlohmann::json doc_json; doc_json["title"]=pc->title; doc_json["source"]=pc->source; doc_json["chain"]=pc->chain; result_json.push_back(doc_json); } json_output = result_json.dump(4); }【三】对指定内容完成摘要
//对内容做提取形成摘要 std::string GetDesc(const std::string& html_content,const std::string& word) { std::string S="None"; //从word位置往前找50个字节,如果不够从word向后找100个字节 //在内容中找word出现的位置 std::size_t it = html_content.find(word); if(it == std::string::npos) { return S; } //如果word前面的字符够50个字符 if(it>=50) { S=html_content.substr(it-50,50); return S; } else//如果word前面不够50个字符 { std::size_t size_content = html_content.size(); if(it+100 <= size_content)//如果足够100个字符 { S=html_content.substr(it,100); return S; } else//如果不足够100个字符 { S=html_content.substr(it,size_content-it); } } return S; }【四】引用 cpp httplib库
这个是已经写好了的服务器端的代码,可以直接调用形成一个简单的服务器(如果想系统的学,可以看小编前面的服务端的实现):
//搜索是否有cpp-httplib的开发包: sudo apt search cpp-httplib//直接安装 sudo apt install libcpp-httplib-dev【五】基本搜索演示
(1)完成首页
首先我们准备一个首页:index.html,放在另外一个目录(wwwroot)下
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>搜索服务器</title> <style> /* 基础样式,保证界面简洁美观 */ body { font-family: "Microsoft YaHei", Arial, sans-serif; background-color: #f8f9fa; margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; } .search-container { background-color: #ffffff; padding: 40px; border-radius: 10px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); width: 550px; text-align: center; } .search-title { color: #333333; font-size: 32px; margin-bottom: 30px; font-weight: normal; } .search-box { display: flex; gap: 0; } #search-input { flex: 1; padding: 14px 18px; font-size: 16px; border: 1px solid #dddddd; border-right: none; border-radius: 6px 0 0 6px; outline: none; transition: border-color 0.3s; } #search-input:focus { border-color: #007bff; } #search-btn { padding: 14px 28px; background-color: #007bff; color: #ffffff; border: none; border-radius: 0 6px 6px 0; font-size: 16px; cursor: pointer; transition: background-color 0.3s; } #search-btn:hover { background-color: #0056b3; } .hint-text { color: #6c757d; font-size: 14px; margin-top: 20px; } </style> </head> <body> <div class="search-container"> <h1 class="search-title">欢迎使用搜索服务</h1> <div class="search-box"> <input type="text" id="search-input" placeholder="请输入搜索关键词..." autocomplete="off"> <button id="search-btn">搜索</button> </div> <p class="hint-text">提示:输入关键词后按回车键或点击搜索按钮提交</p> </div> <script> // 搜索提交逻辑:跳转至/s接口并携带word参数 function submitSearch() { const keyword = document.getElementById('search-input').value.trim(); if (!keyword) { alert('请输入搜索关键词!'); return; } // 拼接搜索接口地址,encodeURIComponent处理中文/特殊字符 const searchUrl = `/s?word=${encodeURIComponent(keyword)}`; window.location.href = searchUrl; } // 绑定按钮点击事件 document.getElementById('search-btn').addEventListener('click', submitSearch); // 绑定回车键提交事件 document.getElementById('search-input').addEventListener('keydown', function(e) { if (e.key === 'Enter') { submitSearch(); } }); </script> </body> </html>(2)调用cpp httplib
这里的 "searcher.h"就是searcher.h中实现的Searcher类
main执行的事件:
(1)完成索引,为了提高速度方便我把索引的次数设置成了5个
(2)创建了一个叫svr的服务器对象
(3)告诉服务器静态文件(比如index.html)从./wwwroot目录里找
(4)设置访问规则:
(1)用户如果要搜索关键字,应该是这种形式:IP:8080/s?word=关键词
(2)如果用户带了关键字,就在服务器输出用户搜索的关键字,在用JSON输出搜索结果
(3)如果没带,就显示错误
#include <httplib.h> #include"searcher.h" const std::string path ="./data/data_clean.txt"; const std::string root_path ="./wwwroot"; int main() { //构造索引 c_searcher::Searcher search; search.Init_index(path); //服务器+响应内容 httplib::Server svr; svr.set_base_dir(root_path.c_str()); svr.Get("/s", [&search](const httplib::Request &req, httplib::Response &rsp) { if(!req.has_param("word")) { rsp.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.searcher(word, json_string); rsp.set_content(json_string, "application/json"); }); svr.listen("0.0.0.0", 8080); return 0; }(3)演示
首先完成索引,然后服务器开始启动:
然后浏览器访问:
使用了关键字,且索引中有对应关键字文档:
搜索了关键字,但索引中没有:
首页效果:
用户没有携带关键字:
【六】优化
(1)首页优化1
用户可以直接采用IP:端口:/?word=关键字的方式响应内容,前面我们已经搭建了首页:
用户也应该可以在首页搜索框中直接搜索关键字,所以我们更改 index.html 文件:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>搜索服务</title> <style> body { display: flex; justify-content: center; align-items: center; min-height: 100vh; background-color: #f5f5f5; font-family: Arial, sans-serif; } .search-box { background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); text-align: center; } .search-box h2 { margin-bottom: 20px; color: #333; } .search-input { padding: 10px 15px; width: 300px; border: 1px solid #ddd; border-radius: 4px 0 0 4px; font-size: 16px; } .search-btn { padding: 10px 20px; background: #007bff; color: #fff; border: none; border-radius: 0 4px 4px 0; font-size: 16px; cursor: pointer; } .search-btn:hover { background: #0056b3; } .tips { margin-top: 10px; color: #666; font-size: 14px; } </style> </head> <body> <div class="search-box"> <h2>欢迎使用搜索服务</h2> <!-- 输入框和按钮 --> <input type="text" id="search-input" class="search-input" placeholder="请输入搜索关键词..."> <button id="search-btn" class="search-btn">搜索</button> <p class="tips">提示:输入关键词后按回车键或点击搜索按钮提交</p> </div> <script> // 1. 获取输入框和按钮元素 const input = document.getElementById('search-input'); const btn = document.getElementById('search-btn'); // 2. 定义“提交搜索”的函数 function submitSearch() { // 获取输入的关键词(去掉前后空格) const keyword = input.value.trim(); // 如果关键词为空,提示用户 if (!keyword) { alert('请输入搜索关键词!'); return; } // 拼接目标URL:IP:端口/s?word=关键词(自动适配当前域名/端口) // encodeURIComponent处理关键词中的特殊字符(比如空格、中文) const searchUrl = `/s?word=${encodeURIComponent(keyword)}`; // 跳转到搜索接口 window.location.href = searchUrl; } // 3. 给按钮绑定“点击触发搜索” btn.addEventListener('click', submitSearch); // 4. 给输入框绑定“按回车键触发搜索” input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { submitSearch(); } }); </script> </body> </html>(2)效果展示
效果如下:通过首页的搜索,可以自动补齐前面的IP:端口:/?word=内容,然后跳转
(2)首页优化2
用户输入关键词后,首先响应的是如下内容:那么可以直接跳转到响应JSON内容中的chain链接
需要注意:我们在searcher.h中实现的JSON序列化是数组形式的,因为对应多个ID,所以下面的网页代码在解析JSON中的chain(URL)时,默认取数组的第一个元素的chian才可以响应!
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>搜索服务</title> <style> body { display: flex; justify-content: center; align-items: center; min-height: 100vh; background-color: #f5f5f5; font-family: Arial, sans-serif; } .search-box { background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); text-align: center; } .search-box h2 { margin-bottom: 20px; color: #333; } .search-input { padding: 10px 15px; width: 300px; border: 1px solid #ddd; border-radius: 4px 0 0 4px; font-size: 16px; } .search-btn { padding: 10px 20px; background: #007bff; color: #fff; border: none; border-radius: 0 4px 4px 0; font-size: 16px; cursor: pointer; } .search-btn:hover { background: #0056b3; } .tips { margin-top: 10px; color: #666; font-size: 14px; } .loading { margin-top: 15px; color: #007bff; display: none; /* 默认隐藏加载提示 */ } </style> </head> <body> <div class="search-box"> <h2>欢迎使用搜索服务</h2> <input type="text" id="search-input" class="search-input" placeholder="请输入搜索关键词..."> <button id="search-btn" class="search-btn">搜索</button> <p class="loading" id="loading">搜索中,请稍候...</p> <p class="tips">提示:输入关键词后按回车键或点击搜索按钮提交</p> </div> <script> const input = document.getElementById('search-input'); const btn = document.getElementById('search-btn'); const loading = document.getElementById('loading'); async function submitSearch() { const keyword = input.value.trim(); if (!keyword) { alert('请输入搜索关键词!'); return; } // 显示“搜索中”提示 loading.style.display = 'block'; try { const response = await fetch(`/s?word=${encodeURIComponent(keyword)}`); if (!response.ok) throw new Error(`请求失败,状态码:${response.status}`); const rawText = await response.text(); const resultArray = JSON.parse(rawText); // 现在是数组 // 取数组第一个结果的chain if (resultArray.length > 0 && resultArray[0].chain) { window.location.href = resultArray[0].chain; } else { alert('未找到对应的链接地址!'); } } catch (error) { alert(`搜索出错:${error.message}`); } finally { loading.style.display = 'none'; } } // 绑定按钮点击事件 btn.addEventListener('click', submitSearch); // 绑定回车键事件 input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { submitSearch(); } }); </script> </body> </html>(3)效果展示
可以看到它的网页结果刚好跳转到第一个chain链接:
【七】BOOST搜索引擎项目梳理
项目目的:模拟实现搜索引擎,输入关键字,是如何响应和匹配我们需要的网页内容的
原理图:
项目搭建过程:
(1)首先我们选择了BOOST资料库作为数据对象,来模拟全网数据
(2)对所有数据进行清洗,筛选出每条网页数据的标题、有效内容、URL,存储起来
(3)通过存储的这些信息构建正排/倒排索引:
正排:通过ID映射每个文档信息
倒排:通过关键字映射文档ID
在其中我们采用jieba分词工具,来完成分词,对每个文档的标题和内容形成关键字
(4)接着我们处理用户输入的搜索信息,再次调用jieba分词工具,通过倒排锁定涉及到的文章ID
再通过正排拿到文章ID对应的文档内容,这样我们就拿到了涉及到的所有文档,进行JSON序列化
(5)对涉及到的文档再次筛选出摘要内容,形成简短的文档内容展示
(6)最后通过cpp httplib库直接形成服务器,设计一套访问规则,根据用户的关键字在倒排中找到对应的文档ID,构建出JSON响应,再响应给用户!