#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 读取 db/menu_map.json, 构建功能树写入 SQLite(features 表)。 层级: level0=顶层模块, level1=分组, level2=菜单页(叶子) 并为每个叶子分配期望截图路径 screenshots/<序号_模块>/<分组>/<序号_名称>.jpg menu_map.json 结构(由实时爬取生成): { "<模块名>": { "loc": "落地页URL", "desc": "模块释义(可选)", "understanding": "开发理解(可选)", "groups": [ {"title": "分组名", "items": [ {"name":"页名","href":"/路径","desc":"","understanding":""}, ...] } ], "single": {"name":"页名","href":"/路径"} # 无侧边栏的单页模块(可选) }, ... } """ import json, os, re, sqlite3 BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) MAP = os.path.join(BASE, "db", "menu_map.json") DB = os.path.join(BASE, "db", "research.db") ORIGIN = "https://s-demob.xinrenxinshi.show" def sanitize(s): s = re.sub(r'[\\/:*?"<>|]', "_", s or "").strip() return s[:40] or "page" def full_url(href): if not href: return "" if href.startswith("http"): return href return ORIGIN + (href if href.startswith("/") else "/" + href) def main(): data = json.load(open(MAP, encoding="utf-8")) conn = sqlite3.connect(DB) conn.row_factory = sqlite3.Row cur = conn.cursor() cur.execute("DELETE FROM features") def up(obj): keys = list(obj.keys()); cols = ",".join(keys); ph = ",".join(["?"] * len(keys)) cur.execute(f"INSERT OR REPLACE INTO features ({cols}) VALUES ({ph})", [obj[k] for k in keys]) for mi, module in enumerate(data.keys(), 1): v = data[module] mod_key = f"mod::{module}" seq_mod = f"{mi:02d}_{sanitize(module)}" up(dict(node_key=mod_key, parent_key="", level=0, name=module, full_path=module, url=v.get("loc", ""), description=v.get("desc", f"{module}模块。"), understanding=v.get("understanding", ""), sort_order=mi, keep=1)) groups = v.get("groups", []) single = v.get("single") flat_items = v.get("items") # 扁平模式: 模块直接挂功能页(level2), 无中间分组层 if not groups and flat_items: for ii, it in enumerate(flat_items, 1): nm = it["name"]; href = it.get("href", "") leaf_key = f"{module}::{href or nm}" rel = f"screenshots/{seq_mod}/{ii:02d}_{sanitize(nm)}.jpg" up(dict(node_key=leaf_key, parent_key=mod_key, level=2, name=nm, full_path=f"{module} / {nm}", url=full_url(href), description=it.get("desc", f"{module}模块下「{nm}」相关功能页。"), understanding=it.get("understanding", ""), screenshot_path=rel, sort_order=ii, keep=1)) continue if not groups and single: nm = single["name"]; href = single.get("href", "") leaf_key = f"{module}::{href or nm}" rel = f"screenshots/{seq_mod}/{sanitize(nm)}.jpg" up(dict(node_key=leaf_key, parent_key=mod_key, level=2, name=nm, full_path=f"{module} / {nm}", url=full_url(href), description=single.get("desc", f"{module}模块下「{nm}」相关功能页。"), understanding=single.get("understanding", ""), screenshot_path=rel, sort_order=1, keep=1)) continue gi = 0 for g in groups: gtitle = g.get("title") or "常规" gi += 1 grp_key = f"{mod_key}::grp::{gi}_{gtitle}" up(dict(node_key=grp_key, parent_key=mod_key, level=1, name=gtitle, full_path=f"{module} / {gtitle}", url="", description=f"{module}模块下「{gtitle}」功能分组。", understanding="", sort_order=gi, keep=1)) for ii, it in enumerate(g.get("items", []), 1): nm = it["name"]; href = it.get("href", "") leaf_key = f"{module}::{href or (gtitle + '/' + nm)}" rel = f"screenshots/{seq_mod}/{sanitize(gtitle)}/{ii:02d}_{sanitize(nm)}.jpg" up(dict(node_key=leaf_key, parent_key=grp_key, level=2, name=nm, full_path=f"{module} / {gtitle} / {nm}", url=full_url(href), description=it.get("desc", f"{module}模块下「{nm}」相关功能页。"), understanding=it.get("understanding", ""), screenshot_path=rel, sort_order=ii, keep=1)) conn.commit() for lv in (0, 1, 2): n = cur.execute("SELECT COUNT(*) FROM features WHERE level=?", (lv,)).fetchone()[0] print(f"level{lv}: {n}") print("total:", cur.execute("SELECT COUNT(*) FROM features").fetchone()[0]) conn.close() if __name__ == "__main__": main()