news 2026/10/3 14:12:30

情感词典+机器学习:微博评论情感分析实战项目拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
情感词典+机器学习:微博评论情感分析实战项目拆解

简介:结合情感词典与机器学习的新闻和微博评论情感分析资料包,面向自然语言处理入门者、舆情分析从业者及电商口碑研究者。项目通过哈工大、知网情感词典提取情感特征,并借助朴素贝叶斯、支持向量机、随机森林等分类器完成正面、负面与中性类别预测,同时涉及情绪强度、情感主题等细化扩展,能够帮助读者快速搭建完整的情感分析实验流程。压缩包共39个文件,大小2.63MB,其中16个Python脚本覆盖数据获取、特征生成与模型训练,9个Markdown文档提供研究背景、数据采集与报告说明,7个CSV文件为实验数据集,另有6个备份文件及1个附加内容压缩包,整体结构清晰、便于按模块学习。目前已有33人浏览/学习,资料来源于网络分享,主要用于学习交流,读者可在此基础上进行特征工程、参数调优及代码复现,以加深对文本情感分析全流程的理解。

1. 情感词典 + 机器学习:这个压缩包里到底有什么,能解什么燃眉之急

做舆情分析或者文本挖掘课程设计的人,大概率经历过这种尴尬:跑通了开源的情感分析 demo,换到自己的数据上准确率直接对半砍。原因往往是通用模型根本不认识你领域里的说法。这个项目压缩包走的是另一条路——用情感词典打底(哈工大、知网两套),再把词典特征喂给朴素贝叶斯、SVM、随机森林做分类。词典负责兜住「高兴」「愤怒」这类的确定性情绪,机器学习负责兜住词典覆盖不到的长尾表达。适合谁?正在做新闻评论、微博评论情感分析的在校生和初级从业者,它把爬虫、词典构建、特征工程、模型训练串成了一条完整链路,不是那种只给你一个孤零零的 ipynb 文件就完事的资源。

2. 数据获取:Spyder 爬虫脚本拆解与评论数据的正确打开方式

2.1 项目里的爬虫脚本各自在干什么

解压后能看到newsSpyder.py、jstvSpyder.py、Weibo.py、tryloadjson.py这几个脚本。其中Weibo.py走的是微博评论采集,newsSpyder.py和jstvSpyder.py负责新闻评论,tryloadjson.py是拿来验证 JSON 数据能否被正确读取的辅助脚本。数据目录里那个「四阶段新浪新闻标题」,说明作者当时是按时间阶段分批抓的新闻标题和评论,把爬取任务拆成多个阶段,是避免被封 IP 的常见做法。

微博评论的抓取,常见做法是走移动端接口,因为移动端的反爬强度比 PC 端低不少,而且返回的是结构化 JSON,解析起来比 HTML 正则省事。新闻评论相对简单,多数是服务端渲染的页面,直接定位评论区的 DOM 节点就行。

# weibo_mobile.py 示例:移动端评论接口的请求与解析 import requests import json # 移动端微博评论接口,oid 是博文 ID,page 控制翻页 url = "https://m.weibo.cn/api/comment/show" params = { "id": 2528838332939910, # 博文 ID,从分享链接里提取 "page": 1 } headers = { "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)", "Referer": "https://m.weibo.cn/" } resp = requests.get(url, params=params, headers=headers, timeout=10) data = resp.json() # 评论数据在 data 字段下的 comments 数组里 for comment in data.get("data", {}).get("comments", []): text = comment.get("text", "") like_count = comment.get("like_count", 0) print(text, like_count)

这里有个关键细节:id参数不是博文数字 ID,而是mid转换后的值,直接从网页 URL 里复制会 404。我一般用wb_id_to_mid这类工具做转换,或者用feed接口先拿mid再请求。timeout=10是防止某个请求卡死拖垮整个采集任务,批量跑的时候建议加个重试机制,502、503 在微博接口里很常见。

2.2 预处理:分词、去停用词和文本清洗

