# -*- coding: utf-8 -*- """ 从Excel提取数据生成知识图谱CSV文件 保持原始数据表述不变 """ import pandas as pd import re import json from pathlib import Path def extract_batches(df): """提取所有唯一的批次(保持原始表述)""" batches = {} batch_counter = {} for batch_str in df['项目批次'].dropna().unique(): if not batch_str or str(batch_str).strip() == '': continue # 解析批次字符串(可能包含多个批次) batch_items = re.split(r'[,、,]', str(batch_str)) for item in batch_items: item = item.strip() if not item: continue # 生成批次ID(使用原始表述的哈希) if item not in batch_counter: batch_counter[item] = 1 else: batch_counter[item] += 1 batch_id = f"BATCH-{abs(hash(item)) % 100000:05d}" if batch_id not in batches: batches[batch_id] = { 'name': item, # 保持原始表述 'original_string': item } return batches def parse_inheritor_field(inheritor_str): """解析传承人字段,保持原始表述(如"吴明新(国)")""" if not inheritor_str or str(inheritor_str).strip() in ['无', '']: return [] # 按顿号、逗号分割,保持原始表述 inheritors = re.split(r'[、,,\n]', str(inheritor_str)) inheritors = [inh.strip() for inh in inheritors if inh.strip() and inh.strip() != '无'] return inheritors def extract_inheritors(df): """提取所有唯一传承人(保持原始表述)""" inheritors = {} inheritor_counter = {} for idx, row in df.iterrows(): inheritor_str = row.get('代表性传承人', '') if not inheritor_str or str(inheritor_str).strip() in ['无', '']: continue # 解析传承人列表 inheritor_names = parse_inheritor_field(inheritor_str) for name in inheritor_names: # 使用原始名称(包括括号)作为key if name not in inheritor_counter: inheritor_counter[name] = 1 else: inheritor_counter[name] += 1 # 生成传承人ID inheritor_id = f"INH-{abs(hash(name)) % 100000:05d}-{inheritor_counter[name]}" if inheritor_id not in inheritors: inheritors[inheritor_id] = { 'name': name # 保持原始表述,如"吴明新(国)" } return inheritors def extract_institutions(df): """提取所有唯一保护机构(保持原始表述)""" institutions = {} inst_counter = {} for idx, row in df.iterrows(): inst_str = row.get('项目保护单位', '') if not inst_str or str(inst_str).strip() in ['', '无']: continue inst_name = str(inst_str).strip() # 使用原始机构名 if inst_name not in inst_counter: inst_counter[inst_name] = 1 else: inst_counter[inst_name] += 1 # 生成机构ID inst_id = f"INST-{abs(hash(inst_name)) % 100000:05d}-{inst_counter[inst_name]}" if inst_id not in institutions: institutions[inst_id] = { 'name': inst_name # 保持原始表述 } return institutions def parse_batch_field(batch_str, batches_dict): """解析批次字段,返回批次ID列表""" if not batch_str or str(batch_str).strip() == '': return [] batches = re.split(r'[,、,]', str(batch_str)) batch_ids = [] for batch in batches: batch = batch.strip() if batch: # 查找对应的批次ID for batch_id, batch_info in batches_dict.items(): if batch_info['name'] == batch: batch_ids.append(batch_id) break return batch_ids def get_institution_id(institution_name, institutions_dict): """获取机构ID""" if not institution_name or str(institution_name).strip() in ['', '无']: return None # 从已提取的机构中查找 for inst_id, inst_info in institutions_dict.items(): if inst_info['name'] == str(institution_name).strip(): return inst_id return None def get_inheritor_id(inheritor_name, inheritors_dict): """获取传承人ID""" if not inheritor_name or not inheritor_name.strip(): return None # 从已提取的传承人中查找 for inheritor_id, inheritor_info in inheritors_dict.items(): if inheritor_info['name'] == inheritor_name: return inheritor_id return None def clean_data(df): """数据清洗""" # 去除完全空白的行 df = df.dropna(how='all') # 填充缺失值为空字符串 for col in df.columns: if df[col].dtype == 'object': df[col] = df[col].fillna('') return df def build_nodes(df): """构建节点数据(保持原始表述)""" nodes_list = [] print("正在构建节点...") # 1. ICH_Project节点 print(" - 构建ICH_Project节点...") for idx, row in df.iterrows(): seq_num = row.get('总序号', idx + 1) project_name = row.get('项目名称', '') # 确定级别(从批次字段推断) batch_str = str(row.get('项目批次', '')) level = '国家级' if '国家级' in batch_str else ('省级' if '省级' in batch_str else '') nodes_list.append({ 'id': f"ICH-{int(seq_num)}", 'label': str(project_name), 'type': 'ICH_Project', 'properties': json.dumps({ 'level': level, 'category': row.get('类别', ''), # 保持原始表述,如"曲艺类" 'batch': batch_str, 'protection_unit': row.get('项目保护单位', '') }, ensure_ascii=False) }) # 2. Category节点(从数据中动态提取,保持原始表述) print(" - 构建Category节点...") unique_categories = df['类别'].dropna().unique() for cat_name in unique_categories: # 使用类别名称作为ID(使用哈希避免特殊字符) cat_id = f"CAT-{abs(hash(cat_name)) % 100000:05d}" nodes_list.append({ 'id': cat_id, 'label': cat_name, # 保持原始表述,如"曲艺类" 'type': 'Category', 'properties': '{}' }) # 3. Batch节点(动态识别,保持原始表述) print(" - 构建Batch节点...") batches = extract_batches(df) for batch_id, batch_info in batches.items(): nodes_list.append({ 'id': batch_id, 'label': batch_info['name'], # 保持原始表述,如"国家级第1批" 'type': 'Batch', 'properties': json.dumps({ 'original_string': batch_info['original_string'] }, ensure_ascii=False) }) # 4. Inheritor节点(动态识别,保持原始表述) print(" - 构建Inheritor节点...") inheritors = extract_inheritors(df) for inheritor_id, inheritor_info in inheritors.items(): nodes_list.append({ 'id': inheritor_id, 'label': inheritor_info['name'], # 保持原始表述,如"吴明新(国)" 'type': 'Inheritor', 'properties': '{}' }) # 5. Institution节点(动态识别,保持原始表述) print(" - 构建Institution节点...") institutions = extract_institutions(df) for inst_id, inst_info in institutions.items(): nodes_list.append({ 'id': inst_id, 'label': inst_info['name'], # 保持原始表述 'type': 'Institution', 'properties': '{}' }) return nodes_list, batches, inheritors, institutions def build_relations(df, batches, inheritors, institutions): """构建关系数据""" relations_list = [] print("正在构建关系...") # 建立名称到ID的快速查找映射 category_map = {} for cat_name in df['类别'].dropna().unique(): cat_id = f"CAT-{abs(hash(cat_name)) % 100000:05d}" category_map[cat_name] = cat_id for idx, row in df.iterrows(): seq_num = row.get('总序号', idx + 1) project_id = f"ICH-{int(seq_num)}" # 1. 项目 → 类别 category_name = row.get('类别', '') if category_name and category_name in category_map: category_id = category_map[category_name] relations_list.append({ 'source': project_id, 'target': category_id, 'type': 'BELONGS_TO', 'properties': '{}' }) # 2. 项目 → 批次(支持多个批次) batch_str = row.get('项目批次', '') if batch_str: batch_ids = parse_batch_field(batch_str, batches) for batch_id in batch_ids: relations_list.append({ 'source': project_id, 'target': batch_id, 'type': 'SELECTED_IN_BATCH', 'properties': '{}' }) # 3. 项目 → 保护机构 institution_name = row.get('项目保护单位', '') if institution_name and str(institution_name).strip() not in ['', '无']: institution_id = get_institution_id(institution_name, institutions) if institution_id: relations_list.append({ 'source': project_id, 'target': institution_id, 'type': 'PROTECTED_BY', 'properties': '{}' }) # 4. 项目 → 传承人(支持多个传承人) inheritor_str = row.get('代表性传承人', '') if inheritor_str: inheritor_names = parse_inheritor_field(inheritor_str) for name in inheritor_names: inheritor_id = get_inheritor_id(name, inheritors) if inheritor_id: relations_list.append({ 'source': project_id, 'target': inheritor_id, 'type': 'HAS_INHERITOR', 'properties': '{}' }) return relations_list def generate_report(nodes_df, relations_df, output_dir): """生成统计报告""" report_lines = [] report_lines.append("# 知识图谱CSV提取报告\n") report_lines.append(f"生成时间: {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')}\n") report_lines.append("---\n\n") # 节点统计 report_lines.append("## 节点统计\n\n") report_lines.append(f"**节点总数**: {len(nodes_df)}\n\n") node_type_counts = nodes_df['type'].value_counts().sort_index() report_lines.append("| 节点类型 | 数量 | 占比 |\n") report_lines.append("|---------|------|------|\n") for node_type, count in node_type_counts.items(): percentage = (count / len(nodes_df) * 100) report_lines.append(f"| {node_type} | {count} | {percentage:.1f}% |\n") # 关系统计 report_lines.append("\n## 关系统计\n\n") report_lines.append(f"**关系总数**: {len(relations_df)}\n\n") rel_type_counts = relations_df['type'].value_counts().sort_index() report_lines.append("| 关系类型 | 数量 | 占比 |\n") report_lines.append("|---------|------|------|\n") for rel_type, count in rel_type_counts.items(): percentage = (count / len(relations_df) * 100) report_lines.append(f"| {rel_type} | {count} | {percentage:.1f}% |\n") # 保存报告 report_path = output_dir / 'extraction_report.md' with open(report_path, 'w', encoding='utf-8') as f: f.writelines(report_lines) print(f"\n报告已生成: {report_path}") # 打印统计信息 print("\n" + "="*50) print("数据提取完成") print("="*50) print(f"\n节点总数: {len(nodes_df)}") for node_type, count in node_type_counts.items(): print(f" - {node_type}: {count}") print(f"\n关系总数: {len(relations_df)}") for rel_type, count in rel_type_counts.items(): print(f" - {rel_type}: {count}") print("="*50) def extract_data_from_excel(): """从Excel提取数据并生成知识图谱CSV文件""" print("开始从Excel提取数据...") # 1. 读取Excel excel_path = r'E:\Project\2026_KG_ICH\data\黑龙江国家级和省级非遗名单.xlsx' print(f"读取文件: {excel_path}") df = pd.read_excel(excel_path) print(f"原始数据: {len(df)} 行 x {len(df.columns)} 列") # 2. 提取所需列 columns = ['总序号', '类别', '项目名称', '项目批次', '项目保护单位', '代表性传承人'] print(f"\n提取列: {', '.join(columns)}") # 检查列是否存在 available_cols = [col for col in columns if col in df.columns] if len(available_cols) < len(columns): missing = set(columns) - set(available_cols) print(f"警告: 以下列不存在: {missing}") df = df[available_cols] print(f"提取后数据: {len(df)} 行 x {len(df.columns)} 列") # 3. 数据清洗 print("\n数据清洗...") df = clean_data(df) print(f"清洗后数据: {len(df)} 行") # 4. 构建节点 nodes_list, batches, inheritors, institutions = build_nodes(df) nodes_df = pd.DataFrame(nodes_list) print(f"节点数据: {len(nodes_df)} 行") # 5. 构建关系 relations_list = build_relations(df, batches, inheritors, institutions) relations_df = pd.DataFrame(relations_list) print(f"关系数据: {len(relations_df)} 行") # 6. 保存CSV output_dir = Path(r'E:\Project\2026_KG_ICH\dofile\kg_project\output') output_dir.mkdir(parents=True, exist_ok=True) print(f"\n保存文件到: {output_dir}") # 保存为UTF-8-BOM编码(Excel友好) nodes_path = output_dir / 'nodes.csv' relations_path = output_dir / 'rels.csv' nodes_df.to_csv(nodes_path, index=False, encoding='utf-8-sig') relations_df.to_csv(relations_path, index=False, encoding='utf-8-sig') print(f" - nodes.csv: {nodes_path}") print(f" - rels.csv: {relations_path}") # 7. 生成统计报告 generate_report(nodes_df, relations_df, output_dir) return nodes_df, relations_df if __name__ == '__main__': extract_data_from_excel()