Files
陈世睿 917613c5ce 初始化 dish-image-toolkit:菜品图爬虫 + 图片名称检索平台
- crawler/:豆果(主)/下厨房/Bing 爬虫 + dishes.jsonl(3056 菜→9167 图映射)+ verify_repair 按 URL 重下
- retrieval/:三路检索(BM25 + 本地 BGE-M3 向量 + RRF 融合),FastAPI + 前端;
  写死图片目录(默认 crawler/images,可 IMAGE_DIR 覆盖)、绑 0.0.0.0 局域网访问、
  启动自动建索引、服务器 serve 图片
- 图片(1.7G)与向量模型(2.3G)不进 git

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 10:27:51 +08:00

38 lines
1.1 KiB
Python

# -*- coding: utf-8 -*-
"""中文 BM25 检索(jieba 分词 + rank_bm25)。文档 = 菜名。
菜名通常很短(2~6 字),除 jieba 词以外再补单字,提升短词召回。
"""
from __future__ import annotations
import re
import jieba
import numpy as np
from rank_bm25 import BM25Okapi
jieba.initialize()
def tokenize(text: str) -> list[str]:
text = text.replace("_", " ").strip()
words = [t for t in jieba.lcut_for_search(text) if t.strip()]
chars = [c for c in re.sub(r"\s+", "", text) if "" <= c <= "鿿"]
toks = words + chars
return toks if toks else [t for t in re.sub(r"\s+", "", text)] or ["_"]
class BM25Index:
def __init__(self, names: list[str]):
self.names = list(names)
self.tokenized = [tokenize(n) for n in self.names]
self.bm25 = BM25Okapi(self.tokenized)
def search(self, query: str, topk: int = 30) -> list[tuple[str, float]]:
q = tokenize(query)
if not q:
return []
scores = self.bm25.get_scores(q)
order = np.argsort(scores)[::-1][:topk]
return [(self.names[i], float(scores[i])) for i in order if scores[i] > 0]