在Python中实现搜索引擎功能,核心需要解决文本预处理、索引构建、查询匹配和相关性排序四个环节,其中倒排索引是整个搜索引擎的基础数据结构,能够大幅提升检索效率。

核心原理概述
搜索引擎的工作流程可以拆解为两个主要阶段:索引构建阶段和查询处理阶段。索引构建阶段会对所有待检索的文档进行分词、去停用词等预处理,然后构建倒排索引;查询处理阶段会对用户输入的查询词做同样的预处理,再到倒排索引中匹配文档,最后通过排序算法返回最相关的结果。
倒排索引是什么
传统的正排索引是文档ID对应文档内容,而倒排索引是关键词对应包含该关键词的文档ID列表,这样查询时不需要遍历所有文档,只需要找到关键词对应的文档列表即可,时间复杂度从O(N)降低到O(1)左右。
基础实现步骤
1. 文本预处理
预处理主要包括分词、转小写、去除停用词和标点符号,这里使用jieba库做中文分词,同时准备一个常见的停用词表。
import jieba
import re
# 停用词表,可根据需求扩展
STOP_WORDS = set(['的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这'])
def text_preprocess(text):
# 去除标点符号和数字
text = re.sub(r'[^u4e00-u9fa5]', ' ', text)
# 分词
words = jieba.lcut(text)
# 转小写(中文无大小写,这里兼容英文场景)
words = [word.lower() for word in words]
# 去停用词和空字符串
words = [word for word in words if word.strip() and word not in STOP_WORDS]
return words
2. 构建倒排索引
遍历所有文档,对每个文档做预处理得到关键词列表,然后将关键词和文档ID的映射关系存入倒排索引字典。
def build_inverted_index(docs):
"""
docs: 列表,每个元素是(文档ID, 文档内容)的元组
"""
inverted_index = {}
for doc_id, content in docs:
words = text_preprocess(content)
for word in words:
if word not in inverted_index:
inverted_index[word] = set()
inverted_index[word].add(doc_id)
# 将集合转为列表方便后续处理
for word in inverted_index:
inverted_index[word] = list(inverted_index[word])
return inverted_index
3. 查询处理与结果排序
查询时先对查询词做同样的预处理,然后取所有关键词对应文档ID的交集作为候选结果,再使用TF-IDF算法计算相关性得分排序。TF是词在文档中的出现频率,IDF是词的逆文档频率,得分越高说明文档和查询越相关。
import math
def calculate_tf(word, doc_content):
"""计算词在文档中的TF值"""
words = text_preprocess(doc_content)
if not words:
return 0
return words.count(word) / len(words)
def calculate_idf(word, docs):
"""计算词的IDF值"""
total_docs = len(docs)
# 包含该词的文档数
doc_count = sum(1 for _, content in docs if word in text_preprocess(content))
if doc_count == 0:
return 0
return math.log(total_docs / (doc_count + 1)) # 加1避免分母为0
def search(query, docs, inverted_index):
# 查询词预处理
query_words = text_preprocess(query)
if not query_words:
return []
# 获取所有候选文档ID,取交集
candidate_doc_ids = None
for word in query_words:
if word in inverted_index:
ids = set(inverted_index[word])
else:
ids = set()
if candidate_doc_ids is None:
candidate_doc_ids = ids
else:
candidate_doc_ids = candidate_doc_ids & ids
if not candidate_doc_ids:
return []
# 计算每个候选文档的TF-IDF得分
scores = []
for doc_id in candidate_doc_ids:
score = 0
doc_content = [content for did, content in docs if did == doc_id][0]
for word in query_words:
tf = calculate_tf(word, doc_content)
idf = calculate_idf(word, docs)
score += tf * idf
scores.append((doc_id, score))
# 按得分降序排序
scores.sort(key=lambda x: x[1], reverse=True)
return scores
完整示例演示
下面用一个简单的示例演示整个搜索引擎的运行过程,包含3个测试文档,查询关键词后返回排序后的结果。
# 测试文档,格式为(文档ID, 文档内容)
test_docs = [
(1, 'Python是一门非常流行的编程语言,适合做数据分析和人工智能开发'),
(2, '搜索引擎的核心技术是倒排索引,能够提升检索效率'),
(3, '使用Python可以实现简单的搜索引擎功能,支持中文全文检索'),
(4, 'Python的第三方库非常丰富,jieba是常用的中文分词工具')
]
# 构建倒排索引
index = build_inverted_index(test_docs)
# 执行查询
query = 'Python 搜索引擎'
results = search(query, test_docs, index)
# 输出结果
print('查询结果:')
for doc_id, score in results:
content = [c for did, c in test_docs if did == doc_id][0]
print(f'文档ID: {doc_id}, 得分: {score:.4f}, 内容: {content}')
运行上述代码后,会输出匹配到的文档ID和相关性得分,得分越高的文档和查询的相关性越强。如果需要支持更复杂的搜索功能,比如短语查询、模糊匹配、分页等,可以在上述基础上扩展对应的逻辑。
注意事项
- 中文分词的效果会直接影响搜索引擎的准确性,可根据业务场景自定义jieba的分词词典,提升专业领域词汇的识别率。
- 倒排索引如果数据量较大,可以存储到磁盘或者数据库中,避免占用过多内存。
- 如果文档数量非常多,TF-IDF的排序效果可能不够好,可以考虑使用BM25等更先进的排序算法。
- 生产环境中还需要考虑索引的更新机制,当文档新增、修改、删除时,需要同步更新倒排索引。