Python技术迷

一个 Python 的轻量级搜索工具 -- Whose

有时候你应该也碰到过这种情况:一堆本地数据、配置、日志、Markdown 文档、甚至一些乱七八糟的文本散落在不同目录里,想找点东西却不知道它在哪。grep?能用,但写复杂一点就要回忆参数;自己写脚本?能写,但重复造轮子。

Whose 就是为这种“我就想随便搜点东西,但又不想折腾”的场景准备的一个小工具。它轻量、无依赖、API 简单,用几行 Python 就能实现一个可用的小搜索器,不上升到 Elasticsearch 那种重量级,也不需要维护索引服务。

一句话总结:

Whose = 轻量级文件/文本搜索库 + Python 的自然表达方式

它的设计思路:简单但不简陋

Whose 的理念很直接:

  1. 不构建复杂索引,适合几十 MB 到几百 MB 的文档量;
  2. 接口极简,一个 search() 就够;
  3. 能过滤、能排序、有点“语义味道”但不假装强 AI;
  4. 纯 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?其实不止)

它不是简单正则匹配,而是做了几点小增强:

  1. 自动拆词与模糊匹配

类似 "proxy 超时" 这种关键词,它会尝试拆开、模糊匹配、计算相关度。

  1. 文本按行评分

不是“这个文件包含关键词”这种粗粒度,而是:

每一行是一个结果,每一行有分数,越相关的排前面。

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