简介面向微博文本情感分析任务这份Python资源包为NLP初学者及舆情分析实践者提供了可直接上手的完整示例。内容覆盖中文短文本的预处理、分词、停用词过滤、特征提取与情感分类内置Python主程序、中文停用词表、十万级微博情感语料数据集以及LSTM可视化结果图便于对照代码理解从原始数据到模型预测的完整流程。压缩包共4个文件含py脚本、txt词表、csv语料与png图像整体约9.76MB结构紧凑适合课程设计、毕业设计或小型舆情监控项目参考。已有199人学习下载。借助这套资源读者可以快速搭建微博情感分析实验掌握jieba分词、TF-IDF/Word2Vec特征表示以及朴素贝叶斯、SVM等分类器在中文文本上的应用差异并基于自带语料完成模型训练与评估。可视化输出还能帮助复现LSTM情感判别的中间过程为后续模型改进或迁移到其他社交媒体数据提供实用基础。1. 微博情感分析是什么先想清楚“情绪”和“观点”的差别“哈哈哈哈哈”在模型眼里大概率是纯正面但在微博语境里可能只是一条敷衍回复一句只有“无语”两个字的评论才是真正需要关注的负向信号。这种字面与语义错位的判断正是 Python 微博文本情感分析要解决的日常问题在短文本、强口语、混合表情和话题标签的内容里自动区分正面、负面与中性。这个方向最直接的价值是替代人工逐条读评论。舆情监控、产品反馈归因、客服工单排序、活动复盘都会用到它当文本量到几千条以上时人工看不过来模型分类就成了刚需。它适合刚入门的 NLP 学习者也想传达给做数据分析的工程师先建立一条从数据清洗到模型评估的完整链路再决定要不要上复杂模型。微博情感分析和新闻分类不同它的难点不在模型选型而在清洗和口语语义的判断这两个环节占了整个落地工作量的七成。把这两个问题先解决掉后面不管是传统机器学习还是预训练模型都会顺手很多。2. 微博文本从哪来数据收集、清洗和标签的落地做法2.1 数据获取在动手抓取之前先建一个最小可用数据集做情感分析的第一步不是选模型而是把数据握在手里。微博文本可以走三条路径公开语料、自采评论、手工标注。公开语料来自一些 NLP 开源项目里面通常附带脱敏后的短文本和情感标签适合先把代码链路跑通不需要关心登录和采集问题。自采评论是不少同学习惯的做法直接在 pycharm 里写爬虫抓微博评论来调试这个方案的风险在于频率和会话状态目标网站的接口对高频访问很敏感很容易把自己账号搞到受限。我一般建议先手动收集几百条自己可见的公开评论存成 txt 或 csv把流程跑通后再考虑自动化。手工标注是数据不足时的兜底方案。我会从自采数据里随机抽 1000 条按“正面、中性、负面”三个标签人工打标。1000 条听起来不多但对微博这种强口语场景已经足够暴露数据里的大部分问题转发、营销、反讽、短句都会在这个样本量里出现。先别追求量大数据量一大清洗规则和标注口径容易翻车后面排查成本比标注成本还高。我常用一个两列的 csv 作为最小数据集格式是这样raw_text,label 这家店服务态度太差了,neg 奶茶味道不错就是排队久,pos 路过随便看看,neu这样标注好的数据就可以用 pandas 直接读入。标签我用 neg/neu/pos不用 0/1/2因为三分类任务里数字标签容易在画混淆矩阵时忘记还原含义字符串标签在 groupby 和绘图时更直观。数据到手第一件事是看类别分布。微博文本里中性内容和营销内容占比很高负面比例经常只有 10% 左右要是直接训练模型会偷懒地把几乎所有句子都判成中性。常见做法是在建标签时把每个类别控制在总量的 25%–40%不够就补样本而不是靠过采样硬撑。如果不想维护 csv也可以直接把这 1000 条数据写进pandas.DataFrame再df.to_csv落盘。我习惯落盘因为清洗、标注、切分每一步都要能回放黑匣子式的处理在模型效果不好时完全没法排查。2.2 清洗规则去掉 URL、话题和 但保留方括号表情微博文本的噪声和新闻语料完全不同。一条典型微博里会混着 URL、用户名、#话题#、方括号表情、连续感叹号这些内容如果不清洗会直接变成特征矩阵里的噪声维度。我常用的清洗函数如下import re import pandas as pd def clean_weibo_text(raw: str, keep_emotion_mark: bool True) - str: # 1. 去掉 URL 和多余空白 text re.sub(rhttps?://\S|www\.\S, , raw) # 2. 去掉 用户名 和 #话题词# text re.sub(r[\w\u4e00-\u9fa5\-], , text) text re.sub(r#.?#, , text) # 3. 连续重复标点压成单个避免特征矩阵被撑大 text re.sub(r([。!?]){2,}, r\1, text) # 4. 方括号表情默认保留例如[泪][微笑]它们是强情感信号 if not keep_emotion_mark: text re.sub(r\[.?\], , text) return text.strip() df pd.read_csv(weibo_sentiment.csv) df[clean_text] df[raw_text].apply(clean_weibo_text) df df[df[clean_text].str.len() 2] df.to_csv(weibo_sentiment_clean.csv, indexFalse)这里每个正则有它自己的理由。先做 URL 清洗是因为 URL 里可能带中文参数和特殊符号如果放在后面会干扰其他正则去掉 和话题是因为它们更多是内容组织作用不携带情感倾向连续重复标点压缩成单个是为了避免“”和“”被当成两个完全不同的特征这两种写法在语气上没有本质区别。keep_emotion_mark这个参数值得单独说。微博的方括号表情不是噪声[泪]在语感上约等于“哭了”是一种强负面信号[微笑]在部分语境下是嘲讽甚至敷衍。常见做法是默认保留让模型自己学。如果你的预处理管线要求删除表情也应该在删除前把它单独抽成一列布尔特征。清洗完成后一定要抽样检查。我会用df.sample(50)随机看 50 条清洗前后的对比重点看三件事URL 是否清干净、 是否残留、表情是否被误删。清洗后文本长度很短的样本直接丢掉代码里df[clean_text].str.len() 2的目的就在这里。如果你的业务里会出现单字情绪表达比如一个“不”字可以把阈值改成 1但要接受噪声变多的事实。有一个顺序问题值得多说一句先清洗再分词。如果先分词再清洗URL 和 会被切成一堆无意义词后面还要二次处理。我一开始也在这个顺序上犹豫过后来固定成“清洗 - 分词 - 停用词过滤”三步每一步的结果都落盘保存排查时随时可以回退。注意不要在清洗阶段做停用词过滤尤其不要把“不”“没”“别”“太”这类否定词放进停用词表。这个动作要挪到分词之后而且否定词必须单独放行否则“不好”和“好”在模型眼里会变成同一个特征。2.3 标签策略三分类是默认起点别一上来就做五分类微博情感分析里二分类覆盖不全因为大量微博是中性、转发和营销内容五分类标注成本高数据少的时候模型分不准边界。我一般建议从三分类开始明确表达不满、投诉、失望算 neg明确表扬、感谢、安利算 pos其余都归 neu。给一条判定示例“再也不来了要求退款”是 neg“味道不错服务也可以”是 pos“今天路过这家店”是 neu。这种口径的好处是和业务容易对齐。等三分类跑通后再用第 6 章讲的强度分档工作把五档输出补上不需要重新训一个五分类模型。3. 中文分词与向量化让模型吃到“词”而不是“字”3.1 jieba 分词自定义词典比调参更管用中文文本不能像英文那样直接按空格切词分词是第一步。jieba 是常见做法默认词典覆盖通用词汇但对微博口语不够。比如“绝了”“破防”“无语子”这类词默认状态下会被切成单字或错误组合情感特征全丢了。我每次都会准备一个weibo_dict.txt把业务里高频出现的网络词和品牌词放进去格式是“词语 词频 词性”示例绝了 100 x 破防 100 x 无语子 100 x 集美 100 n词频不需要按真实统计来给 100 左右就够了给得太高会让分词器过度倾向这个词反而影响上下文切分。词性统一填 x 或 n 都行jieba 在用户词典里主要用词频字段。加载词典和分词的过程如下import jieba jieba.setLogLevel(60) # 关掉 jieba 的初始化日志 jieba.load_userdict(weibo_dict.txt) # 自定义词典路径 text 这家店的奶茶真的绝了[微笑] tokens [w for w in jieba.lcut(text) if w.strip()] print(tokens)代码里jieba.setLogLevel(60)是为了关掉初始化日志不关也不影响结果只是在 pycharm 或 jupyter 里输出会很难看。分词结果 tokens 里可能混着标点和空白所以后面用if w.strip()过滤。分词后的停用词过滤要放在这里而不是清洗阶段。我会先准备一个虚词集合再单独留出否定词stopwords {的, 了, 就, 都, 是, 在, 和, 等, 啊, 吧} tokens [w for w in tokens if w not in stopwords and w not in {不, 没, 别, 太, 很}]把否定词排除出停用词表这件事是情感分析最容易翻车的一个细节。分词后“不好吃”会切成“不|好吃”如果“不”被过滤模型就只能看到“好吃”整条样本在特征层面直接反转成正面。分词质量要不要检查我会随机挑 20~30 条文本打印分词结果如果发现“绝了”被切成“绝|了”就把“绝了”加进词典重跑。这个习惯比调任何模型参数都重要因为传统模型根本看不到你分词切错了。3.2 向量化TF-IDF 的参数要按微博短文本调分词完成后接下来要把文本转成向量。传统机器学习里最常用的是 TF-IDF不是 Word2Vec。原因是微博文本太短词共现信息稀疏Word2Vec 在这种数据上学不到足够稳定的语义TF-IDF 至少能直接把“绝了”“太难喝”这类词的权重放大模型可以直接利用。我常用的配置如下from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( max_features20000, ngram_range(1, 2), min_df2, sublinear_tfTrue, ) X vectorizer.fit_transform(df[clean_text].tolist())max_features20000限制特征维度。微博的词表很大如果全量放进训练矩阵会非常稀疏模型也容易过拟合。20000 是一个比较稳妥的起点特征覆盖不足时再往上调。ngram_range(1, 2)除了单字词还保留双字组合“不好吃”在 1-gram 下是“不”“好吃”在 2-gram 下会多出“不好”“好吃”“不吃”这样的组合否定信息有一定概率被保留下来。min_df2表示只在一条文本里出现过的词绝大多数是噪声直接丢弃。如果语料本身很小可以降到 1否则不建议。sublinear_tfTrue把词频变换成1log(tf)防止“微博”“今天”这类高频词在情感判断里压制真正有信息量的词。建模前还有一个选型问题要不要用字符级 n-gram微博里很多网络梗是错别字或变形写法比如“泰酷辣”“这能处”词级特征会漏掉但字符级 n-gram 会把“你”“的”“了”这类字也纳入特征噪声远大于收益。传统模型下我还是用词级。如果你的最终目标是上 BERT这一整节的分词和 TF-IDF 都可以跳过。预训练模型的输入是原始文本词表由它自己决定强行先分词反而会丢失上下文。先想清楚自己走哪条路径再决定要不要做分词能省很多无用功。提示如果你准备上 BERT分词和 TF-IDF 都不需要做直接喂清理后的原始文本即可。4. 模型选择先跑朴素贝叶斯再决定值不值得上 BERT4.1 朴素贝叶斯给三分类先定基线数据转为 TF-IDF 矩阵后就要选一个能立刻上手的模型。MultinomialNB 是文本分类最常用的基线之一它的假设是特征服从多项式分布和 TF-IDF 的稀疏矩阵天然匹配。训练代码很简单from sklearn.naive_bayes import MultinomialNB from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report X_train, X_test, y_train, y_test train_test_split( X, df[label], test_size0.2, random_state42, stratifydf[label], ) clf MultinomialNB(alpha0.5) clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(classification_report(y_test, y_pred, target_names[neg, neu, pos]))几个参数值得展开。alpha是拉普拉斯平滑系数默认是 1.0微博这种长尾词很多的数据集特征里大量是低频词平滑系数太大会让这些词的统计信息被磨平调成 0.5 或 0.3 通常会好一些。random_state42是为了结果可复现不要省略stratifydf[label]按类别比例切分避免某一类完全落在训练集或测试集里。输出里重点看 neg 和 pos 的 precision、recall、F1不要只看 accuracy。原因在于微博数据里中性占比高模型只要把全部文本判成 neu准确率也能到 70% 以上但这样对业务毫无价值。如果 neg 的 recall 只有 0.3说明一半以上的负向反馈没有被抓到需要回查清洗和标签。训练完后还可以做一次特征体检。MultinomialNB 的feature_log_prob_记录了每个特征在每个类别下的对数概率把它按类别排序打印概率最高的 20 个词和最负的 20 个词。如果 neg 类里出现“好吃”“喜欢”这类明显正面的词多半是标签或清洗出了问题如果 pos 类里出现“难喝”“客服”这类词也一样要回到数据层排查。4.2 BERT 不是默认选项是 F1 到瓶颈以后的选项如果朴素贝叶斯的宏观 F1 低于 0.7先不要换模型大概率是数据或清洗的问题。如果宏观 F1 能稳定在 0.75 以上并且还想继续提升可以考虑预训练模型。中文情感分析场景里常见做法是加载bert-base-chinese这类开源预训练权重只把分类头改成 3。微博是短文本单条长度设定max_length64就够了不需要像长文档那样保留 512 个 token。代码结构如下from transformers import AutoTokenizer, AutoModelForSequenceClassification model_name bert-base-chinese tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForSequenceClassification.from_pretrained(model_name, num_labels3)BERT 能纠正传统模型最头痛的反讽问题。TF-IDF 朴素贝叶斯看到“这家店味道可以就是服务太差”由于“可以”的权重更高很可能判成中性BERT 能结合上下文识别出整句有明确负向倾向。代价是显存和推理延迟。没有 GPU 时训练和推理都很困难有 GPU 时也要区分是离线批量打标还是线上实时接口。我遇到的多数项目线上兜底用的还是传统模型加人工复核BERT 更多用于离线批量重算。BERT 训练本身还要注意类别权重。三类样本量差异大时可以把class_weight设成 balanced或者在 loss 里给少数类更高的权重。具体训练参数上我的常用起点是batch_size16、learning_rate2e-5、epochs3数据量少于 5000 条时别贪多2~3 个 epoch 足够再训练容易过拟合。5. 常见翻车与排查把微博文本情感分析里的坑提前说出来上面这些流程走完之后你大概率会遇到下面几个问题。它们不会同时出现但出现任何一个都足以让项目原地打转。以下每一条都按“现象、原因、解决”来讲排查的时候可以直接对照。5.1 清洗后出现大段空文本模型开始乱分现象训练前统计发现有几百条样本清洗后只剩空白或者长度只有 1。模型在 TF-IDF 矩阵里拿到一堆零向量会全部归到先验概率最高的类别里。原因最常见的两个一是停用词表太激进把“不”“没”等词全删了二是正则顺序不对比如先用[^\u4e00-\u9fa5]把所有中文以外的字符删光把表情和 URL 都清理了但文本本来就短最后什么都不剩。解决清洗后立刻跑一次df[clean_text].str.len().value_counts()看空文本比例。超过 0.1% 就要回头检查清洗规则。空文本行可以直接删掉也可以在清洗前单独统计一下确认是某类特殊内容比如纯表情再决定是否保留。5.2 离线指标很好新数据一测就崩现象train_test_split随机切分后 F1 有 0.8换到按时间排序的后 20% 数据F1 掉到 0.6。原因随机切分会把同一时间段、同一话题的数据同时分进训练集和测试集模型学到的是话题词而不是情感。比如某段时间微博上大量谈论“发布会”模型可能会把“发布会”学会和某类情感强相关换个话题就失效。解决做舆情分析时我一般按时间排序前 80% 做训练、后 20% 做测试。这样才能看到模型在“未来未见数据”上的真实表现。如果数据没有时间戳至少要按文本来源或话题分组切分而不是纯随机。5.3 方括号表情被当成噪声删了现象带[泪]的文本被分到中性带[微笑]的文本被分到负面和人工判断明显不符。原因清洗阶段用了删除所有方括号内容的正则比如re.sub(r\[.?\], , text)把[泪]、[微笑]这些强情感信号当成标签噪声删掉了。解决保留方括号表情。如果必须删除要在删除前抽出一列“是否含负面表情”的布尔特征再把它拼进模型输入。更细一点的做法是把[泪]替换成“流泪”这两个字让模型在文本维度直接感知情绪而不是依赖一个布尔特征。5.4 否定词被分词和停用词一起干掉现象“不好吃”这个明显负向的样本被预测成正面。原因分词后“不好吃”切成“不|好吃”随后停用词过滤把“不”删掉特征矩阵里只剩“好吃”模型当然判正向。这是情感分析最经典的一个翻车点几乎每个阶段都可能踩到。解决停用词表不过滤否定词这一步是底线。其次在向量化阶段开启ngram_range(1, 2)让“不好”“好吃”“不好吃”都作为独立特征保留。如果你用的是 jieba 自定义词典还可以直接把“不好吃”“很难喝”这类高频情感短语整体收进词典彻底避免被切碎。5.5 三分类的“负面”和业务要的“负面”对不上现象模型把“难喝”判成负面但业务方真正想抓的是“要求退款”“投诉”“再也不来”这类动作而非简单情绪。原因情感分类解决的是“态度正负”不是“行为意图强度”。情绪负面和行动意图虽然高度相关但不完全等价业务指标挂在“强负面意图”上就会觉得模型不准。解决在三分类之外额外维护一个强负面意图词表比如包含“退款”“投诉”“拉黑”“再也不”“差评”在模型输出后做一个规则修正。概率分档如果落在弱负或中性但文本命中这些词直接上调一档。这个做法不增加模型训练成本也能明显提高业务满意度。6. 把结果用起来情感强度分档与置信度过滤的实战技巧6.1 用概率分档替代裸概率业务才能用起来模型训练完predict_proba会给出一条文本属于各类别的概率。很多人习惯直接把概率当强度用但这样做有偏差概率是模型对分类的置信度不是业务上的情感强度。我第一版方案就是把概率直接当强度用结果“有点难喝”和“难喝到想哭”的负面概率都在 0.7 左右运营同事来问“到底哪个要优先处理”我才意识到问题。后来改成五档输出对应概率区间的分档如下分档负面概率区间对应文本示例处理建议强正 0.2绝绝子无限回购提取正面卖点弱正0.2-0.4还行可以试试入库观察中性0.4-0.6路过随便看看不触发动作弱负0.6-0.8一般般不推荐客服跟进强负 0.8再也不来了要求退款优先响应这样业务侧就能直接按档位排序。强负优先处理中性和弱正入库观察不需要再关心概率分布长什么样。6.2 置信度过滤与关键词修正让结果更稳定除了分档还要过滤低置信度样本。常见做法是把概率落在 0.45~0.55 这个夹缝里的文本标记为“待人工复核”不让它自动进入业务动作。舆情场景里这部分通常占 10%~15%量不大但价值高因为模型犹豫的样本往往正好是语义复杂的样本。另外用关键词做修正也很实用。命中“退款”“投诉”“再也不”的文本即使概率不高也是强负向因为这些都是用户明确的表达诉求信号。修正逻辑如下STRONG_NEG_KEYWORDS [退款, 投诉, 再也不, 差评, 拉黑] def adjust_strength(neg_prob, text): # 先把概率映射到 1~5 档1 强正5 强负 if neg_prob 0.2: base_rank 1 elif neg_prob 0.4: base_rank 2 elif neg_prob 0.6: base_rank 3 elif neg_prob 0.8: base_rank 4 else: base_rank 5 # 命中强负向意图词直接上调档位 if base_rank 4 and any(k in text for k in STRONG_NEG_KEYWORDS): return min(5, base_rank 2) return base_rankadjust_strength不修改原始概率只修正业务分档。模型概率仍然保留在数据库里人工复核时能对照。实际部署时我会在每条记录里同时保存原始文本、清洗后文本、模型概率、分档、是否命中关键词五列运营直接按强负排序就能开始干活。我现在的习惯是模型分数只作为排序参考业务动作永远由分档和关键词共同决定。这个方向从头做下来最值钱的不是某个模型有多高级而是清洗、标注、分档这条链路足够稳定效果翻车时能快速定位。希望帮到你。本文还有配套的精品资源点击获取