“harrypotter15-1”看起来像某个内部实验目录或者某个数据集的版本号。说实话我第一次拿到这个代号的时候也愣了一下下意识以为是谁在整理同人素材时把所有文档编号成了“哈利波特第15卷第1部分”。但拆开看就清楚多了harrypotter 是主题域15 是实验序号1 是迭代版本。这是一个围绕哈利波特系列作品做的文本挖掘项目核心是用数据的方式重新读一遍七本小说把故事情节、人物关系和情感变化量化成图表和数字。这个项目能回答的问题很具体斯内普这个角色到底在哪一章情绪最低落韦斯莱双胞胎的台词在全书中的分布是均匀还是集中在某几部哪些角色虽然出场不多却在事件串联中起了关键作用。项目涉及的自然语言处理、文本清洗、共现网络分析这些技术在真实业务中也很常用。如果你正在学数据分析或者内容运营想找一个有趣又不至于太复杂的练手文本这套思路非常适合直接复用。1. 项目代号里的信息量为什么叫 harrypotter15-11.1 代号命名逻辑与项目定位先解释一下“15-1”这个编号的来由。我习惯把所有实验型项目按“主题领域-实验序号-迭代版本”来命名。“15”意味着这是我在文本分析方向做的第15个实验“1”代表第一版。这个编号看起来很技术化但它有一个很实际的好处复盘的时候可以直接锁定当时用的数据版本和处理流程不用在文件夹里翻半天。这个项目本质上是一次关于“叙事结构与情感体验”的数据化尝试。我们以七本哈利波特小说为素材目标是完成三件事第一建立完整的章节级语料库第二计算每章的词频、情感倾向、角色出现密度第三用共现分析构建人物关系网络。最终输出物是一份交互式图表集和一份“全书关键剧情节点”排行榜。从这个定位可以看出项目虽然挂着一个很文化的IP名头但底层工具链基本上就是通用的自然语言处理流程。这意味着你在任何长文本分析任务里都能复用同一条路径把素材换成新闻稿、文学名著、产品评论都一样成立。1.2 为什么选择哈利波特作为分析对象选哈利波特不是单纯因为IP热度高而是因为它作为文本分析素材有四个非常突出的优点。第一语料规模合适。七本原著加上中文译本合计超过百万词。对入门级NLP项目来说这个量级既足够跑出有统计意义的结论又不至于大到需要分布式集群。第二人物关系足够复杂。核心角色、次要角色、只出现过几次的边缘角色加起来超过两百个恰好适合做网络图中的社区发现实验。第三情感起伏有明确锚点。书中存在大量读者公认的“高甜时刻”和“高虐章节”这些可以作为情感分析模型的校验基准。第四角色发言风格差异明显。同一句话由不同角色说出来语气完全不同这给“说话者识别”提供了天然的验证场景。这四个特点意味着就算你完全没读过原著只看数据结果也能从图表里看出剧情节奏的峰谷变化。比如各本书的高频词能从“魔法石、分院帽”逐步过渡到“魂器、死亡、决斗”这种词频迁移本身就是一条叙事弧线。1.3 项目想解决的核心问题与应用场景深入来看“harrypotter15-1”要解决的问题并不只是“出几张好看的图”而是三个更具体的诉求。第一个诉求是给文本情绪一个参照系。常规阅读时我们感知“这段很压抑”是主观体验。通过分章节计算情感得分能定位出全书情感最低谷的十个章节然后去对比读者社区的讨论热帖看看主观感受和数据结论是否对齐。第二个诉求是还原角色之间的隐性连接。有些角色在剧情中从来没有直接对话但他们总是出现在同一批人的口中或者总在同一场景周围活动。共现网络能把这些隐性关联挖掘出来帮创作者找到适合展开二创的“人物组合”。第三个诉求是做一个可复用的“长文本分析流水线”。从TXT文本到分词结果再到结构化表格和网络图中间涉及的所有清洗逻辑、词典配置、参数调整都可以固化下来。下次换一批语料只需要改改词典就能跑。所以我一直认为这个项目的核心不在哈利波特本身而在于它把“数据分析如何讲一个故事”这件事完整演示了一遍。它很适合三类人想入门NLP的开发者、做IP向内容策划的运营、以及想给学生找复合型案例的老师。2. 前期准备与数据获取不是爬虫是语料工程2.1 语料来源与合规注意事项很多新手一听“七本书的文本数据”就直接想到爬虫。我建议别这么做而且也没必要。我们当时用的是公开文本库中已有的整理版本也就是把已经公开的语料文件统一转成UTF-8编码的纯文本。这里要强调一个原则项目用途是学习研究不应该涉及商业传播如果要做公开分享最好只发布分析结论和统计图表不传播原始文本文件。数据源确定之后还需要做一个“文件清单”来锁定版本。因为不同来源的TXT在章节划分、英文原名处理和排版上都可能有差异。我当时的做法是建了一个data_meta.csv记录每本书的文件名、章节数量、字符数、清洗时间和备注。这个习惯后来帮了大忙——即使某次分析结果异常也能快速定位是不是某个文件格式不对。语料工程的前期处理顺序大致是这样的把所有文件转码为UTF-8去除BOM头。按书籍分成7个独立文本。用正则去掉目录、页码、出版社信息等噪声。按章节关键词切分生成“章节ID 书名 正文字符串”的结构化数据。针对中文译本额外标注“翻译用词差异”并建立别名表。2.2 文本清洗的关键细节文本清洗是整个项目最耗时、最不讨好的环节。它决定了后续所有统计结果的可靠性。我当时踩过几个比较典型的坑这里直接列出来。第一个坑是引号问题。中文译本里很多对话是“某某说‘……’”嵌套标点如果只按单引号或双引号简单切割会把同一段对话拦腰折断。解决办法是先把文本中成对的引号内容先整体提取出来再处理内层引号不要试图一次正则到位。第二个坑是英文版里的人名大小写差异。Harry、harry、HP、Potter在原文中出现的频率很高如果不做归一化词频统计会把这些实际上是同一个角色的词拆成多个词。处理方式很简单对专有名词建一个mapping表统一替换成标准形式。第三个坑是章节结尾的多余字符。某些章节末尾会附带“本章完”“Next Chapter”之类的内容如果不清理这些词的频率会虚高。我们当时专门做了一次“尾部噪声扫描”把每个章节的最后50个字抽出来人工检查一遍问题立刻暴露。这里给出一段比较常用的清洗代码示例import re import pandas as pd def clean_chapter(text): # 去掉常见噪声 text re.sub(r第[一二三四五六七八九十百千0-9]章[^\n]*, , text) text re.sub(r\bPage\s*\d\b, , text) text re.sub(r[\u3000], , text) # 统一引号为中文格式便于后续切分 text text.replace(“, 「).replace(”, 」) text text.replace(‘, 「).replace(’, 」) return text.strip()这段代码不复杂但它体现了一个原则清洗优先级是“统一格式 去噪声 信息压缩”。先把格式标准化再做删除操作最后才是维度切分。顺序反过来的话很容易在去噪声时误删有效正文。2.3 章节切分与元信息标注清洗完成之后需要把每本书切分成章节级块。我当时没有依赖现成的分章库而是根据每本书的章节标题规律写了一个动态切分函数。切分逻辑是找到“第X章”或“Chapter X”的位置作为新章节起点往前找最近的上一个切分点两个点之间的内容就是上一章正文。切分得到的每条记录需要带上元信息至少包括book_id、chapter_id、chapter_title、text。这样做有两个好处第一可以按章节做所有后续统计第二可以在可视化时让横轴同时显示“第几本第几章”而不是让读者看到一个孤零零的编号。最后我还额外生成了一列pivot_chapter把七本书的首尾章节接成一个连续的章节序列。这个序列在绘制全局情感曲线时非常关键否则折线图会在每本书之间断裂看不出全书的连续变化趋势。3. 词频统计与情感曲线让故事情绪可量化3.1 停用词过滤与高频主题词提取语料就绪之后第一个核心实验是统计每本书的高频词。表面上看就是“数单词”但实际操作时要小心停用词和专有名词的干扰。中文文本先用jieba分词然后加载哈利用语自定义词典避免“霍格沃茨”被切成“霍格”和“沃茨”英文文本用nltk的word_tokenize再过滤掉所有英文停用词。我习惯对每本书提取Top 30的关键词然后横向对比七本书的关键词变化。这个对比在数据结果里非常直观前三本中“魔法、朋友、学校、课程”之类词汇排得很靠前后几本中“死亡、魂器、记忆、战争”开始持续上升。这种词频重心迁移恰好对应了故事从校园冒险转向正邪决战的过程。如果只看词频还不够最好再加上“相对词频”指标也就是“某词在该书中的出现次数 / 全书所有词频总和”。因为七本书篇幅不同绝对次数没有可比性。这样处理后才能真正看出“秘密”一词在第七本中的相对密度比第一本高了多少倍。import jieba from collections import Counter jieba.load_userdict(hp_dict.txt) def tokenize_frequency(text, top_n30): words jieba.lcut(text) filtered [w for w in words if w.strip() and len(w) 1 and w not in stopwords] return Counter(filtered).most_common(top_n)3.2 情感分析方案选型VADER、SnowNLP还是自定义词典情感分析是整个项目里最容易引起争议的部分因为不同模型对同一段文字感受不同。我对比了三种方案最终采用的是“多模型对照 人工校验”的混合方式。第一方案是用英文原文跑VADER。VADER本身为社交媒体文本设计优点是速度快、对简单情绪句判断较准缺点是对反讽和魔法背景下的一些特殊词汇处理不够好。第二方案是用中文译本跑SnowNLP优点是符合大家阅读中文的习惯缺点是这个模型对长文本的整体倾向经常偏中性章节级别容易没有区分度。第三方案是自建一个魔法世界情感词典。我们把“摄魂怪、阿瓦达索命、死亡、背叛”标为强负向把“魁地奇、糖果、圣诞节、朋友”标为正向然后加权计算每章得分。实际跑下来的效果是自定义词典在剧情关键点上的识别能力强于通用模型但它的问题是覆盖率不足需要不断补充词汇。所以最终的流程是先用通用模型算出基础得分再用自定义词典做“情感偏移修正”两者叠加后输出一个综合分。这样既有通用模型的泛化能力又有领域词典的针对性。3.3 章节情感曲线的绘制与关键结论情感曲线是harrypotter15-1最有视觉冲击力的输出。我们把七本书的所有章节拼接成一条连续横轴纵轴是每章的情感均值。绘制时我推荐用matplotlib的折线图再叠加一个滚动平均值曲线用来平滑单章波动。项目中最有意思的发现是全书情感最低点并不集中在最后一本的某些章节而是分散在前面几本的“角色死亡章节”。这说明读者记忆里的“压抑时刻”和实际文本情绪最低潮不一定重合。这个问题特别值得做内容运营的人注意如果你是想做“最虐章节盘点”完全依靠主观记忆很可能漏掉真正的峰值。可视化代码大概长这样import matplotlib.pyplot as plt plt.figure(figsize(18, 6)) plt.plot(all_books_chapter_seq, chapter_sentiment, alpha0.4, labelchapter sentiment) plt.plot(all_books_chapter_seq, rolling_mean, color#d33, linewidth2, labelrolling mean) plt.xlabel(chapter sequence) plt.ylabel(sentiment score) plt.legend() plt.savefig(hp_sentiment_curve.png, dpi200)最终报告里我们还会把“得分最低章节Top 10”和“得分最高章节Top 10”列成对照表。这些表格非常适合后续做内容脚本因为它把情感浓度变成了可引用的数据资产。4. 人物共现网络谁和谁在同一个空间里出现4.1 从共现关系到社交网络人物关系网络是harrypotter15-1中最出彩、也最容易被误解的部分。很多人以为我们要做真实社交关系分析其实不是。我们不关心角色之间是否有私交我们只关心他们在文本中是否“共同出现”。共现的定义很简单以每一段对话场景作为统计窗口如果角色A和角色B的名字同时出现在同一个段落内就计一次共现。然后把所有章节的共现次数累加作为A和B之间的边权。窗口越小关系刻画越紧密窗口越大越偏向“同场景但未必互动”。我当时测试了“段落窗口”和“章节窗口”两种方案最终选择段落窗口因为它更能体现“实际互动”。逻辑清楚了但这个定义有它的局限性。两个角色可能出现在同一个段落里却根本没有直接互动比如几十个人同时出现在礼堂的场景。所以我在输出图表时特意加了一个提示标签说明这是“共现关系代理指标”不是真实社交强度。后面所有分析都在这个前提下进行。4.2 用NetworkX构建人物网络图网络图构建流程分三步。第一步从语料中抽取所有角色名出现的位置第二步按段落窗口聚合共现矩阵第三步用NetworkX生成图结构计算每个节点的度、介数中心度等指标。角色名识别是这一步最麻烦的事。我们没有用复杂的命名实体识别模型而是直接维护了一份“角色名单 常见别名”的对照表。名单覆盖了七本书中有名字的角色大约240个。然后配置了一个前缀匹配规则防止把“汤姆”匹配到“汤姆里德尔”之外的无关对象。import networkx as nx G nx.Graph() for pair, weight in cooccur.items(): if weight 5: G.add_edge(pair[0], pair[1], weightweight) degree dict(G.degree()) betweenness nx.betweenness_centrality(G) nx.write_graphml(G, hp_network.graphml)输出GraphML之后可以用Gephi做可视化也可以直接按NetworkX数据画图。个人推荐先用Gephi做布局因为它可以实时调整社区聚类的配色跑出来的图比matplotlib好看很多。4.3 中心度指标的解读谁是真正的剧情枢纽网络图出来以后最值得分析的是不同中心度指标的排序差异。度中心度衡量的是“谁关联的角色最多”这个指标可以简单理解成“熟人最多的人”。哈利、罗恩、赫敏毫无悬念地排在最前面因为他们作为主角会和几乎所有角色产生共现。介数中心度衡量的是“谁处在信息传递的关键路径上”它反映的是“如果没有这个角色很多其他角色之间的关联会变弱”。结果让人意外麦格教授和邓布利多的介数中心度非常高甚至在某些局部社区里超过哈利。这个结论给做内容二创的人提了个醒主角不一定占据所有的“结构洞”。有些配角虽然不常出现在聚光灯下却承担着串联群体、推进情节的角色。如果你想挖掘“隐藏关键角色”介数中心度比出场次数更有价值。社区发现算法比如Louvain可以把人物网分成若干个社区。我们的实验结果显示分出来的社区大致对应“格兰芬多核心圈”“黑魔法阵营”“魔法部官员圈”等群体。这种分群结构和读者直觉高度吻合说明用共现网络还原群体归属是可行的。5. 实操中的坑与排查记录5.1 人名别名不统一所有错乱的根源这个项目里最难处理、也最影响结果的就是人名别名。中文译本里同一个角色会有昵称、全名、头衔等多种称呼。最典型的例子是阿不思·邓布利多他可能被称为“邓布利多”“阿不思”“老校长”“白胡子老头”。这些称呼在文本里有时指同一个人有时在特定语境中指向不同信息必须谨慎合并。我的处理思路是维护一张总表包含“标准角色名、所有别名、是否默认替换、备注”。默认替换规则要非常小心比如“汤姆”如果直接替换成“汤姆·里德尔”可能会误伤某条指代不明的句子。我当时加了一个条件只有在临近段落中出现“里德尔”“伏地魔”“黑魔王”等关键词时才把“汤姆”合并到主角阵营。5.2 引号嵌套导致的说话者识别问题做角色发言分析时最难识别的是“某人引用别人说的话”以及“多角色连续对话”。如果只是统计“哪一段落在说什么”影响不大但如果想统计“这个角色在全书说了多少句话”就会因为引号嵌套而严重失准。我最终没有做过于复杂的说话者识别模型而是用了一套半自动规则先按引号成对提取所有对话再用对话前后的“某某说/笑道/低语”等线索匹配说话者。匹配不上的对话统一归入“未识别”。在结果分析时“未识别”比例只要低于15%我就认为可以接受。5.3 中文分词的专有名词与情感误判中文分词对哈利波特里大量自创词很不友好。像“霍格沃茨”“阿尼玛格斯”“摄魂怪”这类词如果不提前加进自定义词典会被切得七零八落导致词频统计偏得非常厉害。解决办法是维护一个持续增长的魔法专有名词词典每次遇到新的切分错误就补录。情感模型对魔法词汇也存在误判。SnowNLP对“诅咒”默认打负面分没有问题但“魔法”这个词在很多语境下是中性甚至偏正面的模型却可能给出负向权重。这个问题没有完美解法只能通过“在展示结果时同时标注模型版本和词典版本”来增加透明度。下面整理一个实用的排查速查表问题现象可能原因解决方法我的备注某角色词频异常高别名未合并完善角色别名映射表先跑一遍“候选别名聚合”再统计某章情感分成极端负值对话内容被错误切分检查引号清洗规则优先检查连续对话段落网络图孤立点过多共现阈值设置过高调低最小边权阈值40个孤立点可接受分词干燥无信息量停用词表过宽缩短停用词列表保留“魔法”等情景词章节数量对不上标题噪声未被清除人工校对切分点每本输出章节预览文本5.4 可视化排版时的数据噪音处理最后一个坑是画图时数据噪音很扎眼。原始章节情感得分会在某些极端章节突然跳成一个异常尖峰如果直接展示全量折线读者很容易被异常值带偏。建议做两步处理第一步对单章得分做一次轻度平滑第二步在图上明确标注“平滑窗口大小”。之前看到很多人画情感曲线不带窗口参数后续读者根本不知道曲线是原始值还是平均值这就失去了可复现性。6. 项目还能怎么玩从实验到产品6.1 做成自动化的“角色情绪面板”harrypotter15-1的第一版只输出了静态图表。受到复盘会上的反馈启发我又把脚本整理成了一个定时任务每次更新语料后自动生成各角色的情绪波动热力图并输出“情绪变化最大的章节”摘要。这个思路放到业务里就是对客户舆情做日维度情感监测的简化版本。技术实现上不复杂就是把前面写好的所有处理逻辑封装成一个pipeline函数输入原始语料目录输出汇总表和一个HTML报告。用Jinja2模板把图表和表格拼进同一个页面跑完就能直接打开看结果。6.2 给内容创作提供数据佐证用数据分析结果反过来指导内容策划是这个项目比较意外的价值。之前某个兴趣小组做“原著最感人台词盘点”时直接从情感曲线里选出了几章得分异常的片段再人工摘出台词效率和说服力瞬间提升。你可以把这个思路迁移到任何一个影视IP或文学IP的二次创作里。而且重点在于当你用数据去支撑一个内容选题时不需要冒“主观感受没有依据”的风险。比如做《最强隐没幕后角色》这类视频时直接引用介数中心度榜单比单纯说“我觉得这个角色很关键”更有理有据。6.3 沉淀为可复用的NLP教学案例这个项目最好的归宿是变成一个学习案例模板。把代码、数据说明、结果报告、踩坑汇总整理好后完全可以作为自然语言处理课程的实战作业。学习者不需要重新开发一套分析框架只需要把“哈利波特”替换成自己感兴趣的长篇文本换一份词典和角色表就能跑通整个流程。从第一版到现在的迭代我最大的体会是文本分析项目的成功不是取决于模型多高级而是取决于语料清理多彻底。harrypotter15-1里的所有结论几乎没有一项是“算法聪明”算出来的全是在“数据干净、规则清晰、结果可解释”的前提下得到的。这可能是这个项目留给我的最重要经验。最后分享一个小技巧在所有统计结果交付前花30分钟人工抽查几个极端值。比如找出情感分最高和最低的章节实际读一小段原文看看模型判断是否符合上下文。只要这个抽查能通过报告才算是真的能拿得出手。很多项目最后被质疑不是因为算法不对而是因为连自己都没检查过那些“看起来很反常”的数字。
阅读完成 · 觉得有帮助?