python搜索引擎代码
写一个能用的搜索搜索引擎,核心代码其实不到两百行。引擎我最近用Python搭了个原型,代码跑起来效果还行,搜索索引了大概三万条网页快照,引擎查询响应在80毫秒左右。代码下面直接拆开讲。搜索
倒排索引是引擎整个系统的骨架。简单说就是代码每个词对应一串文档ID,外加这个词在该文档里的搜索位置信息。我用字典套字典的引擎结构,外层键是代码词条,内层键是搜索文档ID,值是引擎一个列表,存出现位置。代码代码大概长这样:
index = { } def add_doc(doc_id, text): words = tokenize(text) for pos, word in enumerate(words): if word not in index: index[word] = { } if doc_id not in index[word]: index[word][doc_id] = [] index[word][doc_id].append(pos) 分词这块,中文得用jieba,英文直接按空格和标点切。我偷懒没做词干还原,搜索"running"和"run"会当成两个词。实际用的时候加个简单的后缀剥离规则就行,比如去掉ing、ed、s。但别搞太复杂,搜索引擎的召回率比精确匹配重要得多。
查询处理有个坑:用户输入"Python 搜索 引擎",你得先分词再查。如果直接拿整个字符串去查倒排索引,大概率啥也搜不到。我写了个query_parser函数,把输入拆成词条列表,然后对每个词条取文档ID集合,做交集。交集用Python的set.intersection,几万条数据下速度完全够用。
def search(query): terms = tokenize(query) if not terms: return [] sets = [set(index.get(t, { }).keys()) for t in terms] result = set.intersection(*sets) return rank(result, terms) 排序用BM25,比TF-IDF效果好不少。BM25考虑了两个东西:词频饱和和文档长度归一化。一个词在文档里出现20次和出现5次,相关性差距没那么大,BM25会把这个曲线压平。公式网上到处都是,我直接抄了个实现,调了k1=1.5、b=0.75。实际测试下来,长文档不会被短文档欺负得太惨。
有个细节值得说:位置信息不只是用来排序的,还能做短语搜索。用户搜"机器学习",如果索引里"机器"和"学习"出现在文档的不同段落,那这个结果就不该排前面。我加了个简单的邻近度打分——两个词的位置差小于5就加分。效果立竿见影,搜"神经网络"的时候,讲深度学习的那篇文章直接冲到第一。
存储方面,我用的shelve模块,把倒排索引持久化到磁盘。内存里维护一份,启动时加载。三万条文档的索引大概占200MB内存,普通笔记本扛得住。如果数据量上千万,得上Elasticsearch那种分布式方案,但自己写着玩,单机足够了。
最后说个实际跑起来的观察:搜索质量七成靠分词,三成靠排序。分词切错了,BM25再牛也救不回来。中文分词尤其明显,"南京市长江大桥"这种句子,jieba默认切法有时候会翻车。我试过加自定义词典,把"长江大桥"设成一个词,搜索准确率立刻上去了。所以别迷信算法,先把词典整明白。
代码我扔在GitHub上了,叫mini-search,有兴趣可以翻翻。下一步打算加个简单的拼写纠错,用编辑距离搞,用户打错字的时候给个"你是不是想搜xxx"的提示。这功能不难,但体验提升很大。









