简介:基于Python的豆瓣电影评论情感分析与关键词提取WordCloud设计源码,面向希望从影评中挖掘情感倾向与话题焦点的开发者、研究人员。项目实现文本预处理、去停用词、情感分类、TF-IDF关键词提取及词云可视化流程,配合多部影片实例,便于理解NLP与分析结果的对应关系。压缩包共39个文件、约44.31MB,含19个PNG图表、6个xlsx分析报告、4个xml配置、4个txt辅助文本、2个otf字体和main.py源码等,图表涵盖唐探1900、哪吒之魔童闹海、射雕英雄传:侠之大者等影片的词云、情感分布与趋势图。还附带停用词表和过滤文本,方便替换语料直接运行。已有183人学习,适合需要参考完整工程结构、复用词云模板或快速上手评论情感分析的读者。
1. 这一份豆瓣电影评论情感分析与词云源码,值不值得先跑一遍
春节档六部电影(唐探1900、哪吒之魔童闹海、熊出没·重启未来、蛟龙行动、射雕英雄传:侠之大者、封神第二部:战火西岐)的豆瓣评论,如果还靠人工一条条翻,那你会发现翻了半个小时还没看完第一屏。这里有个基于Python的豆瓣电影评论情感分析与关键词提取wordcloud设计源码,把爬下来或导出的评论喂给main.py,就能直接出情感分布图、评论趋势图、词云图以及一份xlsx分析报告。适合正在做舆情分析作业、准备写影评数据报告、或者想用NLP给电影宣发做参考的人。它算不上大工程,但作为一条完整可复现的NLP流水线,比东拼西凑的脚本要省事得多。这篇笔记我把源码包里的文件、参数、坑都拆开讲。
2. 源码包结构:38个文件里藏着一条完整的NLP流水线
拿到压缩包不要急着运行,先把文件分三类看明白。第一类是脚本与配置,包括main.py、stopwords.txt、filterText.txt、userdict.txt;第二类是字体与输出模板,包括SourceHanSansCN.otf、NotoSansCJKMedium.otf、font_test.png;第三类是六部电影各自的输出,每个电影都有分析报告xlsx、趋势图png、情感分布png、词云png。还有.gitignore、.idea等IDE与Git相关文件,这些不影响运行,但说明作者用PyCharm开发并且纳入了版本管理。
2.1 main.py不是单脚本,而是一条流水线
main.py是项目主体,它做的事情不是简单的“读取-输出”,而是依次执行:读取评论数据、文本清洗、分词、去停用词、过滤无用文本、情感打分、关键词提取、生成图表和词云、写出xlsx报告。常见做法是把每一步写成函数,主流程用ifname== 'main':驱动。如果你在PyCharm里打开项目直接运行main.py,要留意开头的数据路径变量,因为代码默认处理哪部电影、数据文件放在哪个目录,都在这个变量里控制。
源码包里六部电影的输出文件都齐了,说明作者至少完整跑通过一遍流程,不是半成品。这对我们来说是个好消息:环境配置对了就能复现,不需要再猜数据格式。我一般会先备份原始main.py,然后在副本上改参数,这样随时可以对比源码默认行为和自己的改动。
2.2 支撑文件的作用:filterText、stopwords、userdict、两个otf字体
stopwords.txt是停用词表,用于过滤“的、了、就是、其实”这类无意义词;filterText.txt可能存放需要过滤的正则或特定文本碎片,比如无意义的字符组合;userdict.txt是用户词典,用来保证“哪吒”“魔童闹海”“唐探”这样的词不会被错误切分。两个otf字体文件是给wordcloud用的,中文字体如果没有指定,生成的词云全是方块。SourceHanSansCN.otf(思源黑体)和NotoSansCJKMedium.otf都可以用,后者字符集更完整。
这三个配置文件改起来没什么门槛,但影响极大。我接手这类项目的第一件事就是把stopwords.txt打开扫一遍,看它有没有过滤掉“特效”“演技”这种对电影评论有区分度的词。如果停用词表太激进,情感分析的关键词会变得很干瘪,词云也只剩“电影”“剧情”这类通用词。
2.3 输出文件命名规则:从报告xlsx到趋势图、情感分布、词云
看文件名就能猜到代码的产物命名规则:{电影名}_分析报告.xlsx、{电影名}_趋势图.png、{电影名}_情感分布.png、{电影名}_词云.png。xlsx里一般包括评论明细、情感得分、关键词列表、统计汇总。趋势图是评论数量或情感均值随时间的变化,情感分布图是正面/负面/中性占比。这个命名规则提示我:作者在代码里应该用了一个电影名列表循环生成,而不是每个电影写一遍。
| 文件类型 | 数量 | 用途 |
|---|---|---|
| main.py | 1 | 主流程脚本 |
| stopwords.txt | 1 | 停用词表 |
| filterText.txt | 1 | 过滤规则/文本 |
| userdict.txt | 1 | 用户词典 |
| *.otf | 2 | 词云中文显示字体 |
| *.png | 19 | 词云/分布/趋势图 |
| *.xlsx | 6 | 分析报告 |
| *.xml / .gitignore | 多个 | IDE/版本控制 |
如果你只想跑一部电影,把电影列表改成单个元素即可,输出也不会被其他电影覆盖。注意xlsx文件名里的电影名带全角冒号,在Windows下虽然合法,但某些旧版Excel可能打不开,后面避坑章节会讲。
3. 情感分析这块:先跑通默认模型,再决定要不要换算法
很多人在拿到源码后直接跑,结果出来的情感分布跟自己体感差很多,于是怀疑脚本有bug。其实情感分析本身就是个概率问题,不是非黑即白。这套源码的情感分析大概率走的是“词典打分 + 规则修正”路线,而不是训练一个深度模型。理由很现实:模型训练需要大量标注数据,而词典方法一两百行代码就能出结果,对电影评论这种短文本也够用。
3.1 情感分析的基本路线:词典打分还是模型分类
常见做法有两种。第一种是情感词典法:把评论分词后,逐个词去匹配情感词典,正面词加分、负面词减分,最后总分大于阈值判为正向,小于阈值判为负向,其余中性。第二种是用scikit-learn训练分类模型,先TF-IDF向量化,再用朴素贝叶斯或逻辑回归。这套源码的主脚本如果是走词典路线,你会发现它自带情感词典表,或者通过filterText来修饰分数。
我在实际项目里,电影评论领域用词典法反而比通用模型更稳定。电影评论有大量口语化表达、反讽和烂梗,通用模型容易把“特效炸了”判成负面,词典法配合人工调规则反而更可控。但缺点也很明显:词典覆盖不全,遇到网络新词容易漏判。所以第一步先跑通默认版本,拿到一份结果,再用抽样人工标注校验,比一上来就重复造轮子强。
3.2 数据预处理:去停用词、清洗、用户词典
情感分析前必须清洗原始评论。常见的清洗步骤包括:去HTML标签、去URL、去@用户、去多余换行、去emoji或转换为描述、统一繁体简体。过滤规则写在filterText.txt里,你可以把它理解为一条正则列表,凡是匹配到的文本碎片直接剔除。stopwords.txt负责去掉无情感色彩的词。而userdict.txt里的词,会在jieba分词时被强制保留为一个整体,避免“魔童”“闹海”被切成“魔”“童”“闹”“海”。
import jieba import re def clean_text(text): # 去掉URL、@、多余空白,按需扩展 text = re.sub(r'http\S+', '', text) text = re.sub(r'@\w+', '', text) text = re.sub(r'\s+', ' ', text) return text.strip() # 自定义词典:保证电影名、角色名不被错切 jieba.load_userdict('userdict.txt')这里load_userdict会在分词时把用户词视为整体。注意userdict.txt每行一个词,可以用空格或Tab分隔词频、词性,比如“哪吒 10 n”。freq给得越大,新词越容易被识别,但也不要无限大,否则会影响正常切分。如果你发现某些评论里“哪吒”还是被切成“哪”“吒”,就把词频调大再重新运行。
3.3 跑一次情感分析并输出分布图和xlsx报告
主流程里大概率有类似下面的逻辑:读评论 -> 预处理 -> 逐条打分 -> 统计标签 -> 写xlsx -> 画分布图。如果你只想看某个电影的结果,可以直接修改main.py里的电影列表:
movies = ['哪吒之魔童闹海'] # 完整流程函数,假设在同一个模块里 for movie in movies: comments = load_comments(movie) scores = [sentiment_score(c) for c in comments] labels = classify(scores, pos_threshold=0.1, neg_threshold=-0.1) save_report(movie, comments, scores, labels) plot_sentiment_dist(movie, labels) plot_trend(movie, scores)参数说明:pos_threshold和neg_threshold决定什么分数算正面、什么算负面。默认0.1和-0.1意味着接近中性的分数会被归为中性。这个阈值你可以根据自己的数据分布调整:如果正面评论明显过多,调高pos_threshold;如果负面刷屏,把neg_threshold往下压。load_comments返回的是评论列表,每一条最好保留发布时间和评分字段,这样趋势图才画得出来。
3.4 调整情感阈值或标签,让结果更贴合你的判断
如果源码输出里没有阈值参数,那分数计算可能用的就是一个固定分段:score大于0为正,score小于0为负,其余中性。这时候你会发现很多“还行”“一般”都被算成中性,导致中性占大头。我给这类脚本加过一个小改动:把得分绝对值小于0.05的评论单独抽出来打标为疑似中性,然后人工抽查几百条,用结果反向校准阈值。
情感分析这玩意儿多少带点玄学,不同电影类型阈值都不一样。喜剧片容易高分组团,文艺片评论两极分化,一个阈值套所有电影必然不准。所以我总是把阈值做成配置文件里的变量,而不是硬编码在函数里,这样每个电影可以单独调。
4. 关键词提取与词云:TF-IDF / TextRank 和字体这个大坑
词云图是这套源码最有视觉效果的部分,但也是最容易出问题的地方。关键词提取直接决定词云内容,字体决定中文能不能正常显示。这两步我拆开讲,每一步都可能让成品变成一张废图。
4.1 关键词提取:词频、TF-IDF、TextRank怎么选
三种方式各有适用场景。词频统计简单粗暴,但每部电影都出现的“电影”“剧情”“演员”会被排到前面,没有区分度。TF-IDF能压低普遍词的权重,突出这部电影相对独特的词。TextRank则通过词共现网络给关键词排权重,适合长文本,短评论上效果反而不如TF-IDF。
这套源码如果用的是jieba内置的jieba.analyse.extract_tags,那默认就是TF-IDF算法,你可以传topK参数控制返回多少个词。
import jieba.analyse top_words = jieba.analyse.extract_tags( text, # 已清洗的全文 topK=50, # 提取前50个关键词 withWeight=True, # 返回权重,词云绘制时需要 allowPOS=('n', 'v', 'a') # 只保留名词、动词、形容词 )allowPOS这个参数很关键,默认会全词性输出,结果经常混进一堆副词和连词。我一般只用名词加形容词,动词参与度不高,词云里的词才“像人话”。如果你想要更严格的对比,可以把六部电影的文本合并后单独提取一批全局停用词,再对每部电影提取关键词,看到的东西会干净很多。
4.2 生成词云前必须做的三件事:分词、去停用词、字体指定
如果你自己写词云生成的代码,顺序一定要对:先分词,再过滤停用词,最后把文本拼成空格分隔的字符串交给wordcloud。有个常见错误是把整段原始评论直接扔进WordCloud,因为WordCloud默认是按空格和标点切词,中文没有空格,最后产出的图就是一堆乱码或整个文本粘成一团。所以必须用jieba先分词:
# 所有评论清洗后合并成一个长文本 seg_list = [] for comment in comments: words = jieba.cut(clean_text(comment)) for w in words: if w not in stop_words and w.strip(): seg_list.append(w) space_text = ' '.join(seg_list)stop_words从stopwords.txt加载,用set类型存,判断效率高。注意滤掉单字和纯标点,否则词云里全是“的”“了”“好”。userdict.txt确保专有名词不被切碎,不然“唐探1900”会变成“唐探”和“1900”,词云里会出现数字,看起来很业余。
4.3 用 wordcloud 生成中文词云,参数设置与代码
中文环境下必须指定字体路径,否则WordCloud找不到中文字形,输出全是方块。源码包里自带的SourceHanSansCN.otf就是为了这里。我一般用以下参数:
from wordcloud import WordCloud wc = WordCloud( font_path='SourceHanSansCN.otf', # 中文必须 width=1200, height=800, max_words=100, background_color='white', collocations=False # 避免重复词组堆叠 ) wc.generate_from_frequencies(dict(top_words)) # 用关键词权重生成 wc.to_file(f'{movie}_词云.png')width和height决定画布大小,输出png默认是位图,分辨率够用就行,不要盲目设太大,否则内存占用高且生成速度慢。max_words控制在100左右,词太多会糊成一片。collocations=False是为了让WordCloud不要自动把相邻词拼接成“电影 剧情”这类词组,否则关键词权重会被稀释。
4.4 从词云反推评论热点
词云图不只是好看,它可以用来快速验证情感分析结果。比如哪吒之魔童闹海词云里如果“好看”“特效”“燃”大字号出现,正向情感占比高就说得通;如果负面词字号很大但情感分布显示正向高,那就要回头检查停用词或阈值了。实际动手时我会把xlsx报告里的关键词列表和词云图对照看,确认词云没有过滤掉关键负面词。
还有一个容易被忽视的点:词云生成的随机颜色和布局每次都不一样,但只要词和权重不变,信息量就稳定。如果你看到同一批数据两次生成的词云差别巨大,那是wordcloud的随机性,不用慌。要复现结果,可以在生成前设置random_state参数,虽然wordcloud没有直接暴露,但你可以固定seed,稍麻烦,我一般留给有强迫症的人。
5. 避坑与常见问题:中文乱码、零词频、字体缺失、报告打不开
这类源码包在别人电脑上跑不起来,八成倒在下面几个坑上。我把踩过的记录按“现象-原因-解决”写出来,基本覆盖了新用户能遇到的主要问题。
5.1 词云输出全是一排排方块字
现象:png文件生成了,但上面不是汉字,是方框或乱码。
原因:WordCloud没有指定font_path,或者指定的字体文件路径不存在。Windows下默认字体没有中文字形,Linux下也一样。
解决:确认源码里的字体文件名和路径一致。如果还是不行,直接用绝对路径:
wc = WordCloud(font_path=r'D:\projects\movie_wordcloud\SourceHanSansCN.otf', ...)我建议先把font_test.png跑出来,那应该是一个字体渲染测试图,能正常显示就说明字体路径没问题。如果font_test.png本身就是乱码,那就重新设置字体路径,别急着跑主流程。
5.2 关键词提取结果为空或者全是“我们”“电影”
现象:extract_tags返回空列表,或者返回的全是停用词。
原因:文本清洗后剩的字太少,或者stopwords.txt包含的词太多;另一个原因是jieba.analyse需要全文足够长,评论只有几条撑不起统计。
解决:检查load_comments是不是读到了空数据;打印前100条评论长度。通常累计文本少于500字时,TF-IDF的统计没有意义,可以把多部电影评论合并后再提取关键词,或者调低topK试试。我跑过一个只有80条评论的冷门电影,topK=50提取出来的全是单字,改成topK=20才正常。
5.3 输出xlsx打不开或提示格式损坏
现象:Excel打开报告时提示文件损坏,或者只看到第一行表头。
原因:多数是pandas或openpyxl在写入多sheet时异常中断,也可能是文件名里有非法字符,比如“:”在Windows下是合法的,但某些版本Excel对全角冒号敏感。
解决:把项目路径改为纯英文目录,电影名里带“:”的在变量里替换成“_”。我一般这样写:
safe_name = movie.replace(':', '_') report_name = f'{safe_name}_分析报告.xlsx'使用pandas的ExcelWriter时,记得把engine设为openpyxl,写完调用report.save()再关闭。如果你用的还是xlwt,那只能写xls,别和openpyxl混用。
5.4 main.py直接运行后什么都没发生
现象:控制台没报错,但也没看到新的png和xlsx。
原因:常见是文件路径是相对路径,而当前工作目录不在项目根目录下;或者main.py里有个ifname== 'main':分支,但分支内没有调用主函数。
解决:在PyCharm里把工作目录设置成项目根目录,或者在main.py里用os.chdir(os.path.dirname(file))固定工作目录,然后检查主函数是否被调用。这个小坑我能记一辈子,因为第一次跑这类源码时浪费了半小时定位,最后发现是自己的工作目录在Desktop。
5.5 情感分布结果一边倒
现象:六部电影全部显示正面90%以上,或者全部负面。
原因:情感词典偏向严重或阈值设置太高/太低;也可能评论数据源本身有清理问题,没去掉水军评论。
解决:先随机抽50条评论人工标注,再计算平均得分。如果平均分明显偏离0,考虑调整阈值或补充情感词典。情感分析没有银弹,这一步是最需要人工干预的。还有一点:如果评论里有大量短评“赞”“顶”,正分数会被拉高,建议把少于5个字的评论单独过滤掉,它们信息量太低。
6. 进阶用法:把单电影分析改成批量对比,并自定义电影列表
这套源码已经很接近成品,但留了个扩展口:电影列表。我拿到后第一件事是把main.py里的movies写成了六个电影名,跑通一遍;第二件事就是改成从命令行读电影名,这样下次想分析别的电影不用改代码。
6.1 用命令行参数扩展电影列表
在main.py顶部加一段解析,接收sys.argv里的参数:
import sys if len(sys.argv) > 1: movies = sys.argv[1:] else: movies = ['哪吒之魔童闹海', '唐探1900']运行方式变成:
python main.py "流浪地球3" "疯狂的外星人"命令行传入的电影名会覆盖内置列表。这样你写一个爬虫脚本把多部电影的评论存成统一格式的csv,再循环调用这个main.py,就能自动生成所有报告,不用重复改文件。注意爬取时加延时,豆瓣对频繁请求会限制,源码虽然没带爬虫,但你自己补的时候别把访问密度拉太高。
6.2 把六部电影的xlsx报告合成一张对比表
我更习惯把六部电影的xlsx报告里的情感占比提取出来,拼成一张横向对比图。报告里的sheet名可能叫“统计汇总”,也可能是“情感统计”,先打开看一眼再写代码:
import pandas as pd import glob rows = [] for f in glob.glob('*_分析报告.xlsx'): df = pd.read_excel(f, sheet_name='统计汇总') rows.append({'电影': f.split('_')[0], '正面占比': df['占比'][0]}) summary = pd.DataFrame(rows) summary.to_excel('春节档情感对比.xlsx', index=False)注意:如果sheet名或列名不匹配,pandas会报错,这时先打印df.columns看看实际字段。输出对比表后,我通常还会把趋势图全部合并到一张画布上,观察六部电影在映后第几天的评论量最高,这比单独看单部电影更能看出宣发节奏。
最后我会检查一遍词云图、情感分布、趋势图三个文件的时间戳,确认都是最新生成的,不是上一轮遗留的老图。那个检查习惯帮了我很多次,因为脚本有时被中断,老图还在兜底,看结果时很容易被误导。从那以后我每次跑新数据集,都强制先验证“字体路径-停用词表-评论读取行数”三件事,再去看图。希望这篇笔记能帮你把这套源码真正跑起来,少踩几个我看过的坑,祝顺利。
本文还有配套的精品资源,点击获取