爬下来的评论不能直接喂给模型,先要清洗。微博文本里常见的噪音是「回复 @用户名:」、URL、表情符号,新闻评论则要处理引用串(「//@xx:」)。这些不去干净,后面做词典匹配的时候会把「不」「没」这类否定词误判成情感词的一部分。

import re import jieba def clean_text(text): # 去掉 @ 用户和 URL text = re.sub(r"回复 @.*?:", "", text) text = re.sub(r"//@.*?:", "", text) text = re.sub(r"http\S+", "", text) # 去掉 html 标签,微博接口返回的文本带 <a> 等标签 text = re.sub(r"<.*?>", "", text) return text.strip() def tokenize(text): # jieba 分词,去掉单个字和常见无意义词 words = jieba.lcut(text) stopwords = set() with open("stopwords.txt", "r", encoding="utf-8") as f: for line in f: stopwords.add(line.strip()) return [w for w in words if w not in stopwords and len(w) > 1]

jieba.lcut返回的是 list,比cut的生成器更适合后面做特征拼接。停用词表我建议自己维护一个,通用表会把「这个」「那个」去掉,但对「作者」「内容」这类词要不要留,取决于你后面做不做主题分析——如果做,这些词就得留下。

# 把清洗结果落盘成 TSV,方便后面特征工程读取 import pandas as pd df = pd.DataFrame({ "label": labels, "text": texts, "tokens": ["/".join(tokenize(clean_text(t))) for t in texts] }) df.to_csv("comments_cleaned.tsv", sep="\t", index=False, encoding="utf-8")

注意encoding="utf-8",Windows 下默认编码是 GBK,不指定的话到了别的机器一读就乱码。踩过这个坑的人不少。

2.3 数据量不够怎么办:先跑起来再谈优化

压缩包里自带的 Data 目录是「四阶段新浪新闻标题」,粗看应该有几万条级别。如果你的目标只是复现流程,这批数据够了。但要训练一个能上线的模型,这个量级偏少——情感分类任务,单类别最少也要五千条样本起步,否则随机森林这种模型很容易过拟合。常见做法是先用手头数据跑通 pipeline,再考虑扩充。

扩充的路径有三条:一是用Weibo.py再抓一批带表情的评论,二是把新闻评论按时间维度多抓几个月的,三是对已有样本做同义词替换做数据增强。第一条最实际,因为表情符号本身就带情感倾向,对词典匹配是天然的补充特征。

提示:爬虫脚本里的 Cookie 是写死的,过几天就失效。用之前先跑一次tryloadjson.py验证数据格式,再改自己账号的 Cookie。

3. 情感词典构建:从哈工大词典出发做领域扩充

3.1 词典结构:正负两个文件,符号表是关键

哈工大情感词典和知网情感词典是两种经典资源,前者以词条列表为主,后者除了词条还带极性标注。基础的用法是把两本词典合并去重后,得到「正负词典」。但词典匹配在微博场景下有个天然缺陷——网络新词完全覆盖不到。「绝绝子」「yyds」「破防」这些词在词典里是查不到的,需要用数据驱动的方式去兜底。

# 合并两本词典并去重 pos_words = set() neg_words = set() with open("hit_positive.txt", "r", encoding="utf-8") as f: for line in f: w = line.strip() if w and not w.startswith("#"): # 跳过注释行 pos_words.add(w) with open("how_net_negative.txt", "r", encoding="utf-8") as f: for line in f: w = line.strip() if w and not w.startswith("#"): neg_words.add(w) # 有些词典里带词性标注,比如 "高兴/a",要切掉 pos_words = {w.split("/")[0] for w in pos_words} neg_words = {w.split("/")[0] for w in neg_words} # 两边都有的词按否定优先,因为转折句里常出现 conflict = pos_words & neg_words pos_words -= conflict neg_words |= conflict # 冲突词归入负面

注意最后那步:冲突词归入负面。为什么?因为「高兴」的否定形式「不高兴」是负面,「不」「高」「兴」分开后词典依然匹配到「高兴」这个正面词,会把情感算反。与其这样,不如把能在两个词典都出现的词直接按负面处理,这是文本情感分析里一个非常反直觉但实用的处理方式。

3.2 领域扩充:统计新词,人工审核,写回词典

扩充的逻辑很简单:把训练语料里的高频词拉出来,去掉停用词,人工看哪些有情感倾向,加到词典里。第一轮扩充通常是网络用语,第二轮是领域词——比如你做的是新闻评论,就会有「甩锅」「不作为」「点赞」这类偏新闻场景的词。

from collections import Counter # 统计语料里的词频 word_counter = Counter() for tokens in tokenized_texts: word_counter.update(tokens) # 取词频 50 以上的候选词,去掉纯名词 candidates = [] for w, c in word_counter.most_common(500): if c >= 50 and len(w) >= 2: candidates.append(w) # 输出到文件人工审核 with open("candidate_words.txt", "w", encoding="utf-8") as f: f.write("\n".join(candidates))

候选词里会混入大量中性词,比如「问题」「情况」,这些不能进词典。人工审核这一步不能省,我之前图省事把高频词全丢进负面词典,结果「问题」这个词把所有含「问题」的评论全判成了负面,准确率掉了一大截。这是典型的「词典污染」,比词典覆盖不足还要致命。

3.3 否定词与程度副词的接续处理

这是词典方法最容易被低估的一环。纯粹的数正负词数量,会漏掉「不太高兴」这种句子——「高兴」是正面的,但「不太」把强度拉低了甚至反转了。标准做法是引入程度副词的权重表,和否定词的翻转逻辑。

def sentiment_score(tokens, pos_dict, neg_dict): score = 0.0 negate = False degree = 1.0 degree_dict = {"非常": 2.0, "很": 1.8, "比较": 1.5, "有点": 0.7} negate_words = {"不", "没", "没有", "不太", "不怎么"} for i, w in enumerate(tokens): if w in degree_dict: degree = degree_dict[w] elif w in negate_words: negate = True if w in pos_dict: score += degree * (1 if not negate else -1) negate = False degree = 1.0 elif w in neg_dict: score += degree * (-1 if not negate else 1) negate = False degree = 1.0 return score

这个逻辑里有个隐含缺陷:degree只作用于紧跟其后的一个情感词,如果「非常非常高兴」,第一个「非常」设了 1.8,第二个「非常」又设了 1.8,最终只乘一次。更精细的做法是乘起来,但那样会让长句子的得分爆炸。我一般会把 degree 限制在相邻三个词内有效,超出就重置。这类细节,压缩包自带的代码里没有,但你迟早会遇到。

4. 特征工程与模型训练:词典特征打底,机器学习的参数选型

4.1 三类特征怎么搭:词典得分 + TF-IDF + 情感词占比

词典得分是强特征,但只有它能表达的信息太单调。我的经验是把词典得分和高维稀疏特征拼在一起,让模型自己学组合。「TF-IDF + 情感词典得分」是性价比最高的配置,比单独用词袋特征平均能提升 3~5 个百分点的 F1。情感词占比包括正面词占比和负面词占比,这两个数值对朴素贝叶斯这类概率模型特别有效。

from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer(max_features=3000, ngram_range=(1, 2)) tfidf_matrix = vectorizer.fit_transform(texts) # 拼上词典特征 import numpy as np lexicon_features = np.array([ [sentiment_score(tokens, pos_dict, neg_dict), sum(1 for w in tokens if w in pos_dict) / len(tokens), sum(1 for w in tokens if w in neg_dict) / len(tokens)] for tokens in tokenized_texts ]) X = np.hstack([tfidf_matrix.toarray(), lexicon_features])

max_features=3000是经验值,超过这个数矩阵会变得极其稀疏,SVM 训练时间成倍增加但准确率提升有限。ngram_range=(1, 2)让模型能学到「高兴」和「不高兴」这种二元模式,这是词典方法做不到的。

4.2 三个模型的选型逻辑:朴素贝叶斯、SVM、随机森林

压缩包摘要里提到朴素贝叶斯、SVM、随机森林,这三个模型在这个任务上的表现差异值得细说。

朴素贝叶斯跑得最快,小样本下表现稳定,适合当基线。SVM 在高维稀疏特征上表现最好,尤其配合 RBF 核。随机森林对特征重要性有天然的解释性——它能告诉你哪些词对分类贡献最大,这在舆情分析的场景下比准确率更重要,因为你得向业务方解释模型为什么这么判。

from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.svm import SVC from sklearn.ensemble import RandomForestClassifier from sklearn.naive_bayes import MultinomialNB X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # 朴素贝叶斯:适合做基线的快速验证 nb = MultinomialNB() nb.fit(X_train, y_train) print("NB:", nb.score(X_test, y_test)) # SVM:RBF 核,C 控制误分类惩罚,默认 1.0 通常够用 svm = SVC(kernel="rbf", C=1.0, probability=True, random_state=42) svm.fit(X_train, y_train) print("SVM:", svm.score(X_test, y_test)) # 随机森林:n_estimators 多几个树更稳,但训练时间线性增长 rf = RandomForestClassifier(n_estimators=200, max_depth=10, random_state=42) rf.fit(X_train, y_train) print("RF:", rf.score(X_test, y_test))

用stratify=y做分层抽样很重要。情感分析的数据集通常正负比例不均衡,不做分层抽样,切出来的测试集可能全是正面样本,模型分数虚高但没法看真实泛化能力。

4.3 网格搜索参数设置:SVM 的 C 和随机森林的 depth

参数调优是另一个容易过度投入的地方。GridSearchCV暴力搜索在小数据集上没问题,但特征上万维之后每跑一轮都伤筋动骨。我的习惯是先固定一个模型跑出基线分数,再针对性地搜两三个关键参数。

param_grid = { "C": [0.1, 1, 10], "gamma": ["scale", "auto"] } grid = GridSearchCV(SVC(kernel="rbf"), param_grid, cv=5, scoring="f1_macro") grid.fit(X_train, y_train) print(grid.best_params_)

scoring="f1_macro"比默认的准确率更合理。情感分析里如果负面样本只占 20%,模型全猜正面也能有 80% 准确率,但 F1 会直接暴露问题。gamma="scale"是让 sklearn 根据特征标准差自动推定 gamma,比固定 0.1 更稳。

随机森林的max_depth我一般设 10~20,不设的话容易过拟合。n_estimators从 100 开始,用早停判断再加有没有意义——如果 150 棵和 200 棵的交叉验证分数差不到 0.5%,就停在 150,别为那 0.2% 的分数多等一半训练时间。

5. 避坑指南:爬虫反爬、编码、词典污染与样本失衡的实战教训

5.1 微博 Cookie 失效导致爬取数据全是登录跳转页

现象:Weibo.py跑了一段时间后,抓回来的评论全部变成了「请登录后查看」,解析结果为空。

原因:微博的 Cookie 有效期很短,通常在几小时到一天之间。脚本里如果用了写死的 Cookie,必然过期。另外,频繁请求会触发风控,即使 Cookie 没过期也会被临时限制。

解决:把 Cookie 配置单独抽出来,每次运行前手动更新;请求间隔不小于 2 秒,之前我按 0.5 秒的间隔跑,十分钟就被限制。初版可以用time.sleep(2)硬等,进阶做法是用requests.adapters.HTTPAdapter做重试,遇到 418 或 403 自动切换代理。

5.2 JSON 文件解析报错 UnicodeDecodeError

现象:tryloadjson.py读数据时报UnicodeDecodeError: 'gbk' codec can't decode byte,或者读进来全是乱码。

原因:Windows 默认的open()用 GBK 编码,但 JSON 文件是 UTF-8 写的,字节流对不上。

解决:所有读写文件的操作显式指定编码。

# 这是踩坑后的标准写法 with open("data.json", "r", encoding="utf-8") as f: data = json.load(f)

如果是网络请求返回的内容,resp.json()通常没问题,但如果先写了resp.text再手动解析,就要检查requests是否正确嗅探了编码。可以在resp.encoding = "utf-8"里强制指定。

5.3 词典污染:「问题」被误判为负面词

现象:模型对「存在问题」「发现问题」这类文本全部判负面,准确率暴跌。

原因:词典扩充时把「问题」这种高频词直接丢进了负面词典。但「问题」在新闻语境里更多是中性陈述,「发现问题」本质是中性表达。

解决:扩充词典时,人工审核环节必须有。最稳妥的做法是把候选词按词频排序,但只把情感倾向极其明显的词加入词典。存疑的词宁可不要——词典覆盖不足最多是漏判,词典污染会导致系统性误判。

5.4 训练集和测试集的情感分布不一致

现象:训练时 F1 很高,测试时直接下降 15 个百分点。

原因:数据是按时间阶段爬的,早期新闻评论以正面为主,后期负面增多,随机切分时如果不做分层抽样,测试集和训练集分布就会错位。

解决:前面提到的stratify=y是第一步,更严格的做法是按时段做交叉验证。

from sklearn.model_selection import TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=5) for train_idx, test_idx in tscv.split(texts): X_train, X_test = X[train_idx], X[test_idx] y_train, y_test = y[train_idx], y[test_idx] # 每个 fold 里模型都只用过去的数据训练,预测未来的数据

