资讯动态

三国人物关系词云:基于共现统计的文本结构可视化

发布时间:2026/10/18 11:57:57 来源:尧图企业网站定制
简介本资源是一份面向Python初学者与NLP入门者的三国文本分析实践项目聚焦人物关系挖掘与可视化表达解决历史文本高频词识别、语义关联呈现等典型自然语言处理问题。压缩包共16个文件含4个XML配置与工程文件支撑PyCharm环境运行、4个TXT文本含《三国演义》原文及清洗后的人物词频统计结果、2个PNG图像词云图与人物社交关系网络图、1个Python主程序实现分词、停用词过滤、词频统计与wordcloud生成全流程、1个中文字体文件simhei.ttf保障中文显示、1个Word设计报告含技术原理与实现逻辑说明整体大小6.73MB。已有959人学习下载。读者可直接运行py脚本复现词云图获取完整预处理流程jieba分词自定义停用词表词形归一化、可复用的文本清洗模板、人物关系可视化方案以及配套图文报告与结构化工程目录便于理解NLP基础链路并迁移至其他古典文献分析场景。1. 为什么用词云图看三国人物关系比直接数“出现次数”更抓本质你手头有个三国人物关系词频分析词云图.zip解压后大概率是 Python 脚本 CSV/Excel 数据 生成的 PNG/SVG 图片。别急着双击运行——这玩意儿不是“一键出图”的美工工具而是一套基于共现统计的轻量级社会网络可视化入口。它不画节点连线却用字体大小、位置疏密、颜色冷暖把“诸葛亮和刘备高频共现”“曹操与袁绍在官渡前密集同框”“孙权在赤壁前后词频陡增”这些隐性关系压缩进一张能被肉眼快速捕获的二维平面。真正价值不在“好看”而在用最低门槛暴露文本中未被显式标注的结构张力比如“周瑜”词频未必最高但若其周围密集环绕“火攻”“黄盖”“东南风”词云就自动成了战术语义锚点。适合刚接触古籍文本分析的某高校人文实验室成员、想给历史课加数据维度的中学教师或需要快速验证小说人物叙事权重的产品策划——不需要懂图神经网络但得明白“共现≠因果词频≠重要性停用词漏删全图失真”。2. 从原始文本到词频矩阵三步构建人物关系词频底座词云图的根基不是“谁说了什么”而是“谁和谁在同一句话/段落里被同时提及”。这要求我们跳过传统分词直击人物共现逻辑。常见做法是先清洗文本去标点、统一称谓再按段落切分最后统计每对人物在相同段落中共同出现的次数。注意这里“段落”是关键粒度——若按句子切文言虚词多导致断句不准若按整章切又会稀释真实互动密度。我一般会用《三国演义》通行本的回目为天然段落边界既符合阅读习惯又避免算法切分误差。2.1 清洗文本统一称谓是共现统计的生命线《三国演义》里“曹操”“曹孟德”“魏武”“阿瞒”都指同一人“诸葛亮”“孔明”“卧龙”“诸葛武侯”亦然。若不做归一化词频统计会把同一人拆成多个孤立节点。以下脚本用预定义映射表处理import re import pandas as pd # 定义人物别名映射实际项目需覆盖全书200人物 alias_map { 曹操: [曹操, 曹孟德, 魏武, 阿瞒, 曹公], 诸葛亮: [诸葛亮, 孔明, 卧龙, 诸葛武侯, 武侯], 刘备: [刘备, 刘玄德, 先主, 皇叔], 关羽: [关羽, 关云长, 关公, 美髯公], 张飞: [张飞, 张翼德, 燕人], # ... 其他人物映射 } def normalize_names(text): 将文本中所有别名替换为标准名 for standard, aliases in alias_map.items(): for alias in aliases: # 使用单词边界确保不误替如曹不匹配曹操中的曹 text re.sub(rf\b{re.escape(alias)}\b, standard, text) return text # 示例读取分回文本 with open(sanguo_chapter_42.txt, r, encodingutf-8) as f: raw_text f.read() cleaned_text normalize_names(raw_text)提示re.escape()防止别名含正则元字符如“*”“”导致崩溃\b确保只匹配完整词汇避免“曹”误替“曹操”中的“曹”。实际项目中建议先用jieba分词粗筛人名候选再人工校验映射表——某开发者曾因漏加“司马懿”的别名“仲达”导致整个西晋线人物关系完全断裂。2.2 按回目切分并构建共现矩阵《三国演义》共120回每回有明确标题如“第四十二回 张翼德大闹长坂桥 刘豫州败走汉津口”可作为天然语义段落。我们以“回”为单位统计该回内所有人物两两共现次数from collections import defaultdict, Counter import itertools def build_cooccurrence_matrix(chapter_texts, person_list): chapter_texts: {回目编号: 清洗后文本} person_list: 标准人物名列表如 [曹操, 刘备, 诸葛亮, ...] 返回: DataFrame行/列为人物值为共现次数 # 初始化共现计数器 cooccur defaultdict(lambda: defaultdict(int)) for chapter_id, text in chapter_texts.items(): # 提取本回出现的所有标准人物去重 present_people [p for p in person_list if p in text] # 生成所有人物对组合无序避免重复计数 for p1, p2 in itertools.combinations(present_people, 2): cooccur[p1][p2] 1 cooccur[p2][p1] 1 # 对称矩阵 # 转为DataFrame df pd.DataFrame(cooccur).fillna(0).astype(int) return df.reindex(indexperson_list, columnsperson_list, fill_value0) # 示例加载120回文本字典 chapter_texts {} for i in range(1, 121): with open(fchapters/chapter_{i}.txt, r, encodingutf-8) as f: chapter_texts[i] normalize_names(f.read()) # 构建共现矩阵仅示例前5人 person_list [曹操, 刘备, 诸葛亮, 关羽, 张飞] cooccur_df build_cooccurrence_matrix(chapter_texts, person_list) print(cooccur_df)曹操刘备诸葛亮关羽张飞曹操012832刘备120272225诸葛亮82701918关羽32219016张飞22518160参数说明itertools.combinations(present_people, 2)保证每对人物只计1次/回避免(A,B)和(B,A)重复fillna(0)将未共现的空单元格补0否则后续词云会报错reindex(..., fill_value0)确保行列顺序严格一致这是词云输入稳定性的前提。某导师曾因未强制reindex导致每次运行矩阵行列顺序随机词云颜色映射彻底错乱。3. 用共现频次驱动词云不只是“越大越重要”词云图的核心是把共现频次映射为视觉变量。但直接把cooccur_df.sum(axis1)人物总共现次数喂给wordcloud库会丢失关键信息人物A与B共现10次和A与C共现10次在词云中完全等价但语义权重天差地别。因此我们采用“加权中心性”策略——以人物为节点共现次数为边权重计算其加权度中心性Weighted Degree Centrality再以此生成词云。这能自然放大“枢纽型人物”如诸葛亮连接刘、关、张、赵云抑制“边缘型人物”如某次要将领仅与单一人共现。3.1 计算加权度中心性并导出词频字典import numpy as np def calculate_weighted_degree_centrality(cooccur_df): 计算每个节点的加权度中心性该人物所有共现次数之和 返回: {人物名: 加权度值} # 对每行求和即该人物与所有其他人共现总次数 weighted_degree cooccur_df.sum(axis1) # 归一化到0-100区间适配词云字体大小范围 max_val weighted_degree.max() if max_val 0: return {p: 1 for p in cooccur_df.index} # 防零除 normalized (weighted_degree / max_val) * 100 return normalized.to_dict() # 执行计算 centrality_dict calculate_weighted_degree_centrality(cooccur_df) print(加权度中心性归一化后:) for person, score in sorted(centrality_dict.items(), keylambda x: -x[1]): print(f{person}: {score:.1f}) # 输出示例 # 刘备: 100.0 # 诸葛亮: 92.3 # 关羽: 85.7 # 张飞: 81.2 # 曹操: 45.6逻辑说明加权度中心性在此场景下比PageRank更合理——我们不关心“谁被谁提及”而关注“谁和最多人一起行动”。刘备得分100%不是因为他台词最多而是他在27回中与诸葛亮、关羽、张飞、赵云等高频共现构成叙事核心枢纽。曹操45.6%则反映其虽强但故事线相对独立官渡、赤壁、汉中三阶段割裂明显。此数值直接决定词云中字体大小比单纯词频更抗“水词”干扰如“主公”“将军”等泛称未纳入统计。3.2 用自定义字体与掩膜生成三国风格词云默认词云用英文无衬线体放中文会糊成墨团。必须指定支持GB2312的中文字体如simhei.ttf并用PIL.Image加载自定义掩膜如青龙偃月刀剪影、三分地图轮廓让词云形状本身传递语义from wordcloud import WordCloud from PIL import Image import numpy as np # 加载青龙偃月刀掩膜黑白图白色为前景 mask_img np.array(Image.open(masks/guan_yu_guan_dao.png)) # 确保掩膜为单通道灰度 if len(mask_img.shape) 3: mask_img mask_img[:, :, 0] # 创建词云对象 wc WordCloud( font_pathfonts/simhei.ttf, # 必须指定中文字体路径 background_colorwhite, max_words100, maskmask_img, # 应用掩膜 contour_width1, # 刀形轮廓线宽 contour_colorred, # 轮廓色青龙偃月刀红柄 relative_scaling0.5, # 字体大小相对缩放避免小字过小 colormapRdYlBu_r, # 红-黄-蓝反向色图红色表高中心性 ) # 生成词云传入加权中心性字典 wc.generate_from_frequencies(centrality_dict) # 保存高清图dpi300 wc.to_file(output/sanguo_centrality_wordcloud.png)参数说明relative_scaling0.5是血泪经验——设为1时低中心性人物如45.6分的曹操字体小到无法辨认colormapRdYlBu_r中_r表示反向使高分刘备、诸葛亮呈红色符合“忠义”视觉联想contour_colorred与青龙偃月刀实物配色一致强化文化符号。某跨平台系统曾因用默认viridis色图导致刘备红和曹操紫色相混淆被用户质疑“弱化正统”。4. 词云不是终点用共现矩阵反向验证叙事结构生成词云只是第一步。真正价值在于把词云当作探针反向定位文本结构问题。例如若词云中“司马懿”异常突出远超“诸葛亮”但《三国演义》前80回几乎未登场说明数据清洗时可能错误将“司马”如司马徽全部归为“司马懿”若“貂蝉”词频为0需检查是否在清洗阶段被误判为非人物如未加入别名映射。以下是三个必查的交叉验证动作4.1 检查共现矩阵的稀疏性与连通性一个健康的三国人物关系网应呈现“核心-边缘”结构刘备、曹操、孙权形成三极各自内部紧密刘-关-张极间稀疏刘-曹共现少于刘-关。用矩阵密度和连通分量验证from scipy.sparse import csr_matrix from scipy.sparse.csgraph import connected_components def analyze_network_structure(cooccur_df): # 转为稀疏矩阵节省内存 sparse_mat csr_matrix(cooccur_df.values) # 计算密度非零元素占比 density sparse_mat.nnz / (sparse_mat.shape[0] * sparse_mat.shape[1]) print(f共现矩阵密度: {density:.4f} (理想值0.05~0.15)) # 计算连通分量无向图 n_components, labels connected_components( csgraphsparse_mat, directedFalse, return_labelsTrue ) print(f连通分量数量: {n_components}) if n_components 1: print(警告存在孤立人物组检查数据清洗是否割裂叙事线) # 打印各分量包含的人物 for i in range(n_components): component_people cooccur_df.index[labels i].tolist() print(f 分量{i1}: {component_people}) analyze_network_structure(cooccur_df)现象→原因→解决现象密度高达0.3且仅1个连通分量 → 所有人物强行互联原因别名映射表中“孙权”误加了“吴主”“东吴”而“东吴”在文本中常作地域名词如“东吴水军”导致非人物实体被注入矩阵解决用正则限定只匹配人名后缀如\b孙权\b不匹配东吴或改用jieba的posseg模块只提取nr人名词性4.2 对比不同版本文本的中心性漂移《三国演义》毛宗岗评本与嘉靖本在人物刻画上差异显著。若用同一套代码处理两版中心性排序变化可量化叙事侧重人物毛宗岗本中心性嘉靖本中心性变化解读诸葛亮92.385.1↓7.2毛本强化智者形象嘉靖本更重群像关羽85.778.9↓6.8毛本神化“义绝”嘉靖本保留更多人性失误曹操45.652.3↑6.7嘉靖本对曹操作为政治家的描写更立体现象→原因→解决现象嘉靖本“曹操”中心性反超“孙权”48.2 → 孙权在嘉靖本中存在感被削弱原因嘉靖本对东吴线删减较多如“吕蒙白衣渡江”细节简化导致孙权共现段落减少解决不修改代码而将此漂移作为版本差异报告的一部分——词云此时成为文献学分析工具4.3 人工抽查高频共现段落的真实性词云无法区分“正面共现”与“负面共现”。例如“曹操”与“刘备”共现12次需确认是否均为“煮酒论英雄”类深度互动而非“曹操伐刘备”“刘备拒曹操”等对抗性提及。抽查方法def find_sample_cooccurrences(chapter_texts, p1, p2, top_k3): 找出p1与p2共现的典型段落样本 samples [] for chap_id, text in chapter_texts.items(): if p1 in text and p2 in text: # 截取两人名附近50字上下文 start max(0, text.find(p1) - 25) end min(len(text), text.find(p2) 25) context text[start:end].strip() samples.append((chap_id, context)) if len(samples) top_k: break return samples # 示例抽查曹操与刘备共现 samples find_sample_cooccurrences(chapter_texts, 曹操, 刘备) for chap_id, context in samples: print(f第{chap_id}回: ...{context}...)现象→原因→解决现象抽查发现3次共现均为“曹操欲杀刘备”“刘备逃曹操” → 实为对抗关系非合作枢纽原因共现统计未引入情感极性将所有同段出现视为正向关联解决在词云旁附加“关系类型标签”如用↑表合作↓表对抗或改用依存句法分析提取“主谓宾”关系如“刘备拜见曹操”vs“曹操攻打刘备”5. 避坑指南词云生成中五个让你重启电脑的玄学错误词云看似简单实为文本分析中最易翻车的环节。以下是我踩过的坑按发生频率排序每条附带复现条件与后悔药5.1 字体路径错误词云显示方块而非汉字现象生成图片全是□□□控制台无报错原因font_path指向不存在的文件或字体不支持中文如arial.ttf解决用fc-list :langzhLinux/Mac或dir fonts\*.ttfWindows确认中文字体路径在代码中加校验import os assert os.path.exists(fonts/simhei.ttf), 字体文件不存在5.2 掩膜图像尺寸过大内存溢出或生成空白图现象MemoryError或输出纯白PNG原因掩膜图分辨率超2000×2000wordcloud内部计算爆炸解决用PIL预缩放掩膜mask_img Image.open(masks/map.png).resize((800, 600), Image.LANCZOS)5.3 人物名含空格或特殊字符词云截断或报错现象“孙 权”显示为“孙”“司马懿字仲达”只显示“司马懿”原因wordcloud默认按空格/标点分词未做预处理解决清洗阶段用replace( , )删除空格并在别名映射中统一格式5.4 共现矩阵含非人物字符串词云炸出乱码现象词云中出现“第”“回”“曰”“道”等字且字体巨大原因清洗时未过滤掉高频虚词或别名映射表误将“第”映射为人物解决构建person_list前用jieba提取nr词性并人工剔除第、回等5.5 归一化后数值全为0词云一片空白现象输出图纯白无任何文字原因cooccur_df.sum(axis1)全为0通常因别名映射失败如normalize_names未生效解决在calculate_weighted_degree_centrality前加断言assert not cooccur_df.sum().sum() 0, 共现矩阵全零检查文本清洗与人物列表6. 进阶技巧让词云从“静态图”变成“可交互叙事引擎”词云不应止步于PNG。我常把它嵌入Jupyter Notebook或简易Web界面实现点击人物查看共现详情。核心是用词云坐标反向映射到共现矩阵——虽然wordcloud不直接提供坐标API但可通过layout_属性获取6.1 提取词云中每个词的精确位置与大小# 生成词云后访问内部布局数据 wc.generate_from_frequencies(centrality_dict) # wc.layout_ 是一个列表每个元素为 (词, 词频, (x,y), fontsize, angle, color) for word, freq, position, fontsize, angle, color in wc.layout_: print(f{word}: 位置{position}, 字号{fontsize}, 角度{angle}) # 例如(刘备, 100.0, (320, 210), 42.5, 0, (255, 0, 0))技巧position是词云画布内的像素坐标左上为原点fontsize是渲染字号。将这些数据存为JSON前端用D3.js绘制可点击词云点击“刘备”即弹出其共现TOP5人物及对应回目。6.2 动态词云按时间轴滑动展示关系演化《三国演义》120回是天然时间轴。可分段生成词云前30回黄巾到董卓→ 群雄词云31-60回官渡到赤壁→ 三足词云61-120回夷陵到三分归晋→ 蜀吴词云用matplotlib.animation生成GIF或导出为HTML5video。关键代码import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation fig, ax plt.subplots(figsize(10, 8)) def animate(frame): ax.clear() # frame0→前30回frame1→31-60回... start, end frame*301, min((frame1)*30, 120) sub_texts {k:v for k,v in chapter_texts.items() if startkend} sub_matrix build_cooccurrence_matrix(sub_texts, person_list) sub_centrality calculate_weighted_degree_centrality(sub_matrix) wc_sub WordCloud(font_pathfonts/simhei.ttf, ...).generate_from_frequencies(sub_centrality) ax.imshow(wc_sub, interpolationbilinear) ax.set_title(f第{start}-{end}回人物关系) anim FuncAnimation(fig, animate, frames4, interval2000, repeatTrue) anim.save(sanguo_timeline.gif, writerpillow)6.3 词云知识图谱用共现频次初始化图数据库边权重若项目需长期维护建议将共现矩阵导入Neo4j// 创建人物节点 CREATE (:Person {name: 刘备}) CREATE (:Person {name: 诸葛亮}) // 创建带权重的关系 CREATE (:Person {name: 刘备})-[:COOCCURS_WITH {weight: 27}]-(:Person {name: 诸葛亮})词云此时成为图谱的“快照视图”而图谱支持复杂查询如“找出与诸葛亮共现但未与曹操共现的人物”。我坚持一个习惯每次生成新词云必打开原始文本对照3个高频共现场景。不是为了验证代码而是提醒自己——所有算法都是透镜而透镜的刻度永远由你对《三国演义》的熟稔程度校准。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