简介:本资源是一份面向高校NLP课程学习者与初学者的自然语言处理期末大作业实践方案,聚焦视频弹幕这一典型短文本场景,完成端到端的情感极性分析任务。资源包含完整可运行代码、详细文档说明及配套数据集,覆盖数据爬取、清洗、停用词处理、情感词典匹配与结果可视化全流程,特别适合作为课程设计或期末大作业提交材料。压缩包共15个文件(3.82MB),含6个核心Python脚本(如数据爬取.py、情感分析.py、词云图生成.py)、3个文本类资源(含正负样本语料与停用词表)、2个Excel格式原始与结果数据表、1个Markdown说明文档、1张界面示意图及1个二进制模型文件,结构清晰、模块解耦、注释详尽。已有514人学习下载,代码逻辑直白、部署简易,无需复杂环境配置即可本地运行,兼顾教学规范性与工程实用性。
1. 项目概述与整体设计思路
1.1 为什么选视频弹幕来做情感极性分析
NLP大作业选什么题目,其实是件挺纠结的事。选经典的情感分析吧,用现成的IMDB影评数据集一跑,准确率刷到90%以上很容易,但做完之后自己心里清楚,除了调了几个库,什么都没真正学会。选太难的方向吧,又怕时间不够,代码写不出来,答辩的时候被老师问住。我们组当时商量了很久,最后定了一个看起来“小众”但实际非常有意思的方向:视频弹幕的情感极性分析。
弹幕这个东西,和传统的影评、商品评论差别很大。它本质上是一种短文本,一条弹幕往往只有几个字到十几个字,像“哈哈哈哈”、“泪目”、“就这?”、“爷青回”,信息密度极高,但语法极不完整。传统的情感分析模型,在长文本上表现不错,到了这种碎片化的短文本上,经常翻车。而且弹幕里充斥着网络新词、谐音梗、表情符号、英文缩写,比如“yyds”、“绝绝子”、“破防了”、“awsl”,这些词在常规的词典里根本查不到。正因为这样,把它作为大作业题目,既有难度又有展示空间,做出来之后可以讲的东西非常多。
从实际效果来看,弹幕情感分析也确实有应用场景。视频平台可以通过它实时监控用户对内容的情感反馈,判断哪一段内容是观众喜欢的,哪一段让观众反感,甚至可以辅助内容推荐和弹幕氛围管理。对主播和UP主来说,弹幕情感分布也是调整内容方向的重要参考。所以这个题目不是拍脑袋选的,背后有真实的业务逻辑。
1.2 情感极性分析的主流技术路线与选型
定了题目之后,接下来就是选技术路线。这里其实就两条路:一条是传统的基于情感词典的方法,另一条是基于机器学习或深度学习的方法。
基于情感词典的方法,核心思想是构建一个带情感极性分值的情感词库,比如“喜欢”是+2分,“讨厌”是-2分,然后对一条文本进行分词,匹配情感词,累加得分,再结合否定词、程度副词做调整。这种方法的优点是简单、可解释性强,代码量不大,适合大作业展示,而且不依赖大规模标注数据。缺点是词典覆盖率有限,对网络新词比较无力,需要不断扩充词典。
基于机器学习的方法,常见做法是把文本转成TF-IDF向量或者Word2Vec向量,然后用SVM、朴素贝叶斯、随机森林等分类器训练。再进阶一点,直接用BERT这类预训练模型做文本分类。BERT的效果确实好,但问题也很现实:需要GPU,训练时间长,而且大作业如果直接调HuggingFace的库跑一个BERT,代码层面能展示的东西很少,答辩时容易被追问“你原理讲不清楚”,反而扣分。
我们最终采用的是“词典法为主、机器学习为辅”的混合路线。主体功能用情感词典法实现,保证代码自己写得出来、逻辑讲得清楚;同时在实验部分用TF-IDF+朴素贝叶斯做一个对照实验,对比两种方法的准确率差异。这样一来,大作业的技术含量和完整性都拉满了,工作量也合理,不会把自己陷在模型调参的泥潭里。
2. 数据获取与预处理
2.1 弹幕数据从哪来:采集方案对比
做弹幕情感分析,第一步得先有数据。市面上的公开弹幕数据集很少,所以必须自己动手采集。当时我们调研了几个主流视频平台的弹幕接口,发现大部分平台都有Web端接口可以通过视频CID(视频编号)拉取弹幕列表,一般是XML或JSON格式,里面包含弹幕内容、发送时间、弹幕ID等字段。
采集方式可以自己用Python的requests库写爬虫,也可以用现成的开源工具。考虑到大作业的场景,我不太建议直接用别人打包好的工具一把梭,因为老师问起来“数据怎么来的”如果答不上来,印象分会打折扣。自己写一个简单的爬虫其实不难,核心逻辑就是两步:第一步根据视频页面地址解析出CID,第二步请求弹幕接口解析XML。
给大家一个参考实现框架:
import requests import re import xml.etree.ElementTree as ET def get_video_cid(url): # 请求视频页面,提取cid参数 resp = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}) cid = re.search(r'"cid":(\d+)', resp.text) if cid: return cid.group(1) return None def fetch_danmaku(cid): # 根据cid请求弹幕接口 api_url = f"https://comment.bilibili.com/{cid}.xml" resp = requests.get(api_url, headers={"User-Agent": "Mozilla/5.0"}) resp.encoding = "utf-8" root = ET.fromstring(resp.text) contents = [] for d in root.iter("d"): contents.append(d.text) return contents if __name__ == "__main__": url = "你的视频链接" cid = get_video_cid(url) if cid: danmaku_list = fetch_danmaku(cid) print(f"共获取 {len(danmaku_list)} 条弹幕") # 保存到本地 with open("danmaku.txt", "w", encoding="utf-8") as f: f.write("\n".join(danmaku_list))实际操作下来,要注意两点:第一,弹幕接口的返回量是有限制的,一个视频最多只能拉取最近的一部分弹幕,不同平台规则不一样,我们当时选择的是几个热门视频分别拉取,凑了大概2万多条;第二,爬虫请求频率不要太高,加一个time.sleep()休眠,避免给平台服务器造成压力,这既是礼貌,也是降低被封IP的风险。
2.2 数据清洗:把脏数据挡在门外
采集完原始弹幕之后,你会发现数据质量非常“感人”。最常见的几类脏数据包括:纯表情符号串(比如“😂😂😂”这种没有文字内容的)、无意义的乱码字符、广告信息、重复弹幕等等。如果不做清洗,后面分词和情感判定的准确率都会受到干扰。
清洗的步骤我们分了四步走:
- 去空白:去掉字符串首尾空格、全角空格、换行符。
- 去纯符号:如果一条弹幕去掉非中文字符和英文字母后长度为0,直接丢弃。
- 去重复:把完全相同的弹幕合并统计次数,既节省空间,也方便后面按权重计算。
- 统一格式:全角英文字母转半角,繁体转简体(用OpenCC库),大写转小写。
清洗后的数据量会缩水不少,但这都是正常现象。我记得当时2万多条原始弹幕,清洗完剩下1.6万条左右,去掉的约20%基本都是纯表情和无意义符号。这个比例在弹幕场景中非常典型,不用心疼。
2.3 分词和停用词过滤的细节
弹幕是中文短文本,处理之前必须分词。我们用的是jieba分词,这几乎是中文NLP工作量最低、效果最均衡的工具。不过需要注意,默认的jieba词典对弹幕中的网络新词支持并不好,“绝绝子”这种词会被切成“绝绝”和“子”,所以必须加载自定义词典。
自定义词典的格式很简单,每个词一行,可以包含词频和词性:
绝绝子 100 n 破防了 100 v yyds 50 x 爷青回 50 x把用户自定义词典加到jieba里,分词准确率会有明显提升。这里有个小技巧:如果你不确定一个词该不该加进自定义词典,可以先跑一遍分词结果,把明显被切碎的网络热词记下来,统一加进去,迭代两三轮基本就稳定了。
停用词表也是必须的。弹幕里的“的”、“了”、“吗”、“啊”这类语气词和助词,对情感判定没有贡献,反而会干扰情感词的匹配。哈工大停用词表、百度停用词表都可以直接用,但要注意在这些通用停用词表的基础上,把弹幕特有的高频无用词也加进去,比如“哈哈哈哈”、“666”这类虽然是语气词但有一定的情感色彩,是否清洗取决于你的策略。我们当时的处理是:保留“哈哈哈哈”这类能反映积极情绪的表情性词汇,因为它们本身带有明显的情感倾向,先单独挑出来做规则匹配,再把剩下的部分去停用词。
3. 情感判定核心模块实现
3.1 情感词典的构建:从基础词库到弹幕特色词库
情感词典是整个项目的灵魂,词典的质量直接决定了情感分析的效果。通常我们不会从一个空词典开始,而是使用学术界公开的基础情感词典,再结合弹幕语料进行扩充。
我们使用的两个基础词典:一个是知网HowNet情感词典,包含正面情感词、负面情感词、程度级别词等,覆盖面很广;另一个是大连理工大学的中文情感词汇本体库,这个更细,把情感分成了乐、好、怒、哀、惧、恶、惊七大类,并且每个词都标注了情感极性(0中性、1正、2负、3兼有)和情感强度(1到9)。把这两个词典合并去重,就得到了一个规模不小的基础词库。
但光有基础词库远远不够。前面提过,弹幕里的“yyds”、“awsl”、“破防了”、“emo了”这些词,在正常词典里是查不到的。我们在清洗完弹幕语料之后,做了一个高频词统计,把出现次数Top 300的词挑出来人工过了一遍,把明显带有情感倾向的词挑出来,人工标注情感极性,补充到词典里。这一步工作量不算大,但对效果的提升非常明显。
这里分享一个扩充词典的经验:情感词不全是一眼看穿的,“绝绝子”是正面,“夺笋”是调侃性的负面,“摆烂”是消极的负面,“海王”偏贬义,“宝藏”偏褒义。如果你拿不准某个词的情感倾向,可以把这个词的所有语境弹幕拉出来看一遍,根据多数语境判断,不要凭印象拍脑袋。
3.2 情感打分策略:不止是加加减减
情感极性分析的核心算法逻辑并不复杂,基本流程是:对分词后的词序列,逐个匹配情感词典中的情感词,累加情感得分,同时考虑程度副词和否定词的影响。
但这里有一个非常经典的坑:简单加加减减会翻车。举个简单的例子,“这部电影一点也不好看”,分词后是“这/部/电影/一点/也/不/好看”,其中“好看”是正面词+1,“不”是否定词。如果规则是“否定词使情感词得分反转”,那结果就是-1,判定为负面,这刚好是对的。那再来一句“我不要太喜欢这部电影”,“不要”会反转“喜欢”,得到-1,但这句话的真实含义其实是“我非常喜欢”,情感是正面的。这就是中文否定词的复杂性,口语化表达中“不要太好”往往是反讽或加强语气。
我们当时的处理策略是:建立否定词和程度副词的映射表,设置一个滑动窗口。具体规则如下:
- 在情感词前一个窗口内(我们取前面2个词),如果出现“不”、“没”、“无”、“莫”、“非”等否定词,情感得分乘以-1。
- 如果出现“很”、“非常”、“极其”、“超级”、“太”等程度副词,根据程度级别乘以对应的权重系数,比如“非常”是1.8倍,“有点”是0.5倍。
- 如果否定词和程度副词同时出现,且程度副词在否定词前面,如“不太喜欢”,做了反转衰减处理;如果程度副词在否定词后面,如“不很喜欢”,则保留反转。
这个方法虽然不能解决所有问题,但已经能覆盖弹幕中大部分常见的否定和强调表达。
另外,弹幕里经常出现的连续感叹号“!!!!”和“哈哈哈哈哈”可以视为情感加强信号。我们的规则是:如果一条弹幕匹配到正面情感词且以连续3个以上感叹号结尾,得分额外+1;如果是负面情感词且有大量感叹号,则额外-1。这条规则虽然有点“暴力”,但在弹幕场景中效果很不错。
最后一条弹幕的总得分就是所有情感词得分累加的结果。我们设置了一个阈值:总分大于0判正面,小于0判负面,等于0判中性。考虑到中文表达的含蓄性,当时还加了一个阈值调节参数,只有绝对值大于0.5才判正面或负面,否则划为中性。这个参数可以通过实验调优,在作业报告中也能体现你对系统的思考。
3.3 规则引擎代码实现要点
情感打分模块的代码结构,我在这里贴一个核心逻辑的简化版本,里面带注释,大家可以直接参考:
import jieba import jieba.posseg as pseg class EmotionAnalyzer: def __init__(self, pos_dict, neg_dict, degree_dict, neg_words): self.pos_dict = pos_dict self.neg_dict = neg_dict self.degree_dict = degree_dict self.neg_words = neg_words def analyze(self, text): # 分词并过滤停用词(这里省略停用词处理) words = [w for w in jieba.cut(text) if w.strip()] score = 0.0 window_size = 2 for i, word in enumerate(words): weight = 0 # 匹配正面词 if word in self.pos_dict: weight = self.pos_dict[word] # 匹配负面词 elif word in self.neg_dict: weight = -self.neg_dict[word] if weight != 0: # 往前看窗口内的否定词和程度副词 neg_flag = False degree_factor = 1.0 for j in range(max(0, i - window_size), i): if words[j] in self.neg_words: neg_flag = not neg_flag if words[j] in self.degree_dict: degree_factor = self.degree_dict[words[j]] if neg_flag: weight = -weight score += weight * degree_factor return score这个实现虽然简单,但已经是一个可以跑通全流程的框架。在此基础上,我还加了感叹号加强规则、表情符号规则和网络词规则。
3.4 另一种路线:TF-IDF加朴素贝叶斯做对照实验
为了在作业报告里展示对比分析,我们还实现了一个基于TF-IDF加朴素贝叶斯的对照实验。思路是:把情感词典法对每一条弹幕的判定结果作为伪标签,虽然不完美,但可以用来训练分类器;或者更严谨一点,人工标注一部分数据(大概500到1000条),用这些标注数据训练一个朴素贝叶斯模型。
从结果来看,在标注数据量较少的情况下,朴素贝叶斯的效果会略逊于词典法,因为训练数据不足,而且弹幕文本太短会导致TF-IDF特征非常稀疏。但如果把标注数据扩展到2000条以上,机器学习法的效果会反过来略优于词典法,尤其是在处理“阴阳怪气”这种反讽表达时,机器学习能学到一些词典法学不到的上下文模式。
在报告中,我们画了一张性能对比表,分别计算了两种方法在测试集上的准确率、精确率、召回率和F1值。这种对比实验的写法是老师比较喜欢看到的,因为它体现了你不只是跑通了一个系统,而是对不同的技术方案有比较和思考。
4. 项目代码结构与管理
4.1 代码目录怎么组织
大作业代码的目录结构,虽然没有硬性要求,但一个清晰的组织方式能让你在写报告和答辩时省很多力气。我们的结构如下:
danmaku-sentiment/ ├── data/ │ ├── raw/ # 原始弹幕数据 │ ├── cleaned/ # 清洗后的数据 │ └── dicts/ # 情感词典、停用词表、自定义词典 ├── src/ │ ├── crawler.py # 弹幕爬虫 │ ├── preprocess.py # 数据清洗与分词 │ ├── analyzer.py # 情感分析核心逻辑 │ ├── train_nb.py # 朴素贝叶斯对照实验 │ └── visualize.py # 结果可视化 ├── output/ │ ├── figures/ # 图表 │ ├── result.csv # 情感分析结果表 │ └── report.md # 实验报告 ├── requirements.txt └── README.md这个结构的好处是职责分明,数据、代码、结果分开放,不会像一个文件夹里扔了几十个文件那样混乱。requirements.txt里把jieba、scikit-learn、pandas、matplotlib、opencc这几个核心依赖列清楚,可以让别人在复现的时候少踩很多环境坑。
4.2 中间结果落盘的意义
写大作业代码的时候,最容易犯的一个错误就是追求“一条流水线走到底”,从原始数据直接出结果。这种做法在调试的时候非常痛苦,你根本不知道哪一步出了问题。我们当时的实践是:每一步处理都同步保存中间结果。爬虫爬完存一份原始数据,清洗完存一份清洗数据,分词完存一份带词性的分词结果,最后情感分析的每一条结果都输出成CSV。
CSV里至少包含这些字段:弹幕原始文本、清洗后文本、分词结果、情感得分、情感极性。这样一旦发现某条弹幕的判错了,你可以直接查CSV,定位到是分词的问题还是词典缺失的问题。这也是我在做完这个大作业之后最大的体会之一:写分析类代码,中间结果要尽量落到磁盘上,别全都放在内存里,不然排查问题的成本会高到让你怀疑人生。
5. 常见坑位与排查指南
5.1 编码问题:全局统一UTF-8
弹幕数据是中文,编码问题会出现在各个角落。爬虫请求回来的响应如果直接用默认编码解析,很容易出现乱码。我们的做法是:所有读取和保存文件都显式指定encoding="utf-8",爬虫请求时设置响应的编码,避免Windows平台默认GBK编码导致的头疼问题。
另外还有个细节:在Windows上跑脚本时,控制台输出中文可能会遇到UnicodeEncodeError,这不是代码逻辑错了,而是控制台编码问题,解决方案是设置环境变量PYTHONIOENCODING=utf-8,或者在代码最前面加一行sys.stdout.reconfigure(encoding="utf-8")。
5.2 否定词的语义反转并不总是成立
前面提到过否定词处理,这里再展开说。在中文里,否定词的作用非常复杂,有双重否定表肯定的情况(例如“不会不喜欢”其实是“喜欢”),有反讽用法(“你真是太好了”在特定语境下是负面的),还有口语化的强调用法(“好得不要不要的”是正面)。这些在简单的规则引擎里很难全部处理。
我们的应对策略是:在作业报告里主动承认规则引擎的局限,并给出具体的失败案例。这比装作自己的系统完美无缺要好得多,老师会认为你有批判性思维。同时,我们针对“双重否定”这类常见情况,在代码里做了一层特殊处理:如果同一个情感词前出现偶数个否定词,判定为肯定,不反转极性。
5.3 网络新词和表情符号怎么处理
网络新词更新速度快得离谱,可能你今天刚把“栓Q”加进词典,下周就有新的热词出现了。对于大作业来说,不可能追求百分之百的覆盖率,更合理的做法是设计一个可维护的词典扩展机制。
我们的做法是把自定义网络词单独放在一个文本文件里,标注好情感值和词性,让analyzer.py启动时动态加载。这样以后看到新词,只需要在文本文件里加一行,不需要改任何代码。表情符号的处理类似,我们把常见的“笑哭”、“流泪”、“生气”等emoji单独映射成对应的情感词,比如“😂”映射为“笑哭”(正面或中性),“😡”映射为“愤怒”(负面)。在弹幕里,emoji往往比文字更直接地表达情感,这个信息不能浪费。
5.4 标注工作量与训练数据的平衡
如果你做机器学习对照实验,就会面对一个现实问题:人工标注弹幕数据真的很累。一条弹幕短,语义又复杂,标注速度大概一小时几百条,标2000条需要好几个小时,而且标注的一致性不好保证。我们的做法是两个人各标一半,然后交叉验证,遇到分歧大的弹幕讨论后统一标准。这个过程本身也可以写进报告里,作为“数据标注一致性”讨论的素材。如果实在不想人工标注,可以用词典法的结果作为“软标签”,但必须说明这存在噪声,模型评估指标的可靠性会打折扣。
6. 实验评估与报告撰写建议
6.1 实验设计、评估指标与结果呈现
实验部分,我们把数据划分成训练集和测试集,比例是8:2。词典法没有训练过程,直接在测试集上评估。朴素贝叶斯则在训练集上训练,在测试集上评估。评估指标用了准确率、精确率、召回率和F1四个。由于我们的人工标注量有限(1500条),整体样本量不算大,所以精确率和召回率的波动会比较明显,在报告里要说明这一点,不要强行得出结论。
从我们的结果来看,词典法在测试集上的准确率大约在78%左右,TF-IDF加朴素贝叶斯大约在74%左右。差距不算太大,这个结果其实比较符合预期:词典法在短文本情感分析场景下,如果没有大量标注数据支撑,并不会明显输给传统机器学习方法。在报告里,多展示几个典型的情感判定案例(正确和错误的都要有),比单纯贴数字有说服力得多。
6.2 报告文档的结构建议
作业报告的写法,建议遵循“背景、方法、实验、分析、总结”的主线。背景部分要有佐证,引用几篇情感分析和弹幕研究的论文,让老师知道你做过文献调研。方法部分要写清楚技术选型的理由,比如为什么选词典法作为主体,为什么用jieba分词,为什么选择这两个基础情感词典。实验部分除了数字,还要有可视化图表。总结部分不要空泛地写“本项目实现了什么”,而是写“在过程中遇到了什么困难,怎么解决的,还有哪些不足之处”。
6.3 答辩时高频问题的应对
答辩环节,老师通常不会逐行看代码,但会问几个关键问题。我们当时被问到的主要是:
- 情感词典和权威词典有什么区别?怎么保证词典质量?——就从构建过程讲,说明基础词典来源权威,弹幕词库通过人工审核补充,并给出具体的样例。
- 和BERT相比,你这个方法有什么优势和不足?——优势是计算资源要求低、可解释性强、部署快;不足是语义理解深度不够,反讽和复杂句法处理不了。
- 你的系统能直接应用到生产环境吗?——诚实回答:不能直接商用,因为通用性不足,换一个视频类型可能效果下降,但可以作为一个快速原型,结合业务数据进一步优化。
这些问题都不难,关键是你在做项目的时候真的动过脑子,而不是对着别人的项目改个名字交上去。
7. 从大作业到真实项目的几点体会
做完这个NLP大作业,我最大的体会是:大作业的价值不在于最后得多少分,而在于你完整地走了一遍“数据采集—数据清洗—算法设计—代码实现—实验评估—报告写作”的流程。这个过程比任何一门课程都更接近于真实项目中一个NLP功能的落地路径。
最后再分享一个小技巧:做大作业的时候,建议把每一个关键节点的决策理由记在一个单独的笔记里,比如“为什么选这个词典”、“为什么窗口大小取2”、“为什么阈值设0.5”。这些理由放在报告里,既是答辩的弹药库,也是你在这个项目里的真实收获。等到项目结束回头看,你会发现最值钱的不是那份源码,而是这些决策背后的思考过程。
本文还有配套的精品资源,点击获取