时间序列切分模拟的是真实上线场景——用昨天训练,预测今天。如果这都能保持分数稳定,模型才是真稳。

5.5 随机森林训练时间失控

现象:特征维度两万,随机森林 500 棵树,训练跑了半小时还没结束。

原因:max_features默认是sqrt(n_features),但n_estimators过大,且max_depth没有限制,每棵树都长到完全深度。

解决:n_estimators=200、max_depth=15起步,不要一来就上 500。另外在特征维度上做筛选,用SelectKBest从两万降到五千,训练时间能降 80% 而 F1 几乎不变。

from sklearn.feature_selection import SelectKBest, chi2 selector = SelectKBest(chi2, k=5000) X_selected = selector.fit_transform(X, y)

chi2是专门针对离散特征的检验方式,TF-IDF 值虽然不是严格计数,但经验上和卡方检验兼容得不错。

6. 验证模型:交叉验证和错误分析才是真正省时间的地方

跑完模型别急着看 accuracy,先把混淆矩阵打出来。很多「准确率 90%」的模型,拆开一看是把 95% 的负面样本全判成了正面——这在实际舆情监控里就意味着所有负面舆情都被漏掉了,比误报严重得多。

from sklearn.metrics import confusion_matrix, classification_report y_pred = svm.predict(X_test) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred))

