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

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

标签: Python3 | jieba | 共现矩阵 | NLP | 釜山行 | 人物关系提取 实验环境: 华为云 FlexusX / Ubuntu 24.04 / Python 3.12.3 运行节点: node-01(公网 1.94.202.19)

1. 回顾与目标

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

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

2. 完整代码(busan_segment.py)

python 复制代码
#!/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] += 1

for 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,Weight\n")
    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,Weight\n")
    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. 真实服务器输出

text 复制代码
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

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

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

edges.csv

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

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

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

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

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

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

7. 代码要点解析

7.1 jieba.load_userdict

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

7.2 set() 去重

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

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

7.3 无向边处理

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

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

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

8. 数据传递到下一节点

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

9. 下一篇预告

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

10. 完整代码文件获取

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

这些文件已保存在 node-01(1.94.202.19)的 /root 目录下,读者可以直接复制到本地重复实验。

相关推荐
weixin_4166679621 分钟前
银河麒麟V10看门狗试验-1
网络·chrome·python
古城小栈1 小时前
Pydantic 从入门到实践全讲解
python
happylifetree1 小时前
Python18:核心语法-数据存储与运算-运算符-算术运算符
python
打工仔折腾 AI1 小时前
从Attention到BERT:双向预训练语言模型到底解决了什么问题
人工智能·后端·python·深度学习·语言模型·bert
迅猛龙办公室1 小时前
实现第一个python程序(HelloWorld)
开发语言·python
拉格朗日(Lagrange)2 小时前
【第2 章】WorkBuddy 从入门到高手
开发语言·python
老歌老听老掉牙3 小时前
斜抛运动问题分析:给定最大高度与墙面位置的轨迹与时间求解
python·斜抛运动
言乐63 小时前
Python自动去除水印
开发语言·python·django·virtualenv·pygame
czq_26867194873 小时前
Python打卡第31天
开发语言·python
invicinble3 小时前
python 编程语言 认识维度
开发语言·数据库·python