编辑
2026-02-10
💥AI大模型
00

🎯 一、文章目标:用不到150行Python代码构建一个全文搜索引擎

全文搜索(Full-text search)是现代信息检索系统的基础。Google、Amazon、Netflix 等平台都依赖它快速从海量非结构化文本中找出相关结果。

本文的目标不是构建一个生产级搜索引擎(如 Elasticsearch),而是通过极简代码展示搜索引擎的核心原理,让你理解:

  • 如何高效索引文档?
  • 如何解析用户查询?
  • 如何匹配并排序结果(即“相关性排序”)?

整个系统基于 维基百科摘要数据集(约627万篇文档),在普通笔记本电脑上就能运行,并支持毫秒级响应。

🧱 二、整体架构:三步走

全文搜索引擎通常包含三个核心阶段:

  1. 数据准备(Data Preparation)
  2. 索引构建(Indexing)
  3. 查询与排序(Searching & Ranking)

下面我们逐层拆解。

🔹 第一步:数据准备(Data)

数据来源

文档表示(Document Model) 使用 Python dataclass 定义文档结构:

from dataclasses import dataclass

@dataclass class Abstract: ID: int title: str abstract: str url: str

@property def fulltext(self): return ' '.join([self.title, self.abstract])

💡 fulltext 将标题和摘要拼接,作为全文搜索的内容。

流式加载(Memory-Efficient Parsing)

  • 使用 lxml.etree.iterparse 流式解析 XML,避免一次性加载整个文件到内存。
  • 为每篇文档分配唯一 ID(从1开始递增)。
  • 解析后立即释放 XML 节点内存(element.clear())。

✅ 关键思想:处理大数据时,流式处理 + 内存释放 是必须的。

🔹 第二步:文本分析与索引构建(Analysis & Indexing)

什么是“倒排索引”(Inverted Index)?

想象一本书后面的索引页: beer → p.23, p.45, p.102
london → p.12, p.23, p.89

在计算机中,我们用字典模拟: { "london": {1501027, 1828015, ...}, "beer": {1501027, 1828015, ...}, "flood": {1828015, ...} }

→ 词 → 包含该词的文档ID集合

这就是倒排索引,是全文搜索的基石。

文本分析流程(Text Analysis Pipeline)

为了提高匹配准确率,原始文本需经过“分析”(analyze)变成标准化的词项(tokens):

步骤如下(顺序很重要!):

  1. 分词(Tokenize)
    def tokenize(text): return text.split()

    → 按空格切分(简单但有效)

  2. 转小写(Lowercase)
    def lowercase_filter(tokens): return [t.lower() for t in tokens]

  3. 去除标点(Punctuation Removal)
    import re, string PUNCTUATION = re.compile('[%s]' % re.escape(string.punctuation)) def punctuation_filter(tokens): return [PUNCTUATION.sub('', t) for t in tokens]

  4. 停用词过滤(Stopword Filtering)
    移除高频无意义词(如 the, and, wikipedia): STOPWORDS = {'the', 'be', 'to', ..., 'wikipedia'} def stopword_filter(tokens): return [t for t in tokens if t not in STOPWORDS]

  5. 词干提取(Stemming)
    使用 PyStemmer 将不同形式归一化:

    • "brewery" → "breweri"

    • "breweries" → "breweri"

      import Stemmer STEMMER = Stemmer.Stemmer('english') def stem_filter(tokens): return STEMMER.stemWords(tokens)

⚠️ 注意:停用词过滤必须在词干化之前,因为停用词列表是原始形式(如 "the"),而词干化后可能变成 "th"。

组合分析函数 def analyze(text): tokens = tokenize(text) tokens = lowercase_filter(tokens) tokens = punctuation_filter(tokens) tokens = stopword_filter(tokens) tokens = stem_filter(tokens) return [t for t in tokens if t] # 去除空字符串

构建索引(Index Class)

class Index: def init(self): self.index = {} # 倒排索引:token → set(doc_id) self.documents = {} # doc_id → Abstract 对象

