别再只画词云了!用NetworkX挖掘《三国演义》隐藏的‘朋友圈’与势力图谱
从词频统计到关系网络:用NetworkX解锁《三国演义》的深层人物图谱
当大多数人还在用词云展示《三国演义》人物出现频率时,我们已经可以更进一步——揭示那些隐藏在文本深处的联盟、敌对与权力枢纽。传统词频统计就像只看到了历史舞台上的演员名单,而社会网络分析(SNA)则能还原整场戏剧的角色互动与势力版图。
1. 为什么词云不够?文本分析的维度升级
词云工具能快速呈现"谁说的多",但无法回答这些关键问题:
- 曹操与刘备的直接互动究竟有多少次?
- 诸葛亮是仅仅出场次数多,还是真正处于信息交换的核心位置?
- 当吕布这个名字出现时,通常与哪些人物形成强关联?
NetworkX构建的共现网络可以量化这些"关系数据"。我们定义两个人物如果在同一段落出现即产生连接,通过累计共现次数计算关系强度。这种分析方法与历史学家研究"谁与谁结盟"的思路高度一致。
提示:中文古典小说的段落结构天然适合共现分析,段落转换往往意味着场景切换
2. 环境配置与数据预处理
2.1 核心工具链
# 基础分析套件 import networkx as nx # 网络分析 import jieba.posseg as pseg # 带词性标注的分词 from collections import defaultdict # 高效计数 # 可视化组件 import matplotlib.pyplot as plt plt.style.use('ggplot') # 更美观的绘图风格2.2 人物别名归一化
处理古典小说必须解决的核心问题:
| 原始词 | 归一化结果 | 出现示例 |
|---|---|---|
| 孔明曰 | 诸葛亮 | "孔明曰:此计大妙" |
| 云长 | 关羽 | "云长提刀上马" |
| 玄德 | 刘备 | "玄德闻言大惊" |
实现代码示例:
name_mapping = { '孔明': '诸葛亮', '卧龙': '诸葛亮', '云长': '关羽', '关公': '关羽', '玄德': '刘备', '刘皇叔': '刘备' } def normalize_name(raw_name): return name_mapping.get(raw_name, raw_name)3. 构建人物关系网络的实战步骤
3.1 共现关系提取算法
- 段落级扫描:以
\n划分段落,保留自然场景上下文 - 双向关系计数:避免(A,B)和(B,A)被重复记录
- 权重归一化:将绝对次数转换为0-1之间的相对值
def build_relation_network(text, main_chars): relation_counts = defaultdict(int) paragraphs = [p for p in text.split('\n') if len(p) > 10] # 过滤短段落 for para in paragraphs: present_chars = set() for name in main_chars: if name in para: present_chars.add(name) # 记录所有共现组合 chars_list = list(present_chars) for i in range(len(chars_list)): for j in range(i+1, len(chars_list)): pair = tuple(sorted((chars_list[i], chars_list[j]))) relation_counts[pair] += 1 # 权重归一化 max_count = max(relation_counts.values()) if relation_counts else 1 return {k: v/max_count for k,v in relation_counts.items()}3.2 网络中心性指标解读
计算节点重要性的三大指标:
| 指标类型 | 计算方式 | 文学解读 |
|---|---|---|
| 度中心性 | 直接连接数 | 人际交往广度 |
| 接近中心性 | 到其他节点的平均距离 | 信息传播效率 |
| 中介中心性 | 占据最短路径的比例 | 权力枢纽程度 |
典型人物对比:
# 计算各项指标 degree_cent = nx.degree_centrality(G) betweenness_cent = nx.betweenness_centrality(G) # 结果示例 print("诸葛亮的中介中心性:", betweenness_cent.get("诸葛亮", 0)) print("吕布的度中心性:", degree_cent.get("吕布", 0))4. 多层次关系可视化技巧
4.1 权重阈值分层
设置不同权重区间呈现差异化的关系强度:
- 核心联盟(权重>0.6):用粗实线表示,如"刘备-诸葛亮"
- 一般关联(0.3<权重≤0.6):细实线表示,如"曹操-许褚"
- 弱联系(权重≤0.3):虚线表示,如"孙权-马超"
def visualize_network(G, weight_thresholds=[0.3, 0.6]): pos = nx.spring_layout(G, k=0.8, iterations=50) # 按权重分类边 strong_edges = [(u,v) for (u,v,d) in G.edges(data=True) if d['weight'] > weight_thresholds[1]] medium_edges = [(u,v) for (u,v,d) in G.edges(data=True) if weight_thresholds[0] < d['weight'] <= weight_thresholds[1]] weak_edges = [(u,v) for (u,v,d) in G.edges(data=True) if d['weight'] <= weight_thresholds[0]] plt.figure(figsize=(12,12)) nx.draw_networkx_nodes(G, pos, node_size=800, alpha=0.8) # 分层绘制边 nx.draw_networkx_edges(G, pos, edgelist=strong_edges, width=3, alpha=0.9, edge_color='darkred') nx.draw_networkx_edges(G, pos, edgelist=medium_edges, width=2, alpha=0.6, edge_color='orange') nx.draw_networkx_edges(G, pos, edgelist=weak_edges, width=1, alpha=0.3, edge_color='gray', style='dashed') nx.draw_networkx_labels(G, pos, font_size=10, font_family='SimHei') plt.axis('off')4.2 动态演化分析
通过分章回构建网络,观察关系变化:
# 分章回分析示例 chapter_networks = [] for chapter in text.split('第'): # 简单按章回分割 if not chapter: continue G_chapter = build_network(chapter, main_chars) chapter_networks.append(G_chapter) # 计算诸葛亮在各章的中介中心性 zhuge_evolution = [nx.betweenness_centrality(G).get('诸葛亮',0) for G in chapter_networks]5. 从数据回到文本:验证与解读
当发现夏侯惇的中介中心性异常高时,回到原文验证:
- 定位其关键连接点:主要关联曹操与曹仁
- 文本证据:"操令夏侯惇为都督,引曹仁、李典等守樊城"
- 历史背景:夏侯家族在曹魏军中的桥梁作用
这种分析方式不仅验证了网络指标的可信度,更能发现传统阅读容易忽略的次级重要人物。
