jieba 分词 加 共现矩阵构建:《釜山行》人物关系提取(二)

作者:袖梨 2026-07-27

jieba 分词 + 共现矩阵构建——《釜山行》人物关系提取(二)

1. 回顾与目标

第一篇已经搭好了四节点环境。本篇在 node-01 上完成:

jieba 分词 + 共现矩阵构建——《釜山行》人物关系提取(二)

  1. 加载自定义 jieba 词典;
  2. 对《釜山行》文本分词;
  3. 统计人物出现频次;
  4. 段落为窗口构建人物共现矩阵;
  5. 导出 nodes.csvedges.csvstats.json

2. 完整代码(busan_segment.py)

 复制代码#!/usr/bin/env python3
"""
jieba 分词 + 人物关系提取 — 《釜山行》
节点: node-01 (1.94.202.19)
功能: 加载自定义词典 -> 分词 -> 统计人物出现频次 -> 按段落构建共现关系
"""
import jieba
import jieba.posseg as pseg
import json
from collections import defaultdict, Counter# ============ 配置 ============
TEXT_FILE = "/root/busan_text.txt"
DICT_FILE = "/root/busan_dict.txt"
CHARACTERS = ["石宇", "秀安", "尚华", "盛京", "珍熙", "英国", "容国", 
              "老姐姐", "老妹妹", "乞丐", "列车长"]# ============ 1. 加载自定义词典 ============
print("=" * 60)
print("步骤1: 加载自定义人物词典")
print("=" * 60)
jieba.load_userdict(DICT_FILE)
print(f"已加载词典: {DICT_FILE}")
print(f"人物列表: {CHARACTERS}")# ============ 2. 读取文本 ============
print("n" + "=" * 60)
print("步骤2: 读取《釜山行》文本")
print("=" * 60)
with open(TEXT_FILE, "r", encoding="utf-8") as f:
    text = f.read()
print(f"文本总长度: {len(text)} 字符")
print(f"文本前100字: {text[:100]}...")# ============ 3. 分词处理 ============
print("n" + "=" * 60)
print("步骤3: jieba 分词处理")
print("=" * 60)
words = jieba.lcut(text)                # 精确模式分词
words_pos = pseg.lcut(text)             # 带词性标注
print(f"分词总数: {len(words)} 个词")# ============ 4. 统计人物出现频次 ============
print("n" + "=" * 60)
print("步骤4: 统计人物出现频次")
print("=" * 60)
char_count = Counter()
for w in words:
    if w in CHARACTERS:
        char_count[w] += 1for char, count in char_count.most_common():
    print(f"{char:<10} {count:<10}")# ============ 5. 按段落构建共现关系 ============
print("n" + "=" * 60)
print("步骤5: 构建人物共现关系(基于段落窗口)")
print("=" * 60)
paragraphs = [p.strip() for p in text.split("n") if p.strip()]
print(f"段落数: {len(paragraphs)}")co_occurrence = defaultdict(lambda: defaultdict(int))for para in paragraphs:
    para_words = jieba.lcut(para)
    chars_in_para = list(set([w for w in para_words if w in CHARACTERS]))    for i in range(len(chars_in_para)):
        for j in range(i + 1, len(chars_in_para)):
            c1, c2 = chars_in_para[i], chars_in_para[j]
            co_occurrence[c1][c2] += 1
            co_occurrence[c2][c1] += 1# ============ 6. 输出共现关系 ============
edges = []
for c1 in CHARACTERS:
    for c2 in CHARACTERS:
        if c1 < c2 and co_occurrence[c1][c2] > 0:
            edges.append((c1, c2, co_occurrence[c1][c2]))print(f"n共现关系总数: {len(edges)} 条")# ============ 7. 保存结果 ============
with open("/root/nodes.csv", "w", encoding="utf-8") as f:
    f.write("Id,Label,Weightn")
    for char in CHARACTERS:
        count = char_count.get(char, 0)
        f.write(f"{char},{char},{count}n")with open("/root/edges.csv", "w", encoding="utf-8") as f:
    f.write("Source,Target,Weightn")
    for c1, c2, w in sorted(edges, key=lambda x: -x[2]):
        f.write(f"{c1},{c2},{w}n")stats = {
    "total_words": len(words),
    "total_paragraphs": len(paragraphs),
    "character_frequency": dict(char_count.most_common()),
    "co_occurrence_edges": sorted(edges, key=lambda x: -x[2]),
    "total_edges": len(edges),
}
with open("/root/stats.json", "w", encoding="utf-8") as f:
    json.dump(stats, f, ensure_ascii=False, indent=2)print("n处理完成!")

3. 真实服务器输出

 复制代码root@ecs-11e6-0001:~# cd /root && python3 busan_segment.py