def index_document(self, document): if document.ID not in self.documents: self.documents[document.ID] = document document.analyze() # 计算词频(见下文) for token in analyze(document.fulltext): if token not in self.index: self.index[token] = set() self.index[token].add(document.ID)

✅ 此时已完成索引构建!后续查询只需查字典。

🔹 第三步:搜索与相关性排序(Search & Relevance)

  1. 基础布尔搜索(Boolean Search)

def search(self, query, search_type='AND'): analyzed_query = analyze(query) results = [self.index.get(token, set()) for token in analyzed_query]

if search_type == 'AND': doc_ids = set.intersection(*results) # 所有词都出现 elif search_type == 'OR': doc_ids = set.union(*results) # 任一词出现 return [self.documents[doc_id] for doc_id in doc_ids]
  • AND:高精度(precision),结果少但相关
  • OR:高召回(recall),结果多但噪声大
  1. 相关性排序:TF-IDF

问题:布尔搜索返回的结果没有排序,用户仍需自己筛选。

解决方案:为每个文档打分 → TF-IDF(Term Frequency–Inverse Document Frequency)

(1)词频(Term Frequency, TF)

  • 一个词在文档中出现次数越多,越相关。
  • 在 Abstract 类中预计算:

from collections import Counter def analyze(self): self.term_frequencies = Counter(analyze(self.fulltext))

def term_frequency(self, term): return self.term_frequencies.get(term, 0)

(2)逆文档频率(Inverse Document Frequency, IDF)

  • 一个词在整个语料库中越罕见,区分度越高。

  • 公式:

    text{IDF}(t) = log_{10} left( frac{N}{text{df}(t)} right)

    • N :总文档数
    • text{df}(t) :包含词 t 的文档数

def inverse_document_frequency(self, token): df = len(self.index.get(token, set())) return math.log10(len(self.documents) / df)

(3)最终得分:TF × IDF

def rank(self, analyzed_query, documents): results = [] for doc in documents: score = sum(doc.term_frequency(t) * self.inverse_document_frequency(t) for t in analyzed_query) results.append((doc, score)) return sorted(results, key=lambda x: x[1], reverse=True)

✅ 这样,最相关的文档排在最前面!

🧪 示例效果

查询 "London Beer Flood":

  • 布尔 AND 搜索 → 返回2篇高度相关文档(如“Horse Shoe Brewery”)
  • TF-IDF 排序 → 即使使用 OR 模式返回数万结果,也能把最相关的排在顶部

🛠 技术亮点总结 技术点 说明 倒排索引 核心数据结构,实现 O(1) 词查找

流式 XML 解析 处理大文件不爆内存

文本分析管道 分词 → 小写 → 去标点 → 停用词 → 词干化

布尔搜索(AND/OR) 灵活控制精度与召回

TF-IDF 排序 经典相关性算法,兼顾词频与稀有度

内存效率 所有数据结构用 Python 原生 dict/set,简洁高效

🚀 可扩展方向(Future Work)

作者也指出,这只是一个教学原型,可改进之处包括:

  1. 字段加权:标题中的词比摘要中更重要(可乘以权重)
  2. 更复杂的查询语法:支持 NOT、括号、短语搜索("exact phrase")
  3. 持久化索引:当前全在内存,可序列化到磁盘
  4. 使用更优分词器:如 spaCy、NLTK 替代空格分词
  5. 避免词干化过度:考虑用 lemmatization(词形还原)替代 stemming

✅ 总结:你学到了什么?

  1. 搜索引擎本质 = 倒排索引 + 查询解析 + 相关性排序
  2. 150行代码足够展示核心思想,无需复杂框架
  3. TF-IDF 是经典且有效的排序方法
  4. 工程上要注意内存与效率(流式处理、及时释放)
  5. 文本预处理顺序很重要(停用词在词干前)

这篇文章的价值不在于代码多精巧,而在于用最简方式揭示了 Google 背后的基本原理。

如果你想动手实践,作者已将代码开源: 👉 https://github.com/bartdegoede/python-searchengine

只需: pip install -r requirements.txt python run.py

本文作者:Eric

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!