一个 Python 的轻量级搜索工具 -- Whose
有时候你应该也碰到过这种情况:一堆本地数据、配置、日志、Markdown 文档、甚至一些乱七八糟的文本散落在不同目录里,想找点东西却不知道它在哪。grep?能用,但写复杂一点就要回忆参数;自己写脚本?能写,但重复造轮子。
Whose 就是为这种“我就想随便搜点东西,但又不想折腾”的场景准备的一个小工具。它轻量、无依赖、API 简单,用几行 Python 就能实现一个可用的小搜索器,不上升到 Elasticsearch 那种重量级,也不需要维护索引服务。
一句话总结:
Whose = 轻量级文件/文本搜索库 + Python 的自然表达方式
它的设计思路:简单但不简陋
Whose 的理念很直接:
不构建复杂索引,适合几十 MB 到几百 MB 的文档量; 接口极简,一个 search() 就够; 能过滤、能排序、有点“语义味道”但不假装强 AI; 纯 Python,开箱即用。
你只要给它一些文本、文件路径或数据行,它就会帮你处理基本的关键词匹配、权重评分、结果整理。
举个最简单的例子,查找一堆 markdown 文档里包含 “proxy” 的句子:
from whose import Whose
engine = Whose()
engine.add_folder("./notes") # 自动读取文本文件
result = engine.search("proxy 超时")
for r in result:
print(r.path, r.score, r.line)
输出会类似:
./notes/network.md 0.82 "我们后来排查发现是反向 proxy 导致连接中断"
./notes/bug-list.md 0.60 "proxy timeout 问题记录"
不需要配置,不需要参数,也不需要 Elasticsearch 那些 schema、mapping、分词器。
Whose 做了什么?(你以为它只是 grep?其实不止)
它不是简单正则匹配,而是做了几点小增强:
自动拆词与模糊匹配
类似 "proxy 超时" 这种关键词,它会尝试拆开、模糊匹配、计算相关度。
文本按行评分
不是“这个文件包含关键词”这种粗粒度,而是:
每一行是一个结果,每一行有分数,越相关的排前面。
3. 自动忽略无关文件
默认会跳过二进制文件、太大的文件,只抓纯文本。
听起来还不错,它能用在哪儿?
Whose 更像一个“开发者随手工具”而不是生产级搜索引擎。所以它的用法非常生活化:
✔ 搜代码片段
你想找自己项目里所有和 “redis retry” 相关的代码,不需要 grep 表达式:
from whose import Whose
w = Whose()
w.add_folder("./project")
for r in w.search("redis retry 失败 重试次数"):
print(r.path, r.line_number, r.line)
✔ 搜知识库
你的 notes/ 下有几百篇 Markdown,想找“锁竞争”那篇写到哪个角落:
w = Whose()
w.add_folder("./notes")
results = w.search("锁竞争 MySQL")
✔ 做本地小搜索器的后端
拿 Flask 或 FastAPI 包一下,就变成一个迷你搜索 API:
from fastapi import FastAPI
from whose import Whose
app = FastAPI()
w = Whose()
w.add_folder("./docs")
@app.get("/search")
defsearch(q: str):
return [r.to_dict() for r in w.search(q)]
轻到你部署到树莓派都没问题。
它怎么做到“轻”又“够用”?
核心其实就是三步:
① 索引阶段:轻量级内存结构
它不会建立“倒排索引”,而是把文本拆成 token,构建一个简单的结构用于快速过滤。
② 匹配阶段:基于关键词评分
每个关键词都会计算:
行文本出现次数 是否连续出现 不同关键词之间的位置关系
最后加权得到一个分数。
③ 排序阶段:按分数优先 + 必要的阈值过滤
比如你搜索 “timeout retry”,匹配到 timeout 的行分数很高,但完全不含 retry 的行会被丢掉或排到后面。
简单讲讲内部实现(非常容易读)
这里给你一个简化版本的实现片段,让你感受一下它的风格:
import re
classWhose:
def__init__(self):
self.items = [] # [(path, line_number, text)]
defadd_file(self, path):
with open(path, encoding="utf-8", errors="ignore") as f:
for n, line in enumerate(f, 1):
self.items.append((path, n, line.strip()))
defadd_folder(self, path):
import os
for root, _, files in os.walk(path):
for name in files:
if name.endswith((".md", ".txt", ".py")):
self.add_file(os.path.join(root, name))
defsearch(self, query):
keywords = query.split()
results = []
for path, n, text in self.items:
score = sum(1for k in keywords if k.lower() in text.lower())
if score > 0:
results.append((score, path, n, text))
return sorted(results, reverse=True)
真实的 Whose 当然更复杂,也更高效,但核心思想就这么“朴素直接”。
它的适用边界(诚实讲)
Whose 不适合:
TB 级别的文档库 需要全文检索、分布式索引 复杂的中文分词场景 需要高查询 QPS 的生产服务
适合:
个人知识库 代码库搜索 配置、日志快速定位 快速构建“够用的小搜索API” 脚本级的数据查找任务
一句话:
如果你不想上 Elasticsearch、又对全文搜索有些小需求,那 Whose 就完全够了。
Whose 更像是“Python 工程师的小刀片工具”,轻松、简单、随手就能写个 demo,完全属于那种装在口袋里、用的时候特别爽,不用的时候就静静躺着的小工具。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB