2、索引构建流程:数据采集、文档解析、分词与预处理、倒排索引生成

好,咱们直接进入正题。索引构建,说白了就是把一堆乱七八糟的原始数据,变成搜索引擎能快速查询的倒排索引。这个过程我拆成四个阶段:数据采集、文档解析、分词与预处理、倒排索引生成。每一步都有坑,我一个个说。

2.1 数据采集:从哪捞数据?

数据采集是第一步,也是最容易被低估的一步。很多人觉得不就是爬虫或者读数据库嘛,有什么难的?

嗯,我当年接手过一个项目,数据源是几十个异构的MySQL库,每个库的表结构都不一样。光是把这些数据统一拉过来,就花了两周。所以我的建议是:先搞清楚数据源的类型和规模

常见的数据源有这么几种:

  • 数据库:MySQL、PostgreSQL、MongoDB 等。一般用 JDBC 或 ODBC 拉取,注意增量更新。
  • 文件系统:日志文件、CSV、JSON 文件。用 Flume 或 Logstash 采集。
  • 消息队列:Kafka、RocketMQ。适合实时流式数据。
  • 网页爬虫:抓取外部网页。这个最麻烦,要考虑反爬、去重、时效性。

核心要点:数据采集阶段,一定要做 去重增量识别。否则后面索引里全是重复文档,查询结果一塌糊涂。

我个人习惯用 Bloom Filter 做 URL 去重,内存占用小,速度还快。当然,它有一定误判率,但用在爬虫去重上完全够用。

2.2 文档解析:把原始数据变成结构化文本

数据捞回来了,但格式五花八门。有 HTML、PDF、Word、纯文本……你得把它们统一解析成搜索引擎能理解的 文档对象

举个例子,一个 HTML 页面,里面有很多标签、脚本、样式。你总不能把这些标签也拿去建索引吧?

所以,解析阶段要做这几件事:

  1. 提取正文:去掉 HTML 标签、CSS、JS,只保留纯文本。
  2. 提取元信息:标题、作者、发布时间、URL 等。这些字段后面会用到排序和展示。
  3. 处理编码:统一转成 UTF-8。我遇到过 GBK 编码的文档,解析出来全是乱码,排查了半天。
  4. 文档去重:用 SimHash 或 MinHash 做近似去重。有些网页内容几乎一样,只是 URL 不同。

小技巧:解析 PDF 和 Word 文档时,建议用 Apache Tika 或 PDFBox。这些库成熟稳定,能处理大部分格式。我曾经自己写解析器,结果被各种奇葩格式搞崩溃了。

解析完成后,每个文档会变成一个 Document 对象,里面包含多个字段。比如:

Document {
  id: "12345",
  title: "搜索引擎索引构建实战",
  content: "本文详细介绍了索引构建的流程...",
  url: "https://example.com/article/12345",
  publish_time: "2024-01-15 10:30:00"
}

2.3 分词与预处理:让机器理解中文

这一步是中文搜索引擎的 灵魂。英文单词天然有空格分隔,但中文没有。你想想看,「南京市长江大桥」这句话,分词结果不同,意思完全不一样。

分词与预处理包括:

  • 分词:把句子切分成有意义的词。常用工具有 jieba、HanLP、IK Analyzer。
  • 去停用词:去掉「的」「了」「是」这类高频但无意义的词。
  • 词干提取/词形还原:英文需要,中文一般不需要。
  • 大小写转换:统一转小写,避免「Apple」和「apple」被当成两个词。
  • 同义词扩展:比如「电脑」和「计算机」映射到同一个词。

注意:分词词典一定要根据业务场景定制。我做过一个医疗搜索项目,默认分词器把「阿司匹林」切成了「阿司」和「匹林」,这显然不对。后来我们加了医疗领域的自定义词典,效果好了很多。

分词后的结果,大概长这样:

原始文本: "搜索引擎索引构建流程详解"
分词结果: ["搜索引擎", "索引", "构建", "流程", "详解"]

每个词都会记录它在文档中的 位置信息(偏移量),这个后面做短语查询和 proximity 查询时会用到。

2.4 倒排索引生成:从文档到索引的蜕变

终于到了最关键的一步。倒排索引,说白了就是 从词到文档的映射。正排索引是「文档 -> 词」,倒排索引是「词 -> 文档列表」。

生成过程大致如下:

  1. 遍历所有文档,对每个文档做分词。
  2. 建立词到文档的映射:每个词后面挂一个文档 ID 列表。
  3. 记录位置和频率:每个文档里,这个词出现了几次?分别在什么位置?
  4. 排序和压缩:文档 ID 列表按升序排列,方便合并。然后用变长编码压缩,节省存储空间。

举个例子,假设有两个文档:

文档1: "搜索引擎索引"
文档2: "索引构建流程"

生成的倒排索引大概是这样:

词项 文档频率 倒排列表(文档ID:位置列表)
搜索引擎 1 Doc1: [0]
索引 2 Doc1: [1], Doc2: [0]
构建 1 Doc2: [1]
流程 1 Doc2: [2]

关键点:倒排索引的存储结构,直接影响查询性能。我建议用 跳表(Skip List) 来组织文档 ID 列表,这样在做 AND 查询时,可以快速跳过不匹配的文档,效率很高。

生成倒排索引后,还要做 索引合并。因为数据是分批处理的,每批会生成一个临时索引段。最后要把这些段合并成一个完整的索引。合并时要注意去重和排序,不然查询结果会乱。

2.5 整个流程的串联

好了,四个阶段都讲完了。我画个简单的流程图,帮你理解它们的关系:

数据采集 -> 文档解析 -> 分词与预处理 -> 倒排索引生成
    |            |              |                |
    v            v              v                v
原始数据    结构化文档      词项列表         倒排索引文件

每个阶段都可能出问题。我曾经在数据采集阶段漏掉了增量数据,导致索引和实际数据差了三天。排查了很久才发现是 Kafka 消费偏移量没提交。嗯,从那以后,我每个阶段都加了 监控和告警

我的建议:索引构建流程一定要做成 可重入 的。也就是说,如果某一步失败了,重启后能从断点继续,而不是从头再来。用 checkpoint 机制就能实现。

最后说一句,索引构建不是一次性的工作。数据在变,索引也要跟着更新。所以后面我们会讲增量索引和全量索引的切换策略。别急,一步步来。