项目:轻量级搜索引擎

📅 2026/7/14 18:48:52
项目:轻量级搜索引擎
一、项目简介今天给大家分享我手写的轻量级全文搜索引擎纯 C 开发无大型中间件依赖完整复刻商业搜索引擎核心逻辑HTML 网页数据清洗、结构化解析手动实现正排索引 倒排索引搜索引擎核心结巴中文分词 停用词过滤关键词权重排序、智能摘要生成基于 httplib 搭建 Web 服务浏览器直接搜索返回 JSON 结果本文拒绝纯空话全程原理 核心代码 逐行讲解适合学习、复盘、简历项目展示。二、项目整体架构代码分层整个项目分为 4 大代码模块解耦清晰数据预处理模块遍历 html、清洗网页、生成 raw.txt 数据源通用工具模块 util文件读取、字符串分割、结巴分词索引模块 index构建正排、倒排索引项目核心检索 amp; 服务模块 searcher main检索逻辑 HTTP 在线服务核心链路原始HTML → 清洗结构化 → 构建索引 → 分词检索 → 权重排序 → Web接口返回JSON三、模块一数据预处理网页解析代码详解目的本地一堆 Boost 文档 html 文件无法直接检索需要批量遍历、去标签、提取标题/内容/URL生成统一格式的 raw.txt。1. 批量遍历HTML文件代码基于 boost::filesystem 递归遍历目录过滤无效文件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)){ 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::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; } }作用精简索引、提升检索精准度、减小内存占用。五、模块三索引核心 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::vectorInvertedElem 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; charsetutf-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?wordxxx即可获取结构化搜索 JSON 结果。八、核心知识点总结面试/复盘重点1. 正排索引 vs 倒排索引正排索引文档ID → 文档信息用于结果展示、摘要生成倒排索引关键词 → 文档列表检索加速核心避免遍历全文2. 权重设计思想标题关键词权重 正文权重模拟真实搜索引擎相关性排序逻辑。3. 工程亮点单例模式全局复用索引、分词器避免重复IO提升性能自定义分隔符解决文本分割冲突问题停用词过滤、大小写归一化优化检索效果轻量Web服务实现前后端完整交互九、项目优化方向拔高亮点索引磁盘持久化无需每次启动重建索引增加搜索结果分页、关键词高亮增加缓存优化高频搜索词响应速度引入同义词词库实现智能联想搜索十、总结本项目不是简单的 C 语法demo是完整的后端工程实战项目。从数据清洗、索引算法、业务检索到 Web 服务上线全程手写覆盖了文件IO、STL、设计模式、算法排序、网络编程等核心知识点。非常适合写进简历、面试复盘能清晰讲出索引原理、权重算法、项目踩坑点吊打大多数初级 C 开发者。