classification_report里的macro avg和weighted avg分开看,差异大说明类别不均衡处理得不够好。真正的错误分析要落到具体样本上——把预测错的句子单独拉出来看,你会发现 80% 的错误集中在两类情况:一是反讽,「你说得都对」这种句子字面是正面,实际是负面的;二是省略主语的长句,情感词分布在从句里,词典匹配只抓到了主句。

我的做法是把错误样本做成表,按预测置信度排序,只看置信度最高的那些错判。SVM 的decision_function输出离 0 越远说明模型越自信,这种样本错了最值得研究——它们往往揭示的是特征层面的系统性缺陷,不是参数问题。

# 输出置信度最高的错误样本 proba = svm.decision_function(X_test) error_idx = np.where(y_pred != y_test)[0] error_ranked = sorted(error_idx, key=lambda i: abs(proba[i]), reverse=True) with open("error_analysis.txt", "w", encoding="utf-8") as f: for idx in error_ranked[:50]: f.write(f"{proba[idx]:.4f}\t{y_test[idx]}\t{y_pred[idx]}\t{texts[idx]}\n")

错误分析做完,你会面临一个选择:是继续调参,还是针对错误类型补特征。我的经验是,反讽类的错误靠调参永远解决不了,需要做转折词检测——判断句子里有没有「但」「然而」这类结构,如果有,后半句的情感权重翻倍。这一类规则特征加上之后,F1 的提升比调任何参数都明显。

