一、项目简介
今天给大家分享我手写的轻量级全文搜索引擎,纯 C++ 开发,无大型中间件依赖,完整复刻商业搜索引擎核心逻辑:
HTML 网页数据清洗、结构化解析
手动实现正排索引 + 倒排索引(搜索引擎核心)
结巴中文分词 + 停用词过滤
关键词权重排序、智能摘要生成
基于 httplib 搭建 Web 服务,浏览器直接搜索,返回 JSON 结果
本文拒绝纯空话,全程原理 + 核心代码 + 逐行讲解,适合学习、复盘、简历项目展示。
二、项目整体架构(代码分层)
整个项目分为 4 大代码模块,解耦清晰:
数据预处理模块:遍历 html、清洗网页、生成 raw.txt 数据源
通用工具模块 util:文件读取、字符串分割、结巴分词
索引模块 index:构建正排、倒排索引(项目核心)
检索 & 服务模块 searcher + main:检索逻辑 + HTTP 在线服务
核心链路:原始HTML → 清洗结构化 → 构建索引 → 分词检索 → 权重排序 → Web接口返回JSON
三、模块一:数据预处理(网页解析)代码详解
目的:本地一堆 Boost 文档 html 文件,无法直接检索,需要批量遍历、去标签、提取标题/内容/URL,生成统一格式的 raw.txt。
1. 批量遍历HTML文件代码
基于 boost::filesystem 递归遍历目录,过滤无效文件:
bool EnumFile(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)){ std::cerr << src_path << " not exists" << 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)) continue; // 只保留html文件 if(iter->path().extension() != ".html") continue; files_list->push_back(iter->path().string()); } return true; }代码讲解:
recursive_directory_iterator:递归遍历子目录,适配多层文件结构is_regular_file:过滤文件夹,只保留普通文件后缀判断:只筛选 html 网页文件,作为数据源
2. 标题解析代码(字符串截取)
static bool ParseTitle(const std::string &file, std::string *title) { std::size_t begin = file.find("<title>"); std::size_t end = file.find("</title>"); if(begin == std::string::npos || end == std::string::npos || begin > end){ return false; } begin += std::string("<title>").size(); *title = file.substr(begin, end - begin); return true; }原理:通过 find 定位标题标签首尾,substr 截取中间纯文本,简单高效,无需复杂解析库。
3. 正文解析代码(状态机去HTML标签)
核心亮点:手动实现简易状态机,剔除所有 HTML 标签,保留正文纯文本:
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 { if(c == '\n') c = ' '; content->push_back(c); } break; default: break; } } return true; }代码讲解:
两种状态:标签内(舍弃)、内容区(保留)
遇到
>退出标签,进入内容读取遇到
<进入标签,停止内容读取自动替换换行符为空格,避免文本格式混乱
4. 结构化保存数据(解决分隔符冲突)
重点踩坑:正文自带换行、逗号,不能用常规符号分割,项目采用不可见ASCII字符 \3分割字段:
#define SEP '\3' for(auto &item : results){ std::string out_string; out_string = item.title; out_string += SEP; out_string += item.content; out_string += SEP; out_string += item.url; out_string += '\n'; out.write(out_string.c_str(), out_string.size()); }四、模块二:通用工具类 util.hpp 代码详解
1. 文件读取工具
static bool ReadFile(const std::string &file_path, std::string *out) { std::ifstream in(file_path, std::ios::in); if(!in.is_open()) return false; std::string line; while(std::getline(in, line)) *out += line; in.close(); return true; }2. 结巴分词 + 停用词过滤(核心)
搜索引擎不能搜「的、了、啊」这类无意义词汇,这里封装单例分词工具,自动过滤停用词:
void CutStringHelper(const std::string &src, std::vector<std::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++; } }作用:精简索引、提升检索精准度、减小内存占用。
五、模块三:索引核心 index.hpp(项目灵魂+重点代码)
搜索引擎快的本质:不遍历全文,只查索引。依靠正排+倒排双索引实现毫秒检索。
1. 核心数据结构定义
// 正排索引节点:通过文档ID查文档内容 struct DocInfo{ std::string title; std::string content; std::string url; uint64_t doc_id; }; // 倒排索引节点:通过关键词查文档 struct InvertedElem{ uint64_t doc_id; std::string word; int weight; InvertedElem():weight(0){} }; typedef std::vector<InvertedElem> InvertedList;2. 线程安全单例索引类
索引全局唯一,禁止多次创建,采用懒汉模式+双重检查锁:
static Index* GetInstance() { if(nullptr == instance){ mtx.lock(); if(nullptr == instance){ instance = new Index(); } mtx.unlock(); } return instance; }3. 倒排索引权重计算核心代码
贴合搜索逻辑:标题权重远大于正文,标题命中的文档排序更靠前
#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.weight = X*word_pair.second.title_cnt + Y*word_pair.second.content_cnt; inverted_index[word_pair.first].push_back(std::move(item)); }代码逻辑:同一个词,在标题出现一次=正文出现10次权重,完全贴合用户搜索习惯。
六、模块四:检索器 searcher.hpp 核心代码详解
1. 多关键词检索 + 权重合并
用户输入多个关键词,同一文档命中多个词,权重累加,相关性更高:
// 遍历每一个搜索分词 for(std::string word : words){ boost::to_lower(word); ns_index::InvertedList *inverted_list = index->GetInvertedList(word); if(nullptr == inverted_list) continue; // 合并相同文档的权重 for(const auto &elem : *inverted_list){ auto &item = tokens_map[elem.doc_id]; item.doc_id = elem.doc_id; item.weight += elem.weight; item.words.push_back(elem.word); } }2. 权重降序排序
std::sort(inverted_list_all.begin(), inverted_list_all.end(),\ [](const InvertedElemPrint &e1, const InvertedElemPrint &e2){ return e1.weight > e2.weight; });权重越高,文档和搜索词越匹配,排序越靠前。
3. 智能摘要生成代码
自动截取关键词前后文本,容错边界,生成搜索摘要:
std::string GetDesc(const std::string &html_content, const std::string &word) { const int prev_step = 50; const int next_step = 100; // 忽略大小写查找关键词 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(iter == html_content.end()) return "None1"; int pos = std::distance(html_content.begin(), iter); int start = 0, end = html_content.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"; std::string desc = html_content.substr(start, end - start); desc += "..."; return desc; }七、模块五:HTTP Web服务 main.cpp 代码详解
基于 cpp-httplib 搭建轻量级服务,无需部署 Nginx,直接浏览器访问搜索:
int main() { // 1. 初始化搜索器 & 索引 ns_searcher::Searcher search; search.InitSearcher(input); // 2. 创建HTTP服务 httplib::Server svr; svr.set_base_dir(root_path.c_str()); // 3. 注册搜索接口 /s 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::string json_string; search.Search(word, &json_string); rsp.set_content(json_string, "application/json"); }); LOG(NORMAL, "服务器启动成功..."); svr.listen("0.0.0.0", 8081); return 0; }接口功能:访问http://127.0.0.1:8081/s?word=xxx即可获取结构化搜索 JSON 结果。
八、核心知识点总结(面试/复盘重点)
1. 正排索引 vs 倒排索引
正排索引:文档ID → 文档信息,用于结果展示、摘要生成
倒排索引:关键词 → 文档列表,检索加速核心,避免遍历全文
2. 权重设计思想
标题关键词权重 > 正文权重,模拟真实搜索引擎相关性排序逻辑。
3. 工程亮点
单例模式全局复用索引、分词器,避免重复IO,提升性能
自定义分隔符解决文本分割冲突问题
停用词过滤、大小写归一化,优化检索效果
轻量Web服务,实现前后端完整交互
九、项目优化方向(拔高亮点)
索引磁盘持久化,无需每次启动重建索引
增加搜索结果分页、关键词高亮
增加缓存,优化高频搜索词响应速度
引入同义词词库,实现智能联想搜索
十、总结
本项目不是简单的 C++ 语法demo,是完整的后端工程实战项目。从数据清洗、索引算法、业务检索到 Web 服务上线,全程手写,覆盖了文件IO、STL、设计模式、算法排序、网络编程等核心知识点。
非常适合写进简历、面试复盘,能清晰讲出索引原理、权重算法、项目踩坑点,吊打大多数初级 C++ 开发者。