200行Python造一个搜索系统awesome-reverse-engineered搜索排序算法逐行详解awesome-reverse-engineered 是一个收录 17,000 逆向工程开源项目的索引库而支撑它的搜索系统只用约 200 行 Python、零第三方依赖写成——没有 Elasticsearch、没有倒排索引库仅靠标准库的re、json、math就实现了字段加权、同义词扩展、热度加分和活跃度降权。本文逐行拆解这套搜索排序算法让你学一套可以直接搬到自己项目里的轻量搜索方案。一个数据文件 一个脚本 完整搜索系统这个项目的搜索架构简单到出乎意料组成文件作用数据data/entries.json17,000 条项目条目纯 JSON 数组CC0 协议引擎mcp/re_index.py约 200 行搜索 打分 排序逻辑入口CLI / mcp/reverse_engineered_mcp.py / plugin/skills/find-reverse-engineered/SKILL.md人、AI Agent 三种用法共用同一引擎每条数据就 10 个字段name、url、desc、category、subcategory、stars、lang、license、pushed、topics等。搜索的全部难度不在数据而在下面五个函数的写法。第一步数据加载与 24 小时缓存load() 按优先级从三处取数据环境变量RE_INDEX_DATA→ 本地缓存24 小时内有效→ 仓库自带的data/entries.json。网络拉取失败时自动降级到本地副本并只在 stderr 打一条 note。这种多级回退 静默降级保证了离线也能用path os.environ.get(RE_INDEX_DATA) # 1. 环境变量指定 if not fresh: # 2. 缓存超过 24h 就刷新 data urllib.request.urlopen(RAW, timeout20).read() for p in (CACHE, BUNDLED): # 3. 回退到本地文件 if os.path.exists(p): return json.load(open(p))字段权重表为什么项目名比描述值钱fields() 把每条数据拆成 7 个带权重的文本字段——这是排序质量的第一个关键return [ (3.0, 项目名), # name (3.0, slug), # URL 末段- 换成空格 (2.0, topics 拼接), (1.5, 一句话描述 desc), (1.0, subcategory), (1.0, category), (1.0, platform), ]字段权重理由项目名 / URL slug3.0命名最精确搜psd parser时命中名字就该排最前topics 标签2.0人工精选的关键词描述 desc1.5自然语言噪音较多分类 / 子分类 / 平台1.0粗粒度仅做辅助命中同字段的所有文本按max取最高权重避免在描述里出现 10 次刷分。短关键词的词边界匹配规则hit() 只有 4 行却解决了一个经典坑——短词误匹配def hit(term, text): if len(term) 3: return re.search(rf(?![a-z0-9]){re.escape(term)}(?![a-z0-9]), text) is not None return term in text词长 ≤3 时如ps、x、ai用词边界正则ps不会命中psd、x不会命中max词长 3 时直接子串匹配速度更快还能匹配bluetooth le这类短语片段。同义词扩展ps1 playstation 1 psxSYNONYMS 是一张手写双向映射表SYNONYMS { twitter: [x], x: [twitter], ps1: [playstation 1, psx], ps2: [playstation 2], ble: [bluetooth], bluetooth: [ble], car: [vehicle, automotive], chatgpt: [openai], ... }打分时每个查询词会带上同义词一起检索但同义词命中只算7 折w * 0.7——因为直接搜到ps1显然比搜ps1命中了playstation更可信。这比引入同义词词典或向量近邻便宜得多且完全可控。打分公式逐行拆解score() 是整个搜索系统的心脏for t in terms: # ① 每个查询词独立打分 for alt in [t] SYNONYMS.get(t, []): # ② 词 同义词 for w, text in fs: if hit(alt, text): best max(best, w if alt t else w * 0.7) # ③ 取最高权重同义词 7 折 if not matched: return None # ④ AND 语义任一词没命中 → 整条出局 total 0.8 * math.log10((e.get(stars) or 0) 1) # ⑤ 热度加分对数压缩 if e.get(archived): total - 1.0 # ⑥ 归档仓库 -1.0 if pushed 早于 3 年前: total - 0.5 # ⑦ 久不更新 -0.5 return (matched, total) # ⑧ 返回 (命中词数, 总分)几个值得抄走的细节AND 语义所有查询词都必须命中至少一个同义词否则返回None直接出局不产生沾边结果对数热度加分0.8 * log10(stars1)。1000 星只加 2.4 分6 万星加 3.8 分——热度能微调排名但永远不会压过名字精确命中3.0 分这个最强信号活跃度降权非官方 API 工具随厂商接口变动容易失效归档-1.0和 3 年没提交-0.5会把僵尸项目压下去返回二元组(matched, total)命中词数单独保存给下一级的排序用。过滤、排序与完整匹配优先search() 把前面所有零件串起来流程是硬过滤 → 打分 → 双键排序 → 分层截断# 硬过滤分类子串匹配 / 语言精确匹配 / 最低星数 / 归档开关 if category and category.lower() not in (e[category] e.get(subcategory, )).lower(): continue res.sort(keylambda x: (-x[0][0], -x[0][1])) # 先按命中词数再按总分 full [e for ... if m len(terms)] # 所有词都命中 partial [e for ... if m len(terms)] # 部分命中 return (full partial)[:limit], full # 完整匹配永远排在前面排序键(-命中词数, -总分)保证了语义相关性 分数搜 3 个词时命中 3 个词的低分项目一定排在只命中 2 个词的高分项目前面。最后把结果分成完整匹配 / 部分匹配两层CLI 在只有部分匹配时会明确提示no project matches all of: ...让用户知道自己在看兜底结果。fmt() 负责把结果渲染成编号 星数 · 语言 · License · 最后提交 分类路径的紧凑卡片方便人和 Agent 都能一眼读完。把 200 行脚本变成三种入口同一个re_index模块被复用出三种用法命令行零配置直接跑用仓库自带数据RE_INDEX_DATAdata/entries.json python3 mcp/re_index.py search psd parser --lang Python RE_INDEX_DATAdata/entries.json python3 mcp/re_index.py search whatsapp --min-stars 500 --json RE_INDEX_DATAdata/entries.json python3 mcp/re_index.py categoriesMCP 服务mcp/reverse_engineered_mcp.py 用 stdio JSON-RPC 把脚本包成 MCP server暴露search_reverse_engineered、get_reverse_engineered_project、list_reverse_engineered_categories三个工具Claude Code、Cursor 等 AI 客户端装好后即可让 Agent 先搜再逆向Agent Skillplugin/skills/find-reverse-engineered/SKILL.md 把搜索、选型看归档/活跃度/License、读 README、克隆的四步工作流写成了可执行的技能说明配套的 plugin/skills/find-reverse-engineered/scripts/re_index.py 与 mcp 版本由 scripts/render.py 保持同步。三个可直接复用的搜索排序设计多字段加权 max 取高给不同文本字段设权重同字段多次命中不叠加防止长描述刷分AND 主排序 部分匹配兜底严格结果和宽松结果分层展示而不是混在一起糊弄用户;对数热度 活跃度降权log(stars)让热度只做微调归档/停更惩罚让索引里的死项目自动沉底。整套系统从加载、匹配、打分到排序只有约 200 行、一个文件、零依赖——如果你的项目需要给几千条 JSON 数据加一个够聪明的搜索这套加权字段 词边界 同义词折减 双键排序的组合远比上一套搜索引擎划算。创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考