从那以后我每次跑情感分析模型,都强制自己先走一遍交叉验证加错误分析的流程,调参排在最后。这套流程看着慢,实际是最快找到模型短板的路。希望这篇拆解能帮你在复现这个项目时少走一圈弯路,把时间花在真正有效的手段上。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 14:12:26

LIN一致性测试实战:从ISO 17987标准到CANoe工程搭建

做车载网络这一行&#xff0c;最容易被外行低估的一个环节就是一致性测试。整车厂和Tier 1之间谈项目&#xff0c;除了功能逻辑要对&#xff0c;其次就是挂在嘴边的“过了没有”——这个“过”&#xff0c;指的正是LIN一致性测试。说到LIN一致性测试标准&#xff0c;绕不开两张…

作者头像 李华
网站建设 2026/10/3 14:12:22

关键场景辨别驱动的两阶段鲁棒微网调度与CCG实现

接手园区微网调度项目那阵子&#xff0c;光伏装机占到全园区负荷的将近一半&#xff0c;天气好的时候发电量一路冲到上限&#xff0c;云层一厚又瞬间掉下去&#xff0c;柴油发电机启动需要时间&#xff0c;储能容量又有限。最初用确定性优化模型做日前调度&#xff0c;预测曲线…

作者头像 李华
网站建设 2026/10/3 14:12:22

中文分词自建词典生成:从语料到dic文件的完整流程

开篇先交代一下背景&#xff1a;这是“幽冥大陆”系列项目的第九十七期记录&#xff0c;对应整个仙盟体系里的“练气期”阶段。所谓练气期&#xff0c;说白了就是整个系统刚起步、能跑但不追求极致的第一版。这一期主要是在做分词服务的前置训练工作&#xff0c;核心产出是分词…

作者头像 李华
网站建设 2026/10/3 14:11:54

QNN实战:从模型转换到骁龙平台部署全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 14:11:01

9块9搞定7x24小时AI助理:DeepSeek API+调度推送全攻略

“大龙虾”这个外号&#xff0c;最近在中老年朋友圈和科技群里都挺常见&#xff0c;说的就是 DeepSeek。本来我一个搞运维的&#xff0c;平时最烦追热点&#xff0c;但这套东西我确实自己折腾了几天&#xff0c;效果超出预期&#xff1a;用接近9块9的开销&#xff0c;在公司旁边…

作者头像 李华
网站建设 2026/10/3 14:09:06

降AI率怎么降?论文AIGC检测原理与工具选择全攻略

1. 先搞懂“降AI率”到底降的是什么1.1 学校到底在查什么2026年毕业季&#xff0c;最让毕业生焦虑的已经不是查重率&#xff0c;而是“AI检测率”。不少学校在论文提交系统中新增了AIGC检测报告&#xff0c;查重合格还不算完&#xff0c;AI率超标直接进入“疑似代写”名单&…

作者头像 李华