build_persona_tree.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 构建人设树的中间数据
  5. 输入:节点列表.json, 边关系.json
  6. 输出:persona_tree.json(包含分类和标签的层级树结构)
  7. """
  8. import json
  9. from pathlib import Path
  10. import sys
  11. # 添加项目根目录到路径
  12. project_root = Path(__file__).parent.parent.parent
  13. sys.path.insert(0, str(project_root))
  14. from script.data_processing.path_config import PathConfig
  15. def build_persona_tree():
  16. """构建人设树数据"""
  17. config = PathConfig()
  18. print(f"账号: {config.account_name}")
  19. print(f"输出版本: {config.output_version}")
  20. print()
  21. node_list_file = config.intermediate_dir / "节点列表.json"
  22. edge_list_file = config.intermediate_dir / "边关系.json"
  23. output_file = config.intermediate_dir / "persona_tree.json"
  24. # 读取节点
  25. print(f"读取节点列表: {node_list_file.name}")
  26. with open(node_list_file, "r", encoding="utf-8") as f:
  27. node_data = json.load(f)
  28. all_nodes = node_data.get("节点列表", [])
  29. # 分离分类和标签
  30. category_nodes = [n for n in all_nodes if n.get("节点类型") == "分类"]
  31. tag_nodes = [n for n in all_nodes if n.get("节点类型") == "标签"]
  32. print(f" 分类节点: {len(category_nodes)}")
  33. print(f" 标签节点: {len(tag_nodes)}")
  34. # 读取边关系(获取所有边)
  35. print(f"读取边关系: {edge_list_file.name}")
  36. with open(edge_list_file, "r", encoding="utf-8") as f:
  37. edge_data = json.load(f)
  38. all_edges = edge_data.get("边列表", [])
  39. # 统计各类型边
  40. edge_type_counts = {}
  41. for e in all_edges:
  42. t = e.get("边类型", "未知")
  43. edge_type_counts[t] = edge_type_counts.get(t, 0) + 1
  44. for t, count in sorted(edge_type_counts.items(), key=lambda x: -x[1]):
  45. print(f" {t}: {count}")
  46. # 构建树结构
  47. tree_nodes = []
  48. tree_edges = []
  49. # 添加分类节点
  50. for n in category_nodes:
  51. tree_nodes.append({
  52. "节点ID": n["节点ID"],
  53. "节点名称": n["节点名称"],
  54. "节点类型": "分类",
  55. "节点层级": n.get("节点层级", ""),
  56. "所属分类": n.get("所属分类", []),
  57. "帖子数": n.get("帖子数", 0)
  58. })
  59. # 添加标签节点
  60. for n in tag_nodes:
  61. tree_nodes.append({
  62. "节点ID": n["节点ID"],
  63. "节点名称": n["节点名称"],
  64. "节点类型": "标签",
  65. "节点层级": n.get("节点层级", ""),
  66. "所属分类": n.get("所属分类", []),
  67. "帖子数": n.get("帖子数", 0)
  68. })
  69. # 构建节点ID集合和名称映射
  70. node_ids = set(n["节点ID"] for n in tree_nodes)
  71. # 按层级构建分类名称到ID的映射
  72. category_name_to_id = {}
  73. for n in category_nodes:
  74. level = n.get("节点层级", "")
  75. name = n.get("节点名称", "")
  76. category_name_to_id[(level, name)] = n["节点ID"]
  77. # 从分类的"所属分类"字段构建分类之间的层级边(统一用"属于")
  78. for n in category_nodes:
  79. level = n.get("节点层级", "")
  80. parent_names = n.get("所属分类", [])
  81. if parent_names:
  82. parent_name = parent_names[-1] # 取最后一个作为直接父分类
  83. parent_id = category_name_to_id.get((level, parent_name))
  84. if parent_id:
  85. tree_edges.append({
  86. "源节点ID": n["节点ID"],
  87. "目标节点ID": parent_id,
  88. "边类型": "属于"
  89. })
  90. # 添加所有原始边(两端节点都在树中的,排除"包含"边因为与"属于"重复)
  91. for e in all_edges:
  92. src_id = e["源节点ID"]
  93. tgt_id = e["目标节点ID"]
  94. edge_type = e["边类型"]
  95. # 跳过"包含"边(与"属于"是反向关系,保留"属于"即可)
  96. if edge_type == "包含":
  97. continue
  98. if src_id in node_ids and tgt_id in node_ids:
  99. tree_edges.append({
  100. "源节点ID": src_id,
  101. "目标节点ID": tgt_id,
  102. "边类型": edge_type,
  103. "边详情": e.get("边详情", {})
  104. })
  105. # 从标签的"所属分类"字段补充标签->分类的边(如果不存在)
  106. for n in tag_nodes:
  107. level = n.get("节点层级", "")
  108. parent_names = n.get("所属分类", [])
  109. if parent_names:
  110. parent_name = parent_names[-1]
  111. parent_id = category_name_to_id.get((level, parent_name))
  112. if parent_id:
  113. # 检查是否已存在属于边
  114. edge_exists = any(
  115. e["源节点ID"] == n["节点ID"] and e["目标节点ID"] == parent_id
  116. and e["边类型"] == "属于"
  117. for e in tree_edges
  118. )
  119. if not edge_exists:
  120. tree_edges.append({
  121. "源节点ID": n["节点ID"],
  122. "目标节点ID": parent_id,
  123. "边类型": "属于",
  124. "边详情": {}
  125. })
  126. # 统计各类型边
  127. tree_edge_counts = {}
  128. for e in tree_edges:
  129. t = e["边类型"]
  130. tree_edge_counts[t] = tree_edge_counts.get(t, 0) + 1
  131. print()
  132. print(f"构建人设树:")
  133. print(f" 总节点数: {len(tree_nodes)}")
  134. print(f" 总边数: {len(tree_edges)}")
  135. for t, count in sorted(tree_edge_counts.items(), key=lambda x: -x[1]):
  136. print(f" {t}: {count}")
  137. # 输出
  138. output_data = {
  139. "说明": {
  140. "描述": "人设树结构数据(包含分类、标签和所有边类型)",
  141. "分类节点数": len(category_nodes),
  142. "标签节点数": len(tag_nodes),
  143. "总边数": len(tree_edges),
  144. "边类型统计": tree_edge_counts
  145. },
  146. "nodes": tree_nodes,
  147. "edges": tree_edges
  148. }
  149. with open(output_file, "w", encoding="utf-8") as f:
  150. json.dump(output_data, f, ensure_ascii=False, indent=2)
  151. print()
  152. print(f"输出文件: {output_file}")
  153. return output_file
  154. if __name__ == "__main__":
  155. build_persona_tree()