Building prefix dict from the default dictionary ...
Dumping model to file cache /tmp/jieba.cache
Loading model cost 0.432 seconds.
Prefix dict has been built successfully.
============================================================
步骤1: 加载自定义人物词典
============================================================
已加载词典: /root/busan_dict.txt
人物列表: ['石宇', '秀安', '尚华', '盛京', '珍熙', '英国', '容国', '老姐姐', '老妹妹', '乞丐', '列车长']============================================================
步骤2: 读取《釜山行》文本
============================================================
文本总长度: 1854 字符============================================================
步骤3: jieba 分词处理
============================================================
分词总数: 1175 个词============================================================
步骤4: 统计人物出现频次
============================================================
石宇         34
秀安         26
盛京         24
尚华         20
珍熙         17
英国         16
容国         15
乞丐         8
老妹妹        6
老姐姐        5
列车长        3============================================================
步骤5: 构建人物共现关系(基于段落窗口)
============================================================
段落数: 16============================================================
步骤6: 人物共现关系表
============================================================
  石宇 <-> 秀安: 共现 12 次
  石宇 <-> 盛京: 共现 11 次
  秀安 <-> 盛京: 共现 11 次
  石宇 <-> 尚华: 共现 9 次
  尚华 <-> 盛京: 共现 9 次
  珍熙 <-> 秀安: 共现 9 次
  容国 <-> 秀安: 共现 9 次
  容国 <-> 珍熙: 共现 9 次
  ...
  列车长 <-> 老妹妹: 共现 2 次共现关系总数: 55 条节点文件: /root/nodes.csv
边文件: /root/edges.csv
统计文件: /root/stats.json

4. 关键结果解读

4.1 人物出现频次

人物出现次数角色定位
石宇34男主 / 基金经理
秀安26石宇女儿 / 核心线索人物
盛京24尚华妻子 / 孕妇
尚华20肌肉男 / 保护者
珍熙17女高中生
英国16自私的运营部长 / 反派
容国15棒球少年
乞丐8善良流浪汉
老妹妹6老妹妹
老姐姐5老姐姐
列车长3列车工作人员

频次直接反映人物在文本中的叙事权重。石宇和秀安是父女双主角,因此频次最高。

4.2 最强共现关系(Top 10)

人物A人物B共现次数关系说明
石宇秀安12父女
石宇盛京11共同逃亡 / 尚华之托
秀安盛京11共同被保护
石宇尚华9并肩作战的战友
尚华盛京9夫妻
珍熙秀安9女性互助
容国秀安9青年组共同逃亡
容国珍熙9恋人
石宇英国8冲突对抗
秀安英国8被阻拦的受害者

这些强共现关系基本对应了电影里的核心关系线:父女、夫妻、情侣、战友、对抗。

5. 生成的 CSV 数据格式

nodes.csv

 复制代码Id,Label,Weight
石宇,石宇,34
秀安,秀安,26
盛京,盛京,24
...

Gephi 导入时,列 Id 作为节点唯一标识,Label 作为显示标签,Weight 控制节点大小。

edges.csv

 复制代码Source,Target,Weight
石宇,秀安,12
盛京,石宇,11
盛京,秀安,11
...

SourceTarget 分别对应人物名称,Weight 控制边的粗细。

6. 为什么段落窗口得到的是“完全图”?

细心的读者会发现在本实验中 11 个人物之间两两都有共现(55 条边 = C(11,2)),因此后续的网络密度为 1.0。这并不意外:

  • 文本较短,只有 16 个段落;
  • 故事发生在一列封闭的列车上,所有主要人物在同一灾难空间中高频交互;
  • 老姐姐、老妹妹、列车长等次要人物仍出现在多个段落中。

为了得到更稀疏、更聚焦的核心网络,第三篇会用边权重过滤(只保留 Weight >= 7 的边),那时网络会呈现清晰的社群结构。

7. 代码要点解析

7.1 jieba.load_userdict

自定义词典是识别人名的关键。如果不加载,jieba 可能会把“石宇”切成“石/宇”,从而丢失人物信息。

7.2 set() 去重

同一人物在一段落中可能出现多次,必须去重,否则共现次数会被重复计算。

 复制代码chars_in_para = list(set([w for w in para_words if w in CHARACTERS]))

7.3 无向边处理

共现关系是无向的,所以统计时加两次,保证矩阵对称:

 复制代码co_occurrence[c1][c2] += 1
co_occurrence[c2][c1] += 1

输出时只取 c1 < c2 避免重复输出。

8. 数据传递到下一节点

生成的 nodes.csv / edges.csv 通过 paramiko.SFTPnode-01 传送到 node-03 做网络分析,以及 node-04 做可视化。第三篇会展示这一过程。

9. 下一篇预告

第三篇将在 node-03NetworkX 计算度中心性、介数中心性、接近中心性、特征向量中心性、聚类系数、网络密度、平均路径长度,并导出 .gexf 供 Gephi 使用。

10. 完整代码文件获取

  • busan_text.txt:电影文本
  • busan_dict.txt:自定义人名词典
  • busan_segment.py:分词与共现提取脚本
  • nodes.csv / edges.csv:节点与边数据
  • stats.json:统计结果

相关文章

精彩推荐