我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

Python电商评论情感分析实战:从数据清洗到Streamlit可视化

Python电商评论情感分析实战:从数据清洗到Streamlit可视化 简介一套面向电商数据分析、自然语言处理及课程设计场景的Python买家评论情感分析项目包含完整源码、模型文件、多类评论数据集和详细代码注释可支撑毕业设计、课程作业、答辩演示或入门实践。项目所有代码均测试运行成功曾获课设答辩平均分94.5分并配有Streamlit可视化交互界面运行后能够查看正负面评论分布、情感分数和关键词排序演示效果直观。资源压缩包共1379个文件大小约53.95MB其中以py源码脚本、csv评论数据、txt说明文本、html可视化页面为主还有ipynb分析Notebook、模型权重文件及少量运行所需依赖组件。整体目录按数据、模型、页面入口和文档等划分结构清晰评论数据覆盖多款热水器的历年评论及正面评价CSV可用于分析产品卖点、比较品牌售后、观察评论随时间变化等。目前已有1120人学习下载。新手可快速跑通情感分析全流程有基础的同学也能在现有模块上替换数据集或扩展词云、主题分类、深度学习模型等进阶功能作为课程设计或实际项目参考很实用。1. 基于Python的电商买家评论数据情感分析这个课设包先解决“数据从哪来”做情感分析课设或课程大作业时最常翻车的不是模型代码而是缺一份像样的数据。这个基于Python的电商买家评论数据情感分析资源把爬好的评论数据集、跑通的情感分析模型、能直接演示的Streamlit界面以及带注释的源码打包在一起属于典型的“课程作业全家桶”。它适合正在做课程设计、毕业设计或项目初期立项演示的在校生也适合想快速看完情感分析全流程的入门者。覆盖CSV读取、jieba分词、SnowNLP打分、LDA主题建模、词云与pyLDAvis可视化真正把数据清洗、情感评分、结果展示这一条链路完整串了起来。2. 拆开数据集看结构三条评论CSV到底该怎么分工拿到压缩包先别急着跑命令第一批要搞清楚的是数据文件之间的关系。这套资源里CSV不止一个它们的规模、字段和用途都不同混用会导致后面实验数据不一致答辩时被问到“你的训练集和测试集怎么分的”就容易卡壳。2.1 资源里的数据文件字段、规模与使用场景原压缩包内的文件名涉及具体品牌名统一做脱敏后核心数据文件大致如下文件脱敏后大致规模典型用途spider_comments.csv小样本评论快速验证清洗、分词语料管道spider_comments_34000.csv34000条评论正式实验、训练集与测试集划分某品牌_评论_2019.csv / 某品牌_评论_2020.csv年度评论评论内容随时间变化、舆论趋势分析某品牌_正面.csv正面评论子集正面语料筛选、情感校验集字段结构是爬虫类CSV的常见形态评论内容、评分星级、评论时间、购买型号、用户昵称、点赞数、追评内容、地区等。其中评分和评论时间最关键评分用来构造情感标签评论时间可以按月度做情感均值聚合。我拿到这类数据第一件事是看分布评分是否为1到5的整数评论时间是否覆盖完整年份评论内容是否存在大量重复。原正文里专门提到“挑选合适的商品好差评都多评论量也大”这是有道理的。如果某个品牌全是5星好评情感分析直接没有区分度课程作业的说服力很差。所以2019、2020两个年度文件正好可以完成“同一类商品不同年份情感对比”甚至“售后口碑优劣对比”这是答辩时比较加分的角度。2.2 清洗脚本把34000条原始评论变成可分析语料爬虫拿到的原始评论基本不能直接用。里面掺杂HTML标签、表情符号、用户、短评“好评”两个字、重复评论。清洗这一步直接决定LDA主题能不能聚成有意义的类。import pandas as pd import re def load_comments(path: str) - pd.DataFrame: # 优先尝试UTF-8失败后回退GBK兼容Windows导出的CSV try: df pd.read_csv(path, encodingutf-8-sig) except UnicodeDecodeError: df pd.read_csv(path, encodinggbk) return df def clean_comment(text: str) - str: if not isinstance(text, str): return text re.sub(r[^], , text) # 去掉HTML标签 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9\s], , text) # 保留中文英文数字 text re.sub(r\s, , text).strip() return text df load_comments(spider_comments_34000.csv) df[clean] df[comment].apply(clean_comment) df df.drop_duplicates(subset[clean]) df df[df[clean].str.len() 10] print(f清洗后剩余 {len(df)} 条)这段清洗里两个点要重点说明。第一encoding用“utf-8-sig”而不是“utf-8”是因为很多爬虫CSV带BOM头直接用utf-8读会把第一列名解析出“\ufeff”字符后面groupby、merge全部对不上。第二正则里只保留中文、英文、数字和空白看起来粗暴但对短评足够表情符号和特殊字符会干扰分词效果。长度阈值10的作用是过滤掉“好评”“不错”“还可以”这种无信息量短评如果做的是品牌对比这个阈值还可以往上调到15。清洗后必须做人工抽检随机取50条看是否有整行空文本、是否误删重要标点。很多课设在这一步直接略过结果LDA主题全是“东西”“感觉”“没有”这类废词。2.3 构造情感标签用评分对齐文本处理“好评骂人”怪象有评分字段就能构造监督信号。常见的做法是把4到5分映射为正面1到2分映射为负面3分作为中性丢弃或单独成类。def map_score_to_label(score: float) - int: if score 4: return 1 elif score 2: return 0 else: return -1 df[label] df[score].apply(map_score_to_label)但这里有一个原项目正文里反复强调的坑评论分数和评论内容不吻合。有人打了5星却写“安装师傅还行但热水器用了三天就漏水”也有人打了1星但内容是吐槽物流而非商品本身。如果直接拿评分当标签训练分类器噪声会非常大。我的处理方法是先用SnowNLP初筛找出高评分但情感分很低、低评分但情感分很高的样本单独抽出来人工看。from snownlp import SnowNLP def check_conflict(row): sentiment SnowNLP(row[clean]).sentiments if row[label] 1 and sentiment 0.4: return True # 高分但情感很低需要人工复核 if row[label] 0 and sentiment 0.7: return True # 低分但情感很高 return False df[conflict] df.apply(check_conflict, axis1) conflict_df df[df[conflict]] print(f疑似冲突样本 {len(conflict_df)} 条)这里的0.4和0.7两个阈值不是玄学我是按“明显反向才会被标出来”的标准定的。如果阈值太紧比如0.45和0.65会把很多正常评论误判为冲突太松则抽不出问题样本。实际使用可以先跑一遍看冲突比例再决定要不要调整。这批冲突样本是后面做模型优化时的重点数据别删。3. 情感分析与主题建模评论内容是怎么被算出来的数据清洗完接下来就是情感分析的核心链路。这一章的代码串起了分词、停用词过滤、LDA主题模型和情感打分四个环节是整份源码里复用价值最高的部分。3.1 分词与停用词为什么jieba分词前要先把自定义词典塞进去很多新手直接用jieba.lcut切完就开始算情感结果发现“热水器”被切成“热水”“器”“美的”这种品牌词变成单字。解决方法是加载自定义词典。原资源里的dict目录就是干这个用的。import jieba def load_dicts(): # 每行一个领域词优先保证整体被切出 jieba.load_userdict(dict/brand.txt) # 品牌、型号 jieba.load_userdict(dict/product.txt) # 产品特征词热水器、安装、售后 STOP_WORDS set() with open(dict/stopwords_zh.txt, r, encodingutf-8) as f: for line in f: word line.strip() if word: STOP_WORDS.add(word) def tokenize(text: str) - list: return [w for w in jieba.lcut(text) if w not in STOP_WORDS and len(w) 1]自定义词典的路子是每行一个词jieba会优先把它们切成整体。品牌词、型号、行业术语都适合放进去。停用词表建议在通用中文停用词表基础上补上“客服”“师傅”“安装”“物流”这类场景词——注意这些词不一定都要过滤如果分析目标就是售后服务反而要把“售后”“师傅”保留下来作为主题线索。分词后len(w) 1这个过滤条件是用来去掉单字语气词的但像“水”“热”“电”这种在特定语境下是有实义的单字如果你发现主题词里缺了它们可以单独保留。3.2 LDA主题模型找出评论里真正被高频讨论的卖点jieba切完词后下一步是LDA主题建模。这里不直接使用模型预测情感而是用主题分布判断评论在聊什么外形外观、加热速度、售后服务还是物流体验。原项目正文里“评论的具体关键词外形外观等”说的就是这个。from gensim.corpora import Dictionary from gensim.models import LdaModel texts [tokenize(doc) for doc in df[clean].tolist()] dictionary Dictionary(texts) # 过滤低频词和超高频词no_below低于5在该文档集里可忽略 dictionary.filter_extremes(no_below5, no_above0.5) corpus [dictionary.doc2bow(t) for t in texts] lda LdaModel( corpus, num_topics6, id2worddictionary, passes20, random_state42 ) for topic_id in range(lda.num_topics): words lda.show_topic(topic_id, topn10) print(topic_id, [w for w, prob in words])dictionary.filter_extremes这一步非常关键。no_below5表示词频低于5的直接丢掉no_above0.5表示出现在超过50%文档里的词也丢掉否则“热水器”“东西”“感觉”这种全数据集都出现的词会把主题挤占。num_topics6是初始值不一定是最优解。主题数的确定我在后面第6章会讲一个用coherence曲线选择的方法。passes20控制迭代次数太影响速度追求答辩效果可以保持10到20之间。random_state固定成42是为了保证每次运行主题一样否则两次生成的主题顺序可能完全不同。主题模型跑完后人工看一眼主题词是否合理。比如某个主题出现“外观”“大气”“颜色”“面板”就可以命名“外形外观”出现“漏水”“噪音”“维修”“售后”的可以命名“质量问题”。如果主题词之间毫无关联大概率是数据清洗不到位或者主题数设置不对。3.3 情感打分与score对齐把0到1的情感分数翻译成评论好坏SnowNLP是这套资源里默认的情感打分母子每个句子会输出一个0到1的sentiments值越接近1越正向。但它有两个明显问题一是对电商家电类文本不够敏感二是它只认句子整体不理会“但是”后面的转折。原项目正文里专门把“不用snownlp”列入扩展点说明这个包只适合当保底方案。我的做法是在SnowNLP输出基础上叠加评分校准让最终情感分数同时参考模型判断和用户实际打分。def sentiment_adjusted(text: str, score: float) - float: s SnowNLP(text).sentiments # 评分5分制转0到14分以上按0.8起步2分以下按0.2封顶 score_norm (score - 1) / 4.0 return 0.6 * s 0.4 * score_norm这样处理以后高评分但文本表达含糊的评论会偏正向低评分但文本看起来正常的评论会被压向负向。0.6和0.4的权重可以根据效果回退调整如果你想更相信模型判断就把0.6提到0.7但建议保留至少三成的评分权重用来兜住SnowNLP对家电领域词的误判。打分完成后整个情感分析链路就跑通了文本进情感分数和主题分布出。但要把这些结果变成能让老师眼前一亮的演示还得靠Streamlit那套可视化界面。4. Streamlit可视化把分析结果变成能现场演示的交互页面课程设计答辩最怕的是对着黑框讲代码评委看不到结果。这套资源用Streamlit搭了一个图形界面把词云、情感趋势、LDA主题都搬进了浏览器。运行方式原包写得很清楚。4.1 启动命令与代码目录结构先看启动命令streamlit run ./Comment_analysis/Streamlit/streamlitEXP.py这个命令要求你在解压后的根目录执行路径不能错。如果直接双击streamlitEXP.py一般会闪退或报“streamlit未找到”。原因是pip安装的streamlit可执行脚本不一定在当前shell的PATH里用命令行运行最稳。Streamlit应用的核心目录结构通常是Comment_analysis/ ├─ Streamlit/ │ └─ streamlitEXP.py ├─ model/ ├─ data/ ├─ dict/ └─ utils/streamlitEXP.py是入口里面按页面分区数据加载区、情感分布区、主题模型区、词云区。答辩时可以直接在页面上切换不同品牌、不同年份的数据比PPT里贴静态图灵活得多。不过要留意一点第一次启动时会下载一些前端资源如果网络环境受限页面会卡在Loading状态这个不是代码问题。4.2 词云与情感趋势高频词和情感均值怎么画在一张页面上词云是最容易出效果的可视化。WordCloud生成词云时中文必须指定字体路径否则出图全是方框。这是中文词云最常见的坑没有之一。from wordcloud import WordCloud wc WordCloud( font_pathSimHei.ttf, # 中文字体必须指定否则全是方块 width800, height600, max_words200, background_colorwhite ).generate( .join(df[clean].tolist()))font_path参数在Windows下可以用“SimHei.ttf”在Linux服务器上需要用系统中文字体文件路径比如“/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc”找不到字体时先运行fc-list查系统有什么中文字体。max_words控制在200左右词太多会糊成一片词太少看不出规律。情感趋势图用Streamlit内置的折线图就能画import pandas as pd import streamlit as st df[time] pd.to_datetime(df[time]) trend df.groupby(df[time].dt.to_period(M))[sentiment_score].mean() st.line_chart(trend.astype(float))这里的关键是先把time列转成datetime类型再按月份聚合。如果原始时间字段是“2020-03-15 10:22:33”这种字符串不转换直接groupby会按天分折线会非常碎看不出趋势。把2019年和2020年两个文件分别跑一遍放在同一个图表里还能看出同一品牌评价随时间的变化。这正是原正文里“同一个热水器的评论内容随时间变化”的落地方式。4.3 pyLDAvis把主题模型变成可拖拽的网页LDA主题模型本身是抽象的主题数和关键词列表很难让评委快速理解。pyLDAvis能把每个主题画成气泡图气泡大小代表主题占比拖到气泡上能看到每个主题下的关键词。这段可视化是整套作业里技术加分最明显的地方。import pyLDAvis.gensim_models vis pyLDAvis.gensim_models.prepare(lda, corpus, dictionary) html_content pyLDAvis.gensim_models.get_html(vis)get_html拿到的是完整HTML字符串得到的html_content可以直接嵌入Streamlit页面。这里要注意pyLDAvis生成的HTML包含脚本和样式资源传统st.markdown会直接把它当作纯文本显示。正确的嵌入方式是用streamlit.components.v1.html来渲染。import streamlit as st from streamlit.components.v1 import html st.title(LDA主题交互式可视化) html(html_content, height700)嵌入后页面会多一个可拖拽的气泡图主题距离远近、主题关键词分布一目了然。这个组件在答辩时基本百试百灵但如果你发现页面空白问题很可能出在height值太小pyLDAvis的完整高度至少在600到800像素之间400像素以下会把底部关键词列表截掉。5. 课程作业级避坑从虚拟环境到中文乱码的五个实战记录这套资源代码能跑通但换一台机器很容易翻车。下面五条是按真实踩坑频率排的每一条都是我处理课程作业类包时的高频问题。5.1 虚拟环境激活失败activate.bat能点开却进不了环境现象解压后进入目录命令行执行venv\Scripts\activate提示找不到Python或激活后python命令还是指向系统路径。原因压缩包里的虚拟环境是打包者在自己电脑上创建的venv内部配置记录的是创建时的绝对路径。整个目录被拷贝、解压到别的路径后pyvenv.cfg和sysconfig.cfg里的路径就失效了。解决不要试图修复旧环境直接删除venv目录重建。python -m venv venv venv\Scripts\activate pip install -r requirements.txt如果机器上没有Python环境就先装Python 3.8或3.9装完再执行上面三条。激活后执行python --version确认进入的是venv环境。5.2 CSV读取乱码load完打印全是锟斤拷现象用pandas读取CSV后列名正常但评论内容全是乱码。原因爬虫导出的CSV编码不统一有些是带BOM的UTF-8有些其实是GBK直接用默认utf-8读会乱码。解决写一个自动探测编码的函数按优先级尝试多种编码。def load_csv_with_fallback(path: str) - pd.DataFrame: for enc in [utf-8-sig, gbk, gb18030]: try: return pd.read_csv(path, encodingenc) except UnicodeDecodeError: continue raise ValueError(f无法读取文件: {path})gb18030是GBK的超集能兼容很多特殊字符。注意编码探测顺序不能把gbk放前面否则带有BOM的UTF-8文件可能被误读成乱码。5.3 SnowNLP把“好评”判成负面现象输入“外观很大气师傅安装也很快”sentiments输出只有0.35。原因SnowNLP的训练语料以酒店、餐饮评论为主对家电类产品的“外观大气”“师傅专业”“安装及时”这类场景词缺少正向权重。解决在SnowNLP基础上叠加一个自定义情感词规则。把原正文里提到的外形外观、售后服务等关键词直接做成情感词表。POS_WORDS {大气, 划算, 值得, 专业, 很快, 售后好, 安装规范} NEG_WORDS {漏水, 异响, 售后差, 有噪音, 加热慢, 客服不理} def rule_sentiment(text: str) - float: score 0.5 for w in POS_WORDS: if w in text: score 0.05 for w in NEG_WORDS: if w in text: score - 0.08 return max(0.0, min(1.0, score))最终情感分数在SnowNLP和规则分数之间取加权平均而不是直接用SnowNLP结果。这条规则几乎对所有电商短评数据集都有效效果提升非常直接。5.4 pyLDAvis在Streamlit页面里白屏现象用st.components.v1.html嵌入pyLDAvis生成的HTML后页面加载出来是空白或只有标题。原因pyLDAvis.get_html返回的是完整HTML文档而且它内部依赖的脚本和样式是内联的。如果直接截取字符串片段嵌入或者height设置过低浏览器渲染时会把关键脚本裁掉。解决先保存成临时HTML文件再读回字符串嵌入同时给足高度。import pyLDAvis import pyLDAvis.gensim_models vis pyLDAvis.gensim_models.prepare(lda, corpus, dictionary) pyLDAvis.save_html(vis, vis_tmp.html) with open(vis_tmp.html, r, encodingutf-8) as f: html_content f.read() html(html_content, height800)保存再读取这一步看起来多余但它能保证生成的HTML完整落盘避免在内存中被Streamlit组件截断。答辩机上如果没有Chrome建议提前用Edge测试一次兼容性。5.5 数据清洗后索引断裂页面筛选和图表对不上现象Streamlit页面里按品牌或年份筛选后图表行数正确但标签错位或者LDA训练时用的数据和页面展示的评论不是同一条。原因很多人在清洗时做了drop_duplicates和长度过滤但没有reset_index。过滤后的DataFrame索引还保留原位置后续用loc按索引取值时会把清洗前的旧数据带出来。解决清洗链路最后强制重置索引。df_clean df_clean.drop_duplicates(subset[clean]) df_clean df_clean[df_clean[clean].str.len() 10] df_clean df_clean.reset_index(dropTrue)加一行reset_index(dropTrue)所有下游分析就统一了。这个问题排查起来很隐蔽因为大部分统计结果看起来都“差不多”只有按索引对应评论内容时会发现错位。6. 进阶技巧把准确率从0.7提到0.85的四个调整点基础链路跑通以后如果还想在答辩里拿出“算法优化”的亮点不用上深度学习也能明显提升效果。我习惯把这类课程作业分成两档能跑通是及格能说出参数怎么调、为什么调才是优秀。第一个调整点是自定义情感词表的叠加。直接用SnowNLP常见准确率在0.7上下叠加规则打分后能到0.8附近。规则词表不需要大针对这个家电评论场景准备30到50个词就够关键是覆盖“售后、安装、外观、漏水、噪音”这些领域词。final_sentiment 0.6 * snownlp_sentiment 0.4 * rule_sentiment第二个调整点是用主题一致性coherence确定LDA主题数而不是拍脑袋定6。跑一个循环从3个主题试到10个主题看一致性评分曲线取峰值或第一个明显拐点。from gensim.models.coherencemodel import CoherenceModel coherence_scores [] for k in range(3, 11): lda_k LdaModel(corpus, num_topicsk, id2worddictionary, passes20, random_state42) cm CoherenceModel(modellda_k, textstexts, dictionarydictionary, coherencec_v) coherence_scores.append(cm.get_coherence())曲线出来后选择分数最高的主题数。如果分数普遍低于0.4先回去检查停用词表和分词语料不要只调主题数。第三个调整点是装饰器统一计时与日志。原资源里明确提到了装饰器我把它用在所有耗时函数上清洗、分词、LDA训练、情感批量打分各跑一轮就能知道瓶颈在哪。这既是代码规范也是答辩时能说出来的工程化细节。import time from functools import wraps def log_time(func): wraps(func) def wrapper(*args, **kwargs): start time.time() result func(*args, **kwargs) print(f[{func.__name__}] 耗时 {time.time() - start:.2f}s) return result return wrapper第四个调整点才是真正拉开差距的把每个句子的情感结果按“但是”“不过”“然而”切分分别计算转折前后的情感分数。电商评论里大量存在“外观不错但是售后很差”SnowNLP把整句混在一起算会互相中和。遇到含转折词的句子我一般切分成子句后赋予后半部分更高的权重因为用户真正想吐槽的点往往在后面。从那以后我每次拿到情感分析类的课程作业包都强制自己先走一遍数据清洗、打分冲突检测和主题一致性评估同一个数据集跑两遍对比结果再往下做界面不再急着启动Streamlit。避坑清单里那几条看着简单但每一处都是实实在在影响最终分数的地方。希望帮到你。本文还有配套的精品资源点击获取
返回列表