🎯 一、文章目标:用不到150行Python代码构建一个全文搜索引擎
全文搜索(Full-text search)是现代信息检索系统的基础。Google、Amazon、Netflix 等平台都依赖它快速从海量非结构化文本中找出相关结果。
本文的目标不是构建一个生产级搜索引擎(如 Elasticsearch),而是通过极简代码展示搜索引擎的核心原理,让你理解:
整个系统基于 维基百科摘要数据集(约627万篇文档),在普通笔记本电脑上就能运行,并支持毫秒级响应。
🧱 二、整体架构:三步走
全文搜索引擎通常包含三个核心阶段:
下面我们逐层拆解。
🔹 第一步:数据准备(Data)
数据来源
使用 English Wikipedia Abstracts
格式:Gzipped XML 文件(约785MB)
每条记录结构如下:
Wikipedia: London Beer Flood https://en.wikipedia.org/wiki/London_Beer_Flood The London Beer Flood was an accident...
文档表示(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)
✅ 关键思想:处理大数据时,流式处理 + 内存释放 是必须的。
🔹 第二步:文本分析与索引构建(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):
步骤如下(顺序很重要!):
分词(Tokenize)
def tokenize(text): return text.split()
→ 按空格切分(简单但有效)
转小写(Lowercase)
def lowercase_filter(tokens): return [t.lower() for t in tokens]
去除标点(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]
停用词过滤(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]
词干提取(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)
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]
问题:布尔搜索返回的结果没有排序,用户仍需自己筛选。
解决方案:为每个文档打分 → TF-IDF(Term Frequency–Inverse Document Frequency)
(1)词频(Term Frequency, TF)
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)
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":
🛠 技术亮点总结 技术点 说明 倒排索引 核心数据结构,实现 O(1) 词查找
流式 XML 解析 处理大文件不爆内存
文本分析管道 分词 → 小写 → 去标点 → 停用词 → 词干化
布尔搜索(AND/OR) 灵活控制精度与召回
TF-IDF 排序 经典相关性算法,兼顾词频与稀有度
内存效率 所有数据结构用 Python 原生 dict/set,简洁高效
🚀 可扩展方向(Future Work)
作者也指出,这只是一个教学原型,可改进之处包括:
✅ 总结:你学到了什么?
这篇文章的价值不在于代码多精巧,而在于用最简方式揭示了 Google 背后的基本原理。
如果你想动手实践,作者已将代码开源: 👉 https://github.com/bartdegoede/python-searchengine
只需: pip install -r requirements.txt python run.py
本文作者:Eric
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!