简介这份资源面向文本挖掘与舆情分析方向的学习者和开发者提供一套可直接运行的微博评论情感分析完整方案涵盖LDA主题建模与情感极性判别两大核心任务。压缩包共39个文件以23个Python脚本为主体辅以7个Markdown说明文档、7个txt语料与停用词表、1个w2v词向量模型及1个xlsx数据表整体约16.16MB目录按爬虫、数据清洗、LDA、情感分析、可视化等模块划分。已有2326人学习下载。读者可借助微博爬虫脚本获取评论数据经分词、去停用词等预处理后用LDA脚本完成主题抽取与超参调优再结合情感词典与API/SDK两种方式实现情感分类并通过折线图、余弦相似度等脚本输出主题分布与情绪走势。项目还包含热度计算、员工新增、情感均值等扩展分析适合作为舆情监控、市场研究或课程设计的实操参考。1. 微博评论情感分析从原始文本到 LDA 主题与情感标签的完整链路拿到一份微博评论数据想同时看清“大家在说什么”和“大家是什么情绪”这是很多做舆情、运营、产品分析的人最直接的诉求。微博评论短、口语化、表情符号多、噪声大直接丢进模型往往效果很差。一个可复现的方案是先做中文分词与停用词过滤再用 LDA 做无监督主题聚类最后用情感词典或轻量分类模型打情感标签两条线交叉验证。这套流程不依赖 GPU一台普通笔记本就能跑通适合想快速上手文本挖掘的新手也适合需要给业务方出可视化结论的熟手。下面按“数据准备 → 主题建模 → 情感判定 → 踩坑排查 → 调优技巧”的顺序把每一步的参数和边界讲清楚。2. 数据准备与中文预处理把微博评论变成 LDA 能吃的格式2.1 微博评论的脏数据长什么样微博评论的原始形态和标准语料差距很大。常见噪声包括转发链残留“//用户:”、话题标签“#话题#”、提及、URL、表情符号“[哈哈]”“[泪]”、重复刷屏、纯数字或纯字母。如果不处理分词后会出现大量无意义的高频词LDA 的主题会变得非常模糊。我一般先做一轮正则清洗再进入分词环节。清洗的目标不是“干净到像论文语料”而是“保留情绪和语义信息的同时去掉结构性噪声”。比如“#考研#”里的“考研”是有信息量的不能整个删掉应该只去掉井号保留内容。2.2 清洗与分词的完整代码import re import jieba import pandas as pd # 读取原始评论假设列名为 comment df pd.read_csv(weibo_comments.csv, encodingutf-8) df df.dropna(subset[comment]).copy() def clean_text(text): text str(text) # 去掉转发链 text re.sub(r//[\w\u4e00-\u9fa5\-][:], , text) # 去掉 提及 text re.sub(r[\w\u4e00-\u9fa5\-], , text) # 去掉 URL text re.sub(rhttp[s]?://\S, , text) # 话题标签只去符号保留内容 text re.sub(r#([^#])#, r\1, text) # 去掉表情符号 text re.sub(r\[[^\]]{1,6}\], , text) # 去掉多余空白 text re.sub(r\s, , text).strip() return text df[clean] df[comment].apply(clean_text) df df[df[clean].str.len() 4] # 过滤过短评论 # 加载停用词 with open(stopwords.txt, encodingutf-8) as f: stopwords set([line.strip() for line in f if line.strip()]) def tokenize(text): words jieba.lcut(text) words [w for w in words if w not in stopwords and len(w) 1] return words df[tokens] df[clean].apply(tokenize) df df[df[tokens].map(len) 2] df.to_pickle(weibo_clean.pkl) print(清洗后评论数, len(df))这段代码的逻辑是先做结构性噪声去除再分词再过滤停用词和单字。参数上len(w) 1是为了去掉“的”“了”“吗”这类单字但要注意“爱”“恨”这类单字情感词也会被误删。如果你的数据里单字情感词很重要可以改成保留一个自定义白名单。len(df[tokens]) 2是过滤掉分词后不足两个词的评论避免它们干扰 LDA。停用词表建议在通用表基础上加入微博场景的高频无意义词比如“转发”“微博”“真的”“感觉”等具体根据你的数据词频调整。2.3 构建词典和语料库的两种方式LDA 在 gensim 里需要词典Dictionary和语料库Corpus。常见做法是先建词典再过滤极端词频。filter_extremes是关键参数no_below去掉出现次数太少的词no_above去掉出现频率太高的词。微博评论里“哈哈”可能出现在 30% 以上的文档中这种词对区分主题没有帮助应该过滤掉。我一般设no_below5、no_above0.5再根据主题数量微调。from gensim import corpora dictionary corpora.Dictionary(df[tokens]) dictionary.filter_extremes(no_below5, no_above0.5, keep_n5000) corpus [dictionary.doc2bow(tokens) for tokens in df[tokens]] print(词典大小, len(dictionary))keep_n5000是保留词频最高的 5000 个词防止词典过大导致训练慢。如果你的数据量只有几千条可以降到 2000。注意doc2bow之后每条评论变成一个稀疏向量这是 LDA 的标准输入格式。3. LDA 主题建模参数怎么设、主题数怎么选3.1 LDA 在微博短文本上的适用边界LDA 假设文档是主题的混合每个主题是词的分布。微博评论很短通常一条只有十几个词这会导致 LDA 的统计信号不足主题容易发散。我的经验是评论长度中位数低于 8 个词时LDA 的主题可解释性会明显下降这时候要么把多条评论合并成“用户级文档”要么改用 BTMBiterm Topic Model这类专门针对短文本的模型。但 BTM 需要额外装包如果你只想用 gensim 快速跑通LDA 仍然可以作为基线只是主题数不要设太多一般 5 到 10 个就够了。3.2 训练 LDA 并输出主题词from gensim.models import LdaModel import pyLDAvis.gensim_models as gensimvis import pyLDAvis num_topics 6 lda_model LdaModel( corpuscorpus, id2worddictionary, num_topicsnum_topics, passes15, iterations100, alphaauto, etaauto, random_state42 ) for idx, topic in lda_model.print_topics(num_words10): print(f主题{idx}: {topic}) # 可视化保存 vis gensimvis.prepare(lda_model, corpus, dictionary) pyLDAvis.save_html(vis, lda_vis.html)参数说明passes15是遍历语料的次数太小会欠拟合太大会过拟合15 到 30 之间比较稳。iterations100是每次遍历内的迭代次数一般 50 到 100 够用。alphaauto和etaauto让模型自动学习先验省去手动调参。random_state42保证结果可复现这在写报告时很重要。print_topics输出的每个主题是一组词和权重你需要人工给主题命名比如“考研焦虑”“追星打榜”“日常吐槽”等。3.3 主题数怎么定困惑度和一致性主题数不是拍脑袋定的。常用两个指标困惑度perplexity和一致性coherence。困惑度越低越好但它在短文本上经常和人工判断不一致。一致性c_v更贴近人的感知一般越高越好。我一般跑 4 到 12 个主题画一条一致性曲线选拐点。from gensim.models import CoherenceModel coherence_scores [] for k in range(4, 13): model LdaModel(corpuscorpus, id2worddictionary, num_topicsk, passes10, random_state42) cm CoherenceModel(modelmodel, textsdf[tokens], dictionarydictionary, coherencec_v) coherence_scores.append((k, cm.get_coherence())) for k, score in coherence_scores: print(f主题数 {k}一致性 {score:.4f})如果一致性在某个点之后下降就选那个点。注意c_v计算比较慢数据量大时可以用u_mass替代但u_mass对短文本不太友好。选好主题数后把每条评论的主题分布保存下来后面可以和情感标签做交叉分析。4. 情感分析词典法还是模型法怎么选怎么跑4.1 两种路线的适用场景情感分析在微博评论上有两条主流路线基于情感词典的规则法和基于标注数据的分类模型。词典法不需要标注数据跑得快可解释性强但对网络新词和反讽无能为力。模型法需要标注数据效果上限更高但标注成本高且微博领域迁移时容易翻车。我的建议是如果只是做趋势判断词典法够用如果要精确到每条评论的正负中性且有一定标注预算用模型法。下面两条都给出可运行方案。4.2 词典法SnowNLP 加自定义规则from snownlp import SnowNLP def dict_sentiment(text): s SnowNLP(text) score s.sentiments # 0 到 1越接近 1 越正面 if score 0.6: return 正面 elif score 0.4: return 负面 else: return 中性 df[sentiment_dict] df[clean].apply(dict_sentiment) print(df[sentiment_dict].value_counts())SnowNLP 自带一个电商评论训练的模型直接用在微博上会有偏差。我一般会加一层规则如果文本包含“笑死”“哈哈”“喜欢”等强正面词直接判正面包含“恶心”“垃圾”“无语”等强负面词直接判负面。规则和模型冲突时规则优先。这样能把准确率拉高几个百分点。4.3 模型法用 BERT 做微调的轻量方案如果你有标注数据可以用transformers微调一个中文 BERT。没有标注数据的话可以用开源的情感分类模型做零样本或少量样本推理。下面是一个用预训练模型做推理的示例不涉及训练适合快速验证。from transformers import pipeline sentiment_pipeline pipeline( sentiment-analysis, modeluer/roberta-base-finetuned-jd-binary-chinese ) def model_sentiment(text): result sentiment_pipeline(text[:512])[0] label result[label] score result[score] if label.lower() in [positive, label_1]: return 正面 if score 0.7 else 中性 else: return 负面 if score 0.7 else 中性 df[sentiment_model] df[clean].apply(model_sentiment)注意text[:512]是截断因为 BERT 输入长度有限。微博评论一般不会超过 512 个字符但如果你合并了多条评论就要注意截断会丢信息。模型输出的score是置信度设一个阈值比如 0.7可以过滤掉模棱两可的样本减少误判。4.4 把主题和情感交叉起来看单独看主题或情感信息量有限。把两者交叉能回答“哪个主题的负面情绪最重”这类问题。具体做法是对每条评论取 LDA 主题分布中概率最高的主题作为主主题再和情感标签做交叉表。def get_main_topic(bow): topics lda_model.get_document_topics(bow) return max(topics, keylambda x: x[1])[0] df[main_topic] [get_main_topic(bow) for bow in corpus] cross pd.crosstab(df[main_topic], df[sentiment_dict], normalizeindex) print(cross)normalizeindex让每行加起来为 1方便比较不同主题下的情感占比。如果某个主题的负面比例超过 60%就值得重点分析。这个交叉表可以直接放进报告业务方一看就懂。5. 避坑与排查微博情感分析里最容易翻车的五个地方5.1 分词把情感词切碎了现象情感分析结果和人工判断差距很大检查发现“不开心”被切成“不”和“开心”导致情感反转。原因jieba 默认词典没有覆盖“不开心”这类组合词。解决把“不开心”“不喜欢”“不推荐”等否定组合加入自定义词典用jieba.add_word动态添加或者在分词前用正则把否定词和后面的词用下划线连接。5.2 LDA 主题里全是“哈哈”“转发”现象每个主题的前十个词都差不多都是高频无意义词。原因停用词表没覆盖微博场景词或者no_above设得太高。解决先统计词频把出现在超过 30% 文档里的词手动加入停用词再重新训练。no_above0.5可以降到 0.3 试试。5.3 情感词典对反讽完全失效现象“真是太好了又加班”被判为正面。原因词典法只看词不看语境。解决这种情况没有低成本完美方案。我的做法是对包含“又”“真是”“呵呵”等反讽标记的评论单独打一个“疑似反讽”标签不直接归入正面或负面交给人工复核。如果反讽比例高建议直接上模型法。5.4 主题数选太多导致主题重叠现象选了 15 个主题结果好几个主题的词几乎一样。原因微博短文本信息量有限主题数超过 10 个后模型开始强行拆分。解决把主题数降到 5 到 8 之间或者改用层次狄利克雷过程HDP自动确定主题数。gensim 有HdpModel但速度慢适合数据量不大的场景。5.5 训练结果每次跑都不一样现象同样的数据和参数两次跑出的主题词不同。原因LDA 是随机初始化没有固定随机种子。解决设置random_state42并且把passes和iterations固定。如果还是不稳定说明数据量太小建议增加数据或改用更稳定的模型。6. 进阶技巧用 pyLDAvis 和情感趋势图把结论讲清楚跑完模型只是第一步能把结果讲清楚才是落地。我一般会做两张图一张是 pyLDAvis 的主题可视化用来和业务方讨论主题是否合理另一张是情感随时间变化的趋势图用来发现舆情拐点。pyLDAvis 的 HTML 文件可以直接在浏览器打开左侧是主题气泡右侧是词频条交互式探索很方便。情感趋势图用 matplotlib 或 plotly 按天聚合正面和负面各一条线。import matplotlib.pyplot as plt df[date] pd.to_datetime(df[created_at]).dt.date trend df.groupby([date, sentiment_dict]).size().unstack(fill_value0) trend.plot(kindline, figsize(12, 5)) plt.title(微博评论情感趋势) plt.xlabel(日期) plt.ylabel(评论数) plt.legend(title情感) plt.tight_layout() plt.savefig(sentiment_trend.png, dpi150)这张图的关键是日期字段要提前解析好如果原始数据没有时间戳这一步就做不了。另外如果某天评论量突然暴涨结合 LDA 主题看当天的主要话题往往能定位到具体事件。我自己的习惯是每次跑完模型先看主题词是否可解释再看情感交叉表最后看趋势图。三步走完基本能判断这份数据值不值得深入做。如果主题词混乱、情感分布均匀、趋势无波动说明数据本身信息量不足换数据比调参更有效。希望帮到你。本文还有配套的精品资源点击获取