项目:轻量级搜索引擎

一、项目简介

今天给大家分享我手写的轻量级全文搜索引擎,纯 C++ 开发,无大型中间件依赖,完整复刻商业搜索引擎核心逻辑:

  • HTML 网页数据清洗、结构化解析

  • 手动实现 正排索引 + 倒排索引(搜索引擎核心)

  • 结巴中文分词 + 停用词过滤

  • 关键词权重排序、智能摘要生成

  • 基于 httplib 搭建 Web 服务,浏览器直接搜索,返回 JSON 结果

本文拒绝纯空话 ,全程 原理 + 核心代码 + 逐行讲解,适合学习、复盘、简历项目展示。

二、项目整体架构(代码分层)

整个项目分为 4 大代码模块,解耦清晰:

  1. 数据预处理模块:遍历 html、清洗网页、生成 raw.txt 数据源

  2. 通用工具模块 util:文件读取、字符串分割、结巴分词

  3. 索引模块 index:构建正排、倒排索引(项目核心)

  4. 检索 & 服务模块 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++ 开发者。

相关推荐
A_humble_scholar3 小时前
Linux(十七)深入多线程编程:同步原语与实战指南
linux·运维·c++
hehelm4 小时前
Linux网络编程—HTTP静态文件服务器
linux·服务器·开发语言·网络·c++
库克克5 小时前
【C++】类和对象--this指针详解
java·开发语言·c++
ch0sen1pm5 小时前
加班之余从零写了个 spdlog 风格的日志库
c++
吃着火锅x唱着歌5 小时前
Effective C++ 学习笔记 条款33 避免遮掩继承来的名称
c++·笔记·学习
库克克6 小时前
【C++】类和对象--类对象模型与大小计算
开发语言·jvm·c++
hehelm6 小时前
Linux网络编程—UDP 群聊系统
linux·网络·c++
从零开始的代码生活_20 小时前
C++ 内存管理:从内存分区到 new/delete 底层原理
开发语言·c++·后端
aaPIXa62221 小时前
C++ 用 13 条规则让模型写出安全现代 C++
java·c++·安全