ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于Python的电影舆情分析全链路:从分词、情感识别到LDA主题建模与预警

2026/9/11 16:16:06 拓冰建站 浏览量
基于Python的电影舆情分析全链路:从分词、情感识别到LDA主题建模与预警 简介围绕电影《雄狮少年》的网络舆情分析项目提供了完整源码与项目说明适合计算机、数学、电子信息等专业学生用于课程设计、期末大作业或毕业设计参考。包内共157个文件包含44个Python脚本用于数据采集与分析、40个CSV文件汇集多平台评论数据、19张PNG图片展示可视化图表另有Markdown/HTML报告、PDF论文及参考资料压缩包整体40.47MB。目前已有65人学习下载。通过阅读源码和项目说明可掌握从舆情数据抓取、清洗、情感分析到结果可视化的完整流程项目说明中还梳理了研究思路与实验步骤便于借鉴对‘高口碑低票房’等话题的观点挖掘方法并迁移到其他影视或社会热点分析场景。1. 从“雄狮少年”弹幕到数据舆情分析源码包背后的完整链路《雄狮少年》在豆瓣短评和B站弹幕里呈现出两种截然不同的情绪形态长文本评论里“剧情”“角色”出现频次更高短文本里“画面”“配乐”出现得更密集。同样是一部电影的舆情平台不同、文本长度不同分析链路要动的环节就不一样。围绕“基于电影《雄狮少年》的网络舆情分析源码项目说明.zip”这个打包交付形态这里整理一条从采集、清洗、情感识别到话题演进与负面预警的完整技术链路目标是让拿到源码的同行不只是跑通主程序还能知道分词词典为什么这样加、情感阈值为什么取 0.5、主题数为什么试到 6 就停以及换一部影片后哪些参数必须重调。素材适合内容运营、数据分析师和爬虫方向的工程师对照使用整套方案在主流配置的笔记本上就能跑完。2. 分词、情感与主题中文舆情分析的核心模型与选型2.1 中文分词中的自定义词典与停用词以“雄狮少年”为例中文舆情分析的第一步不是训练模型而是把文本切成有意义的词。jieba 默认词典覆盖通用词汇但《雄狮少年》里的角色名“阿娟”“咸鱼强”以及“舞狮”“木棉花”这类电影语境词默认分词经常被切碎。常见做法是准备一个 custom_words.txt每行写入“词语 词频 词性”让分词器优先按整词切分。import jieba jieba.load_userdict(custom_words.txt) # custom_words.txt 内容示例 # 雄狮少年 10 n # 咸鱼强 5 n # 阿娟 5 n # 舞狮 10 n text 雄狮少年里的阿娟最后没有逆袭但咸鱼强师傅的舞狮真的很好哭 tokens [w for w in jieba.lcut(text)] print(tokens) stopwords {里, 的, 最后, 但, 真的, 了} tokens [w for w in tokens if w not in stopwords] print(tokens)jieba.lcut返回词列表默认开启 HMM 处理未登录词但对“咸鱼强”这类口语称谓仍可能错切。自定义词典中的词频会影响词图切分优先级填 5 到 10 即可生效词性可填可不填。停用词表要特别注意不能把“不”“没”“别”这类否定词去掉否则“不推荐”“没意思”在后续情感计算里会直接翻向。止损的做法是先跑一遍全量分词统计高频词表再人工筛出与电影无关的虚词加入停用词集合。B站弹幕里的“哈哈哈哈”“啊啊啊”这类拟声词也要单独处理它们对情感分值有干扰。分词之后做 TF-IDF 或 TextRank能进一步区分“电影”“少年”这类常词和“醒狮”“擎天柱”这类话题词。这一步不用做得太重但要保证词表能落在后续情感和主题模型使用的统一集合里否则后面每次调用都要重新加载词典。2.2 情感分析三选一情感词典、SnowNLP、BERT 的多场景对比拿到干净的分词结果后下一步是给每条评论打一个情感分。业界常用三种方案选型依据是标注成本、文本长度和解释需求。方案训练数据短文本效果长文本效果部署成本可解释性情感词典大连理工/知网通用领域人工整理一般一般低高SnowNLP电商购物评论较好一般低中BERT 微调需要自己标注好好高低SnowNLP 是很多舆情项目默认选型因为 pip 装完直接能用但要注意它是在电商评论上训练的对电影语境有偏置。“燃”“好哭”这类词在电商语料里几乎不出现情感分会被压低。我一般先跑一批数据看分布再决定要不要调整阈值或重训。from snownlp import SnowNLP import pandas as pd df pd.read_csv(danmu_cleaned.csv) df[sentiment] df[text].apply(lambda x: SnowNLP(x).sentiments) print(df[sentiment].describe())sentiments输出 0 到 1 的连续值越接近 1 越正向。0.5 不是天然分界因为模型偏置会导致整体分值偏移正确做法是人工标注 200 到 300 条评论画情感分直方图找到正向和负向分布的交界点。一般电影评论语料里这个交界点落在 0.35 到 0.45 之间所以很多项目把阈值定为 0.4。如果对准确率要求高就把 SnowNLP 的分值当作特征输入逻辑回归和文本长度、词频特征一起训练效果会比直接用原始分值好。BERT 微调适合有标注人力且文本较长的场景但舆情项目通常时间窗口紧张标注 300 条已经接近成本上限。2.3 LDA 主题建模K 值选择与干扰词过滤情感分只能看出正负看不出大家在吵什么。主题建模的作用是把评论拆成若干话题比如“剧情逻辑”“角色塑造”“画面音乐”“文化争议”这样舆情报告才有结构。gensim 的 LdaModel 是常见选择K 值不固定要按困惑度和人工可解释性来回试。import jieba import gensim from gensim import corpora docs df[text].tolist() texts [[w for w in jieba.lcut(d) if w not in stopwords and len(w) 1] for d in docs] dictionary corpora.Dictionary(texts) dictionary.filter_extremes(no_below5, no_above0.5) corpus [dictionary.doc2bow(t) for t in texts] lda gensim.models.ldamodel.LdaModel( corpuscorpus, num_topics6, passes10, random_state42 ) lda.print_topics(num_topics6)filter_extremes(no_below5, no_above0.5)表示删除在少于 5 篇文档中出现的词以及出现在超过一半文档中的词。后者的干扰很大“电影”“少年”“觉得”这类词几乎每篇都有不滤掉会让所有主题趋同。num_topics6是根据困惑度曲线和主题词可读性折中的结果K 值太小时“剧情”和“角色”会揉在一起太大时同一个话题被拆成两三个。passes控制迭代轮数数据量超过两万条时建议调到 20否则主题稳定性不够。每跑完一组 K 值打印主题下的前 10 个词肉眼判断主题之间区分度比单纯看困惑度数字更可靠。3. 数据获取与清洗搭建《雄狮少年》舆情数据集的最小可运行方案3.1 豆瓣、B站、微博三类数据源的采集差异不同平台的评论形态差异决定采集策略。豆瓣短评以 140 字内的长文本为主讨论集中但反爬严格登录态和请求频率都敏感B站弹幕和评论短促、口语化、情绪密度高接口相对开放适合做时间维度的突发检测微博话题流覆盖面广但噪声大需要先按话题 ID 过滤再存。做单一电影舆情分析时我一般以 B站评论为数据主干因为公开接口不用模拟点击时间戳齐全弹幕密度能反映实时情绪波动。数据源接口形式核心字段主要限制豆瓣短评网页反爬评分、短评内容、时间封禁风险高需登录B站评论公开 JSON 接口评论内容、回复数、点赞数、ctime风控限频部分接口需 Cookie微博话题移动端接口博文、转发、评论登录授权复杂数据冗余高B站评论接口有个细节视频页面地址里的av号是 aid接口要用oid参数传它如果用 BV 号部分接口要额外转换。排序参数sort0按时间正序可做时序分析sort2按热度排序适合抓高质量长评。两个方向最好各抓一份一份做趋势一份做话题结构。3.2 用 B 站评论 API 抓取目标视频评论的 Python 脚本B站公开评论接口会校验请求头完全裸的 requests 容易被拦。常见做法是先从浏览器开发者工具复制一次真实请求的User-Agent和Cookie代码里只改分页参数控制频率后能稳定跑完几千条评论。import requests import time import pandas as pd headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Cookie: 从浏览器开发者工具复制, } all_rows [] params { type: 1, # 1 表示视频评论 oid: 517125995, # 换成目标视频的 aid sort: 0, # 0 按时间正序2 按热度 pn: 1, # 页码 ps: 20, # 每页条数 } for page in range(1, 21): params[pn] page resp requests.get( https://api.bilibili.com/x/v2/reply, headersheaders, paramsparams, timeout10 ) data resp.json() replies data.get(data, {}).get(replies) or [] if not replies: break for r in replies: all_rows.append({ text: r[content][message], like: r[like], reply_count: r[rcount], ctime: r[ctime], }) time.sleep(1) df pd.DataFrame(all_rows) df.to_csv(danmu_raw.csv, indexFalse, encodingutf-8-sig)sort0是舆情分析的关键参数它保证评论按发布时间排列后续按ctime做时间窗口聚合时才不会错位。ps最大值是 20调大不会更快反而容易触发风控。每页之间time.sleep(1)是底线跑短评数据时我会加到 2 秒。响应解析里先判data.replies为空再退出循环防止翻页到底后继续请求浪费次数。存 CSV 用utf-8-sig编码Excel 打开不会乱码后续 pandas 读进来也顺畅。注意本方案仅用于个人学习与研究请求频率要控制在对目标平台无影响的范围采集的数据不要用于商业化传播。3.3 simhash 文本去重与水军特征识别抓下来的原始评论里重复短评占比经常超过 15%。有的是用户复制转发有的是同一账号在多个视频下重复刷同一句。直接按字符串去重会漏掉“太好看了吧”“太好看了吧”这类微变体simhash 是处理这种场景的实用工具。from simhash import Simhash def is_duplicate(new_text, seen_hashes, threshold3): sh Simhash(new_text) for h in seen_hashes: if sh.distance(h) threshold: return True seen_hashes.append(sh) return False seen [] df[is_dup] df[text].apply(lambda x: is_duplicate(x, seen)) df df[~df[is_dup]].drop(columns[is_dup])simhash 把文本压缩成 64 位指纹distance表示汉明距离也就是两个指纹有多少位不同。距离越小越相似阈值 3 表示允许少量文字差异比如多一个标点或换一个词。短文本对噪声更敏感弹幕类数据建议把阈值降到 2否则“好看”和“好好看”会被判重误伤正常表达。水军识别只看文本不够还要统计同一用户 ID 的评论条数、发布时间的集中程度。如果某个用户在一小时内连续发 30 条好评即使文本各不相同也值得单独打标在情感分布统计中按权重降权。4. 热度演化、话题阶段与负面预警舆情分析结果怎么用4.1 舆情热度指数把“发酵”“讨论度”量化成一个数评论量、点赞量、回复量单位不同直接相加没有意义。我会先把每个指标做 z-score 归一化再按权重合成一个热度指数。权重没有标准答案要按数据源特性来定弹幕和短评论反映讨论规模点赞代表认可强度回复数代表争议深度。指标权重说明每日评论/弹幕量0.4反映讨论规模点赞/投币数0.3反映情绪强度回复数0.3反映争议深度daily df.groupby(date).agg( comment_count(text, count), like_sum(like, sum), reply_sum(reply_count, sum), ).reset_index() for col in [comment_count, like_sum, reply_sum]: daily[col _z] (daily[col] - daily[col].mean()) / daily[col].std() daily[heat] ( 0.4 * daily[comment_count_z] 0.3 * daily[like_sum_z] 0.3 * daily[reply_sum_z] ) daily[heat_smooth] daily[heat].rolling(3, centerTrue).mean()rolling(3, centerTrue)做三日中心滑动平均能削掉单日突刺。分析时我会同时保留原始曲线和滑动平均曲线原始曲线找爆点平滑曲线看趋势。微博搜索指数这类外部数据如果拿得到可以作为第 4 个指标加入权重替换 comment 的 0.1让热度指数兼顾站内站外。4.2 情感趋势的窗口统计均值、标准差与分位数热度指数回答“多少人讨论”情感趋势回答“讨论的方向怎么变”。按天聚合情感分后不能只看均值标准差和负面占比更能体现舆论是否在撕裂。daily_sent df.groupby(date)[sentiment].agg( mean_scoremean, std_scorestd, neg_ratiolambda x: (x 0.4).mean(), ) daily_sent[roll_mean] daily_sent[mean_score].rolling(3).mean() daily_sent[roll_std] daily_sent[std_score].rolling(3).mean()neg_ratio使用 2.2 节校准后的 0.4 阈值计算负面占比不能全局硬编码每部电影的分布不同。标准差的含义要单独解释均值不变但标准差上升说明支持者和反对者都在坚持己见舆论两极化均值下降同时标准差上升说明负面情绪在扩散且没有形成统一共识这是风险最高的一种状态。分位数比均值更稳健可以补充看 10% 分位数它代表最激烈的那批负面评论的分值走向。比如均值从 0.48 缓慢回升到 0.52看着乐观但 10% 分位数从 0.30 掉到 0.22说明核心负面群体的情绪还在恶化这种信号均值看不到。4.3 话题演进从首映热议到二轮口碑的识别方法LDA 主题模型本身不携带时间信息把时间切片和主题分布叠在一起就能看出舆论焦点怎么迁移。方法是对每一天的评论求主题平均概率再按周聚合。topic_series [] for bow in corpus: dist dict(lda.get_document_topics(bow, minimum_probability0.05)) topic_series.append(dist) topic_df pd.DataFrame(topic_series).fillna(0) topic_df[date] pd.to_datetime(df[date]) weekly_topic topic_df.groupby(pd.Grouper(keydate, freqW)).mean()minimum_probability0.05把概率低于 5% 的主题权重清零保证一条短评只落在一到两个主题上减少噪声。按周聚合能平滑日粒度波动尤其适合首周内集中爆发型舆情。操作上我会把每周占比最高的主题打上标签输出一份“第 1 周主题 A 占比 60%第 3 周主题 C 占比 45%”的转移矩阵。再配合各主题下权重最高的词做关键词追踪比如“剧情”主题里的“逻辑”“强行”突增说明讨论焦点从“画面”转向了“剧本”这时候情感均值可能没有变化但舆论议题已经切换后续内容运营要换一套应对口径。4.4 负面预警规则从统计值到自动告警把舆情指标翻译成可执行的告警规则才算是从分析走到应用。规则参数必须基于前面几步校准出来的分布不能照抄别的项目的阈值。规则触发条件信号含义情感均值下穿3 日均值低于 0.42整体情绪转为负面负面占比激增neg_ratio 大于 0.35 且周增幅超过 20%突发性负面事件争议话题扩散单一主题占比超过 50% 且连续 3 周上升舆论焦点向争议点集中latest daily_sent.iloc[-1] if latest[roll_mean] 0.42: print(alert: 情感均值下穿阈值建议排查诱因) if latest[neg_ratio] 0.35: print(alert: 负面评论占比超过 35%)这里加一个反直觉的检查如果均值在上升但标准差同步上升要把第一条规则改成“均值下穿且标准差连续两日上升”再触发否则会出现“均值回升但标准差飙升”的假回暖告警反而漏了。真实舆情事件里最危险的点不在情感分最低的那一天而在负面占比连续三日超过 30% 且话题正在切换的阶段这时负面还没完全暴露应对窗口最充分。5. 舆情结论的定量校验用标准差和置信区间拒绝“感觉”均值 0.53 和 0.50 差异算不算真的偏正向很多时候是抽样波动不是真实信号。我习惯在报告里加一个自助法置信区间用最少的代码给结论上证据。import numpy as np rng np.random.default_rng(42) scores df[sentiment].values boot_means np.array([ rng.choice(scores, sizelen(scores), replaceTrue).mean() for _ in range(2000) ]) ci_lower, ci_upper np.quantile(boot_means, [0.025, 0.975]) print(fmean{scores.mean():.4f}, 95% CI[{ci_lower:.4f}, {ci_upper:.4f}])原理是有放回地抽取 2000 次与原样本等量的子集每次计算一个均值得到均值抽样分布取 2.5% 和 97.5% 分位数作为置信区间。default_rng(42)固定随机种子保证报告里的数字可复现。判断标准很简单如果置信区间下限大于 0.5可以说整体倾向正向如果区间包含 0.5就写“与中性无明显差异”。样本量小于 50 时改用 t 检验占比类指标用卡方检验结论形式完全不一样。把这段脚本存成 verify_ci.py连同 requirements.txt 放进项目说明 zip 包的根目录下一个人拿到包里先跑这个文件就能知道你的分析结论是否站得住。换电影时只需改动 CSV 路径和情感阈值其余代码原样可用。本文还有配套的精品资源点击获取