Files
dish-image/crawler/verify_repair.py
T
陈世睿 74caa9d112 初始化 dish-image:菜品图爬虫 + 图片名称检索平台
- 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 21:32:49 +08:00

104 lines
3.7 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""校验 dishes.jsonl 里记录的图是否都在盘上且为有效 JPEG;缺失/损坏的按记录的
image_url 重新下载(应对杀软清图 / 下载失败)。
python verify_repair.py # 只报告(dry-run)
python verify_repair.py --repair # 重下缺失/损坏的图
"""
import argparse, json, os, sys, time, random
import requests
for _s in (sys.stdout, sys.stderr):
try:
_s.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
def valid_jpeg(path: str) -> bool:
"""接受常见图片格式(jpg/png/gif/webp/bmp);过小多半是错误页/截断。"""
try:
if os.path.getsize(path) < 800:
return False
with open(path, "rb") as f:
b = f.read(12)
return (b[:3] == b"\xff\xd8\xff" or b[:8] == b"\x89PNG\r\n\x1a\n"
or b[:6] in (b"GIF87a", b"GIF89a")
or (b[:4] == b"RIFF" and b[8:12] == b"WEBP") or b[:2] == b"BM")
except OSError:
return False
def redownload(url: str, path: str) -> bool:
h = {"User-Agent": UA, "Referer": "https://www.xiachufang.com/",
"Accept": "image/avif,image/webp,image/png,image/*;q=0.8,*/*;q=0.5"}
try:
r = requests.get(url, headers=h, timeout=25)
if r.status_code == 200 and r.content:
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
with open(path, "wb") as f:
f.write(r.content)
return valid_jpeg(path)
except requests.RequestException as e:
print(f" [!] 重下失败 {e}", file=sys.stderr)
return False
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--jsonl", default="dishes.jsonl")
ap.add_argument("--repair", action="store_true", help="重下缺失/损坏的图")
args = ap.parse_args()
dishes = found = no_result = 0
total = present = missing = corrupt = repaired = failed = 0
bad_dishes = []
with open(args.jsonl, encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
rec = json.loads(line)
dishes += 1
if not rec.get("found"):
no_result += 1
bad_dishes.append(rec["dish"])
continue
found += 1
for e in rec.get("images", []):
fp = e.get("image_file")
if not fp:
continue
total += 1
ok = valid_jpeg(fp)
if ok:
present += 1
continue
if os.path.exists(fp):
corrupt += 1
else:
missing += 1
if args.repair and e.get("image_url"):
if redownload(e["image_url"], fp):
repaired += 1
else:
failed += 1
time.sleep(random.uniform(0.1, 0.3))
print(f"菜条目: {dishes} (有图 {found} / 无结果 {no_result})")
print(f"图片记录: {total} 在盘有效: {present} 缺失: {missing} 损坏: {corrupt}")
if args.repair:
print(f"重下成功: {repaired} 仍失败: {failed}")
if no_result:
print(f"无搜索结果的菜({no_result}): {''.join(bad_dishes[:40])}{'' if no_result>40 else ''}")
health = present + (repaired if args.repair else 0)
print(f"\n图片完好率: {health}/{total} = {100*health/total:.1f}%" if total else "无图片记录")
if __name__ == "__main__":
main()