尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

微博评论情感分析全链路:爬虫、LDA主题建模与情感打分实战

微博评论情感分析全链路:爬虫、LDA主题建模与情感打分实战 简介这份资源面向文本挖掘与舆情分析的学习者提供一套可直接运行的微博评论情感分析完整方案涵盖LDA主题分析与情感分类两大核心任务。包内共39个文件以23个Python脚本为主辅以7个Markdown说明、7个txt语料与停用词表、1个w2v词向量模型及1个xlsx数据表压缩包约16.16MB。内容按模块组织包括微博爬虫与数据清洗、分词与词向量训练、LDA主题建模及超参调优、主题余弦相似度计算、基于词典与API的情感分析、情感均值与正向比重统计、折线图与降维可视化等并附有正向与负向语料、自建词表、近义词表等辅助材料。已有2326人学习下载。读者可借此掌握从原始评论采集、预处理到主题发现与情感极性判定的完整流程理解如何将主题分布与情感标签关联进而用于公众情绪监测、市场研究与企业决策等场景。1. 微博评论情感分析项目从爬虫到LDA再到情感打分的完整链路微博评论区的数据量级和噪声密度做过舆情的人都有体会。一条热搜下面动辄几十万条评论里面有带节奏的、有刷表情包的、有纯转发的真正能用来做分析的干净文本可能不到三成。这个项目要解决的就是这条链路从微博评论采集开始经过清洗、分词、停用词过滤再分别走LDA主题分析和情感极性判断最后输出主题分布和情感趋势。项目包里包含爬虫脚本、数据清洗代码、LDA建模代码、情感分析API版和SDK版两套实现、词向量模型以及可视化脚本文件结构按功能模块分目录组织。适合做舆情监控、市场研究、社交媒体分析的从业者也适合想拿真实中文文本练手LDA和情感分析的学生。下面按实际跑通的顺序拆一遍。2. 数据采集与清洗爬虫脚本怎么选、清洗到什么程度算够2.1 三个爬虫脚本的适用场景项目里weibo-crawler目录下有三个采集脚本comment crawler.py、comments-crawler_random.py、comments-crawler_random仅针对去年的评论.py。名字就能看出来第一个是按固定目标抓取第二个是随机采样第三个加了时间过滤。我一般会这样选如果你要做某个特定话题的全量分析用comment crawler.py它按关键词或微博ID定向抓取如果只是想做趋势摸底、不需要全量用comments-crawler_random.py随机采样能省不少时间第三个脚本适合做同比分析比如对比今年和去年同一话题的评论情感变化。采集环节有几个参数需要关注。请求间隔不能太短微博对高频请求有风控建议每次请求间隔设在2到5秒之间具体看你的采集量。另外user information crawler.py是单独抓用户信息的如果你后续要做用户画像或者影响力加权这个脚本需要单独跑。2.2 数据清洗的四个关键步骤data cleaning.py是清洗主脚本但实际用的时候你会发现它需要配合几个辅助文件。清洗流程大致是这样的import re import pandas as pd # 读取原始评论数据 df pd.read_excel(新增.xlsx) # 第一步去除URL和提及 df[clean_text] df[comment_text].apply( lambda x: re.sub(rhttp\S|\S, , str(x)) ) # 第二步去除表情符号和特殊字符保留中文和基本标点 df[clean_text] df[clean_text].apply( lambda x: re.sub(r[^\u4e00-\u9fa5。、], , x) ) # 第三步去除重复评论转发带同样文案的情况很常见 df df.drop_duplicates(subset[clean_text]) # 第四步过滤过短评论少于4个字的通常没有分析价值 df df[df[clean_text].str.len() 4] df.to_excel(cleaned_comments.xlsx, indexFalse)这段代码的逻辑很直白微博评论里URL和提及占比很高不删掉会干扰后续分词表情符号在文本分析里通常没有语义贡献除非你做多模态分析重复评论在热搜话题下非常普遍一条热门转发可能带来几百条相同文案过短评论比如“哈哈哈”“转发微博”对主题建模是纯噪声。参数方面str.len() 4这个阈值可以调。如果你分析的是短评为主的话题可以降到3如果是长评为主可以提到6。没有固定标准取决于你的数据分布。2.3 分词与停用词处理分词处理.py配合停用词表.txt和LDA 自建词表.txt使用。停用词表是通用的自建词表是你针对微博场景补充的领域词。比如“转发”“微博”“热搜”这些词在通用停用词表里可能没有但在微博评论分析中它们几乎出现在每条文本里必须加进去。import jieba # 加载停用词 with open(停用词表.txt, r, encodingutf-8) as f: stopwords set([line.strip() for line in f]) # 加载自建词表确保领域词不被切碎 jieba.load_userdict(LDA 自建词表.txt) def tokenize(text): words jieba.lcut(text) return [w for w in words if w not in stopwords and len(w) 1] df[tokens] df[clean_text].apply(tokenize)jieba.load_userdict这行很关键。微博评论里经常出现网络新词、缩写、谐音梗默认词典切不准。比如“绝绝子”可能被切成“绝绝”“子”加载自建词表后就能正确识别。len(w) 1过滤单字因为单字在LDA里几乎没有区分度。3. LDA主题建模从超参调优到主题余弦相似度计算3.1 LDA模型的核心参数怎么定LDA.py和LDA超参.py是两个版本的实现后者多了超参数搜索。LDA最关键的三个参数是主题数K、alpha、beta。主题数K的选取没有绝对标准。常见做法是跑多个K值看困惑度perplexity曲线的拐点或者用主题一致性coherence score来评估。我一般会先在5到30之间以5为步长跑一轮找到大致范围后再细化。from gensim import corpora, models from gensim.models.coherencemodel import CoherenceModel # 构建词典和语料 dictionary corpora.Dictionary(df[tokens]) corpus [dictionary.doc2bow(tokens) for tokens in df[tokens]] # 遍历K值找最优主题数 for k in range(5, 31, 5): lda_model models.LdaModel( corpuscorpus, id2worddictionary, num_topicsk, alphaauto, # 自动学习alpha etaauto, # 自动学习beta passes15, # 训练轮数 iterations100, # 每轮迭代次数 random_state42 ) coherence CoherenceModel( modellda_model, textsdf[tokens], dictionarydictionary, coherencec_v ) print(fK{k}, Coherence{coherence.get_coherence():.4f})alphaauto和etaauto让模型自己学习先验参数省去手动调的麻烦。passes15是遍历语料的次数太小会欠拟合太大会过拟合且耗时。iterations100是每次遍历的迭代上限。random_state42固定随机种子保证结果可复现——这个在做对比实验时非常重要不固定种子每次跑出来的主题词都不一样。3.2 主题余弦相似度计算主题余弦相似度.py和w2v.model、word2vc.py配合使用。这个模块的作用是计算不同时间段或不同话题下LDA主题之间的相似度判断主题是否发生了漂移。import numpy as np from sklearn.metrics.pairwise import cosine_similarity # 假设有两个时间段训练出的LDA模型 # 提取每个主题的top30词及其概率 def get_topic_vectors(model, num_topics, topn30): vectors [] for i in range(num_topics): terms model.get_topic_terms(i, topntopn) vec np.zeros(len(model.id2word)) for term_id, prob in terms: vec[term_id] prob vectors.append(vec) return np.array(vectors) vecs_t1 get_topic_vectors(lda_t1, k) vecs_t2 get_topic_vectors(lda_t2, k) # 计算两组主题之间的余弦相似度矩阵 sim_matrix cosine_similarity(vecs_t1, vecs_t2) print(sim_matrix)逻辑说明把每个主题表示为一个高维向量维度是词典大小每个位置的值是该词在该主题下的概率。然后算两组主题向量的余弦相似度。如果某个主题在两个时间段内相似度很高比如0.85以上说明这个话题一直存在如果相似度很低说明话题发生了转移。w2v.model是预训练的词向量模型word2vc.py用来训练或加载词向量。词向量在这里的用途是计算词与词之间的语义相似度辅助判断两个主题虽然用词不同但是否在讲同一件事。比如“价格”和“售价”在词向量空间里距离很近即使LDA分到了不同主题通过词向量也能识别出关联。3.3 多日期降维与折线图绘制多日期降维.py和折线图绘制.py负责可视化。降维通常用PCA或t-SNE把高维主题向量降到二维方便画散点图观察主题分布。折线图则是把情感均值按日期画出来看趋势变化。from sklearn.decomposition import PCA import matplotlib.pyplot as plt # 对主题-文档矩阵做PCA降维 pca PCA(n_components2) topic_dist np.array([lda_model.get_document_topics(doc, minimum_probability0) for doc in corpus]) reduced pca.fit_transform(topic_dist) plt.figure(figsize(10, 6)) plt.scatter(reduced[:, 0], reduced[:, 1], alpha0.3, s5) plt.title(微博评论主题分布降维可视化) plt.savefig(topic_pca.png, dpi150)minimum_probability0确保每条文档都有完整的主题分布向量不会因为概率太低被截断。alpha0.3让散点半透明数据量大时能看出密度分布。dpi150保证输出图片清晰度够用。4. 情感分析两套实现API版和SDK版的差异与选择4.1 情感分析_API版的工作方式情感分析_API版.py走的是HTTP接口调用。你需要把评论文本发给远端服务拿回情感极性标签和置信度。这种方式的优点是模型在服务端你不用关心GPU、模型文件、依赖版本缺点是依赖网络有调用频率限制数据要出本地。import requests import json import time def sentiment_api(text): url https://your-sentiment-api-endpoint/analyze headers {Content-Type: application/json} payload {text: text} try: resp requests.post(url, headersheaders, datajson.dumps(payload), timeout5) result resp.json() return result.get(sentiment), result.get(confidence) except Exception as e: print(fAPI调用失败: {e}) return None, None # 批量处理时加延迟避免触发频率限制 results [] for text in df[clean_text]: sent, conf sentiment_api(text) results.append({text: text, sentiment: sent, confidence: conf}) time.sleep(0.5) # 控制调用频率timeout5是必须的不设超时的话网络卡住整个脚本就挂在那里。time.sleep(0.5)是控制调用频率具体间隔看你用的服务限制。批量处理时建议加异常捕获和重试逻辑不然跑了几千条中间断掉很麻烦。4.2 情感分析_SDK版的工作方式情感分析_SDK版.py用的是本地SDK模型文件在本地加载。优点是速度快、不依赖网络、数据不出本地缺点是需要配置环境、模型文件占空间、首次加载慢。from sdk_module import SentimentAnalyzer # 初始化分析器加载本地模型 analyzer SentimentAnalyzer(model_path./sentiment_model) def sentiment_local(text): result analyzer.predict(text) return result[label], result[score] # 批量处理 df[sentiment], df[score] zip( *df[clean_text].apply(sentiment_local) )SDK版适合数据量大、对延迟敏感的场景。初始化那一步比较耗时建议放在脚本开头只做一次不要每次调用都重新加载。4.3 两套方案的对比与选择维度API版SDK版部署复杂度低只需网络中需配置环境和模型文件单条延迟100-500ms10-50ms批量吞吐受频率限制取决于本地算力数据隐私文本需外发完全本地成本按调用量计费一次性部署成本模型更新服务端自动更新需手动替换模型文件我的建议是数据量在1万条以下、对隐私不敏感用API版快速出结果数据量在10万条以上、或者数据不能外发用SDK版。如果做长期舆情监控SDK版更划算。4.4 情感均值与正向比重计算Emotional mean.py和正向比重.py是统计脚本。情感均值是把每条评论的情感得分按日期聚合求平均正向比重是统计正面评论占总数的比例。import pandas as pd # 按日期聚合情感均值 df[date] pd.to_datetime(df[created_at]).dt.date daily_sentiment df.groupby(date)[score].mean().reset_index() daily_sentiment.columns [date, avg_sentiment] # 计算正向比重 df[is_positive] df[sentiment] positive daily_positive_ratio df.groupby(date)[is_positive].mean().reset_index() daily_positive_ratio.columns [date, positive_ratio] # 合并输出 result pd.merge(daily_sentiment, daily_positive_ratio, ondate) result.to_excel(daily_sentiment_report.xlsx, indexFalse)pd.to_datetime那一步需要你的日期字段格式统一。项目里有个修改日期格式.py就是干这个的如果原始数据的日期格式混乱先跑那个脚本统一格式不然to_datetime会报错。5. 避坑与排查跑这个项目最容易翻车的五个地方5.1 jieba分词把关键领域词切碎现象LDA跑出来的主题词里全是“转发”“微博”“哈哈”这种无意义词真正的话题词被切成了单字。原因默认词典对微博场景的网络用语覆盖不够加上停用词表没有针对微博补充。解决在LDA 自建词表.txt里补充你数据中出现频率高的领域词和网络用语格式是每行一个词可以带词频。同时检查停用词表.txt是否包含了“转发”“微博”“热搜”“哈哈”“表情”这类高频无意义词。我一般会先跑一遍词频统计把Top100里明显无意义的词手动加进停用词表。5.2 LDA每次跑出来的主题词不一样现象同样的数据和参数两次运行结果不同主题词排序和内容都有差异。原因LDA是概率模型初始化随机种子不同会导致收敛到不同的局部最优。解决在LdaModel里固定random_state参数。另外passes设大一些比如20到30让模型更充分收敛减少随机性带来的波动。如果做对比实验所有模型必须用同一个random_state。5.3 情感分析API调用返回空值或超时现象批量处理时部分请求返回None或者脚本卡住不动。原因网络波动、API频率限制、单条文本过长。解决加timeout参数和重试机制。对返回None的记录单独存到一个失败列表里最后统一重试。文本过长的话先截断到API支持的最大长度。另外注意有些API对特殊字符敏感清洗步骤要到位。5.4 Excel读写编码报错现象pd.read_excel或to_excel报编码错误或者中文变成乱码。原因Windows下Excel默认编码和Python不一致或者文件本身是CSV但用了Excel的后缀。解决统一用encodingutf-8-sig读写CSV。如果是Excel文件确保安装了openpyxl引擎。项目里excel转txt.py就是处理格式转换的如果遇到Excel读取问题可以先转成TXT再处理。5.5 词向量模型加载失败现象w2v.model加载时报维度不匹配或文件损坏。原因gensim版本和模型训练时的版本不一致或者模型文件在传输过程中损坏。解决先确认requirement.txt里的gensim版本用pip install gensim对应版本安装。如果模型文件损坏需要重新训练word2vc.py里有训练代码。训练词向量需要大量语料建议至少几十万条评论起步否则词向量质量很差。6. 进阶技巧用主题-情感交叉分析定位负面舆情源头跑通基础流程之后真正有价值的是把LDA主题和情感得分交叉起来看。单独看主题分布你知道大家在聊什么单独看情感趋势你知道大家情绪好不好但只有交叉起来你才知道哪个话题是负面情绪的集中爆发点。具体做法是对每条评论同时拿到它的主题分布向量和情感得分。然后按主题分组算每个主题下的平均情感得分和负面评论占比。import numpy as np import pandas as pd # 获取每条评论的主题分布 topic_distributions [] for doc in corpus: dist lda_model.get_document_topics(doc, minimum_probability0) vec np.zeros(k) for topic_id, prob in dist: vec[topic_id] prob topic_distributions.append(vec) topic_df pd.DataFrame(topic_distributions, columns[ftopic_{i} for i in range(k)]) df pd.concat([df.reset_index(dropTrue), topic_df], axis1) # 对每条评论取概率最高的主题作为主主题 df[main_topic] topic_df.idxmax(axis1) # 按主主题分组计算情感均值和负面占比 df[is_negative] df[sentiment] negative topic_sentiment df.groupby(main_topic).agg( avg_score(score, mean), negative_ratio(is_negative, mean), comment_count(clean_text, count) ).reset_index() # 按负面占比降序排列找出最负面的主题 topic_sentiment topic_sentiment.sort_values(negative_ratio, ascendingFalse) print(topic_sentiment)这段代码的关键在idxmax(axis1)它把每条评论归属到概率最高的主题。实际用的时候你会发现有些评论主题分布很分散最高概率可能只有0.3这种评论的归属就不太可靠。我一般会加一个阈值比如最高概率低于0.5的评论标记为“主题不明确”不纳入分组统计。topic_sentiment输出后负面占比最高的那个主题就是你需要重点关注的。再结合LDA.py输出的主题词你就能知道具体是什么话题引发了负面情绪。比如主题词是“价格”“涨价”“贵”“不值”负面占比0.72那结论就很明确了。还有一个技巧是看时间维度上的交叉。把main_topic和date做分组看某个主题的负面占比是不是在特定日期突然升高。如果是结合当天的微博事件就能定位到具体的舆情触发点。# 按日期和主题交叉分析 daily_topic_sentiment df.groupby([date, main_topic]).agg( avg_score(score, mean), count(clean_text, count) ).reset_index() # 找出负面情感突然下降的日期-主题组合 pivot daily_topic_sentiment.pivot(indexdate, columnsmain_topic, valuesavg_score) # 计算每日环比变化 pivot_diff pivot.diff() # 找出单日下降超过0.3的主题 alert pivot_diff[pivot_diff -0.3].stack().reset_index() print(alert)diff()算的是环比变化 -0.3是我常用的告警阈值。这个阈值可以根据你的数据波动情况调整波动大的话调到-0.5波动小的话-0.2就够。stack()把宽表变成长表方便筛选。从那以后我每次跑完LDA和情感分析都会强制走一遍主题-情感交叉表不只看总体情感均值。总体均值会掩盖很多问题——可能整体情感是正的但某个特定主题已经炸了。希望帮到你。本文还有配套的精品资源点击获取
返回列表