ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python电商评论情感分析实战:从爬虫到可视化完整指南

Python电商评论情感分析实战:从爬虫到可视化完整指南 简介情感分析是自然语言处理的重要分支旨在让计算机自动识别文本中的主观情绪倾向。在电商场景中产品评论包含大量用户对质量、物流、客服等方面的真实反馈通过中文分词、停用词过滤与特征提取等技术可以将非结构化文本转化为可量化指标。Python生态提供了jieba、SnowNLP等成熟工具结合自定义情感词典与机器学习方法可有效提升特定领域的情感判断准确率。情感分析技术不仅服务于商家口碑洞察与用户消费决策还可延伸至舆情监控、市场调研等场景。本文基于Python电商产品评论情感分析实训项目系统讲解从数据爬取、预处理、情感建模到可视化展示的完整链路并重点介绍SnowNLP与词典加权融合的优化策略。1. 这个实训项目到底在做什么需求拆解与整体设计拿到“基于Python的电商产品评论数据情感分析”这个题目时大多数人第一反应是“又是课程设计老套路”。但实际做完拿到95分以上之后我发现这个题目能拿高分的关键恰恰不是算法多复杂而是有没有把“产品评论情感分析”这条链路完整打通——从数据获取、清洗、分析建模到结果可视化每一步都经得起问、经得起看。先说清楚这个项目解决的是什么问题。电商平台的商品评论里藏着用户对产品的真实态度质量好不好、物流快不快、客服态度怎么样、有没有色差、尺码偏不偏。这些信息是文本形式人工逐条看效率太低而且带有主观情绪倾向。情感分析要做的事情就是让计算机自动判断“这条评论是好评、中评还是差评”更进一步可以拆解出用户具体在夸什么、骂什么把散落的文本变成可量化的数据指标帮助商家理解产品口碑。从实训答辩角度看这个项目考察的不只是“你会不会调用现成库”而是你对以下问题的理解程度文本数据是怎么来的爬虫获取评论有哪些坑中文文本和英文文本在预处理上有哪些本质区别词向量、TF-IDF这些概念在情感分析里是干什么用的基于情感词典和基于机器学习的方法各自适用什么场景分析结果怎么展示才能让人一眼看懂我接到的任务是完成一份可以“直接交付”的实训大作业包括完整源码和文档说明。下面我会按照实际项目推进的顺序把整个实现过程、核心代码、踩坑记录以及答辩准备的思路全部展开保证你照着做能跑通也能讲明白。先看项目整体技术栈这也是答辩时最容易被打分老师追问的部分技术模块具体选型用途说明开发语言Python 3.8生态完整文本处理库丰富数据爬取requests BeautifulSoup获取京东/淘宝商品评论数据存储CSV Excel双格式便于后续分析和展示中文分词jieba中文文本的基础切割情感分析SnowNLP 自定义词典解决通用模型准确率不足问题可视化matplotlib WordCloud情感分布图和评论词云Web展示Flask ECharts可选加分项将分析结果页面化展示目录结构建议这样设计既清晰又在答辩时有条理sentiment-analysis/ ├── data/ │ ├── raw/ # 爬取的原始评论数据 │ ├── cleaned/ # 清洗后的数据 │ └── processed/ # 带有情感得分的结果数据 ├── crawler/ │ ├── jd_crawler.py # 京东评论爬虫 │ └── taobao_crawler.py # 淘宝评论爬虫可选 ├── analysis/ │ ├── preprocess.py # 数据清洗与分词 │ ├── sentiment.py # 情感分析核心逻辑 │ └── visualization.py # 可视化输出 ├── web/ │ ├── app.py # Flask展示系统 │ └── templates/ # 前端页面 ├── docs/ │ └── 实验报告.md └── main.py # 主程序入口这里有个心得不要图省事把所有代码堆在一个文件里。模块化设计不但方便调试更重要的是在实训报告里“系统设计”这一章节有素材可写答辩时也显得思路清晰。我当时光目录设计这一点就在设计文档部分拿到了不错的评价。2. 数据从哪来电商评论爬虫的完整实现与反爬应对2.1 为什么选择京东评论接口实训项目里最耗时间的一步其实是数据获取。很多同学在这里就卡住了所以我仔细说一下我当时是怎么解决的。电商平台里淘宝的评论接口反爬策略比较严格需要处理cookie、签名、滑块验证等一堆问题对学生项目来说成本太高。京东相对友好而且商品评论的接口结构清晰返回的是标准JSON数据解析起来非常方便。京东商品评论的接口地址格式如下https://club.jd.com/comment/productPageComments.action? callbackfetchJSON_comment98vv466 productId100012043978 score0 sortType5 page0 pageSize10 isShadowSku0 fold1各参数含义productId是商品ID可以从商品详情页URL中提取score0表示获取全部评分评论1差评、2中评、3好评可以单独过滤sortType5表示按时间排序推荐使用能拿到最新评论page是页码从0开始pageSize是每页数量固定为10超过不生效。看一下返回数据的结构{ comments: [ { id: 123456789, content: 手机外观漂亮运行流畅拍照效果很满意, score: 5, creationTime: 2023-06-15 10:32:00, nickname: j***d, productColor: 黑色, productSize: 12GB256GB } ], maxPage: 100, productScoreSummary: { good: 0.96, general: 0.02, poor: 0.02 } }2.2 爬虫核心代码与抓取策略我当时写了一个比较完整的爬虫核心部分做了两件特别重要的事随机User-Agent和请求延迟控制。这两件事直接决定了你能否稳定抓取数据而不被封。import requests import json import time import random import pandas as pd from urllib.parse import quote class JDCrawler: def __init__(self, product_id, max_pages50): self.product_id product_id self.max_pages max_pages self.base_url https://club.jd.com/comment/productPageComments.action self.headers { User-Agent: self._random_ua(), Referer: fhttps://item.jd.com/{product_id}.html, Accept: application/json, text/plain, */* } self.user_agents [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36, Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ] def _random_ua(self): return random.choice(self.user_agents) Chrome/120.0.0.0 Safari/537.36 def _fetch_page(self, page): params { productId: self.product_id, score: 0, sortType: 5, page: page, pageSize: 10, isShadowSku: 0, fold: 1 } try: resp requests.get( self.base_url, paramsparams, headersself.headers, timeout10 ) resp.encoding utf-8 if resp.status_code 200 and resp.text.strip(): return resp.json() return None except Exception as e: print(f第{page}页抓取失败: {e}) return None def crawl(self): all_comments [] for page in range(self.max_pages): data self._fetch_page(page) if data and data.get(comments): for item in data[comments]: all_comments.append({ 商品ID: self.product_id, 用户名: item.get(nickname, ), 评分: item.get(score, 0), 评论内容: item.get(content, ), 评论时间: item.get(creationTime, ), 商品颜色: item.get(productColor, ), 商品版本: item.get(productSize, ) }) print(f已抓取第{page1}页累计{len(all_comments)}条) # 控制请求频率防止被封IP time.sleep(random.uniform(1.5, 3.5)) else: print(没有更多评论或接口返回异常停止抓取) break return pd.DataFrame(all_comments)几个容易踩坑的细节京东评论接口返回的是JSON不是JSONP。如果你在浏览器地址栏直接访问会发现返回的是fetchJSON_comment98vv466({...})这种带回调的格式但用requests访问不加callback参数时返回的是纯JSON可以直接.json()解析。pageSize参数固定为10。改成20、50都不生效这是接口限制不用担心多翻几页就行。评论时间字段格式要留意。京东返回的是2023-06-15 10:32:00字符串格式直接存起来就行不用额外转换。请求频率要控制。我实测下来每页间隔1.5到3.5秒比较安全。连续快速请求容易触发验证码IP会被临时限制一段时间。2.3 数据量的确定逻辑实训项目里数据量过小比如几十条说明工作态度有问题数据量过大几万条处理时间又太久。我当时抓了大约2000条评论覆盖一个商品的多页评论这个数据量在实训报告里既能支撑统计分析又不会让计算机跑得喘不过气。如果你做的是“多商品对比分析”建议3个商品各抓1000条总计3000条左右这样可以做横向对比。如果是一个商品的深度分析2000条就足够了。抓下来的数据建议先存储一份原始CSV防止后面处理出错重新爬。我当时用to_csv(data/raw/jd_comments_raw.csv, indexFalse, encodingutf-8-sig)保存这里有个细节一定要用utf-8-sig编码否则用Excel打开CSV文件时中文会乱码。这个细节我在实验报告里也特别注明了算是体现工程经验的一个小亮点。3. 数据预处理中文文本分析真正的分水岭数据拿到手真正让项目拉开差距的第一步来了。很多实训代码跑完爬虫就直接扔给模型分析结果准确率惨不忍睹原因就是跳过了预处理环节。中文文本和英文不同词与词之间没有空格分隔如果不去做清洗和分词再好的模型也白搭。我在这部分投入的时间占了整个项目的三成回报也最高。3.1 数据清洗四步走原始评论数据里除了有效文本还有大量杂质英文字母、数字、标点、URL链接、表情符号等。这些内容对情感分析没有帮助甚至会成为干扰项。我的清洗流程是这样的import re import pandas as pd def clean_text(text): if not isinstance(text, str): return # 去除URL text re.sub(rhttp[s]?://\S, , text) # 去除HTML标签 text re.sub(r[^], , text) # 去除用户和#话题 text re.sub(r\w|#\w#, , text) # 去除特殊字符保留中英文、数字、常用标点 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。!?、“”‘’\s], , text) # 去除多余空白 text re.sub(r\s, , text).strip() return text # 读取原始数据 df pd.read_csv(data/raw/jd_comments_raw.csv, encodingutf-8-sig) # 去除重复评论同一用户可能重复评论 df df.drop_duplicates(subset[评论内容]) # 清洗文本内容 df[清洗后评论] df[评论内容].apply(clean_text) # 去除清洗后为空的评论 df df[df[清洗后评论].str.len() 2] print(f清洗后剩余数据量: {len(df)})清洗这一步有两个容易被忽略的点去重一定要做。我在实际抓取中发现同一个用户对同一商品可能发表多条评论或者商家回复内容重复出现这些都会导致后续情感分析结果偏差。清洗后要检查空文本。有些评论本身就是纯表情、纯链接清洗后可能变成空字符串这些要直接删除否则分词阶段会报错。如果你是按评分维度分析可以在清洗后统计一下各评分的数据量分布。我当时抓的2000条数据里评分5星大约占81%1星大约占10%3星大约占9%这个分布也符合电商评论的“极端化”特征——用户要么很满意给五星要么很不满给一星中间态反而少。3.2 中文分词jieba的核心用法分词是中文情感分析的基石。我用的jieba分词器有三种模式精确模式jieba.lcut(text)把句子最精确地切开适合文本分析全模式jieba.lcut(text, cut_allTrue)把句子中所有可以成词的词语都扫描出来速度极快但不能解决歧义搜索引擎模式jieba.lcut_for_search(text)在精确模式基础上对长词再次切分适合搜索引擎分词情感分析场景下用精确模式就够了。不过直接调用会有个问题电商评论里的很多词汇要么被切碎要么根本不在词库里。比如“性价比”“物流快”“客服给力”“有点小失望”这些表达标准分词器不一定处理得好。解决办法是加载自定义词典和停用词表。自定义词典的作用是告诉分词器“这些词是一个整体不要切开”。比如“性价比”如果不指定可能被切成“性价”和“比”“蓝牙耳机”可能被切成“蓝牙”和“耳机”。我当时整理了一份电商领域词典格式很简单每行一个词性价比 2 n 物流快 2 n 客服态度 2 n 做工精细 3 n 待机时间 3 n 外观漂亮 3 n格式是三列词语、词频、词性。词频建议填2代表自定义词的权重。加载方式是jieba.load_userdict(dict/ecommerce_dict.txt)。停用词则是反过来把对情感判断没有帮助的虚词、代词、助词过滤掉比如“这个”“那个”“而且”“但是”“了”“的”“是”。网上有现成的中文停用词表我在此基础上又加了一批电商评论高频但无意义的词比如“京东”“物流”“快递”“商品”这些高频出现但无关情感的词。放一下加载自定义词典并分词的完整代码import jieba import jieba.analyse # 加载自定义词典 jieba.load_userdict(dict/ecommerce_dict.txt) # 读取停用词表 with open(dict/stopwords.txt, r, encodingutf-8) as f: stopwords set([line.strip() for line in f.readlines()]) # 添加额外停用词 extra_stopwords {京东, 物流, 快递, 商品, 东西, 收到, 购买} stopwords.update(extra_stopwords) def segment(text): words jieba.lcut(text) words [w for w in words if w not in stopwords and w.strip()] return .join(words) df[分词结果] df[清洗后评论].apply(segment) df[词列表] df[分词结果].apply(lambda x: x.split()) print(df[[清洗后评论, 分词结果]].head(10))分词效果对比如下原文不加载自定义词典加载自定义词典后这个手机性价比很高这个 手机 性价 比 很 高手机 性价比 很 高客服态度特别好客服 态度 特别 好客服态度 特别 好做工精细颜值高做工 精细 颜值 高做工精细 颜值 高看到差异了吗自定义词典直接把“性价比”“客服态度”“做工精细”这些对情感判断至关重要的词完整保留了避免被切碎后丢失语义。3.3 TF-IDF关键词提取预处理做完后我额外做了一步关键词提取用TF-IDF算法找出评论中的核心主题词。这一步在报告里可以作为“数据探索性分析”的一部分展示在可视化环节也会用到。TF-IDF的原理用一个类比就懂了把每篇评论看成一个作者把全部评论看成整个出版社。TF词频是“这个作者多爱用这个词”IDF逆文档频率是“这个词在全出版社范围内有多稀缺”。一个词在单篇评论中出现频率高但在全部评论中很少出现说明这个词是这篇评论的特色词值得关注反之如果所有评论都在说“京东”这个词就没有区分度。from jieba.analyse import extract_tags # 提取整个数据集的关键词 all_text .join(df[清洗后评论].tolist()) keywords extract_tags( all_text, topK50, withWeightTrue ) for kw, weight in keywords: print(f{kw}: {weight:.4f})我当时提取出来的关键词排名大致是“服务”“质量”“速度”“包装”“满意”“失望”“正品”“价格”“性价比”“客服”这些非常直观地展现了用户关心的核心维度也为后面“多维度情感拆解”提供了方向。4. 情感分析建模从SnowNLP到自定义优化的完整链路4.1 情感分析主流方法对比实训项目最怕“只会调用库不懂原理”。我在实验报告里系统地整理了目前主流的情感分析方法答辩的时候老师问“你为什么不直接调API”我回答得很有底气。方法原理优点缺点适用场景基于情感词典将文本与情感词典匹配计算积极词和消极词得分总和简单、可解释性强、无需标注数据依赖词典质量无法处理复杂语义领域词汇固定的场景基于机器学习提取文本特征TF-IDF、词向量训练分类器准确率高、泛化能力较好需要标注数据特征工程耗时有标注数据的通用场景基于深度学习LSTM、GRU、BERT等端到端建模准确率最高能捕获上下文语义需要大量训练数据、GPU资源大规模数据集、工业级应用基于预训练大模型调用API或微调大型语言模型效果最好成本高不适合离线作业展示型项目实训环境下时间、数据量都有限深度学习方案数据量不够容易过拟合预训练模型又显得“太作弊”。我当时选择的是“基于情感词典 SnowNLP综合判定”的路线既能讲清楚原理又能在准确率上比单纯用SnowNLP高出不少。4.2 SnowNLP的用法与“水土不服”问题SnowNLP是一个纯Python编写的中文文本处理库内置了训练好的情感分类模型可以直接用SnowNLP(text).sentiments得到0到1之间的情感倾向得分接近1是积极接近0是消极。用法非常简单from snownlp import SnowNLP test_texts [ 这个手机太棒了用起来非常流畅值得购买, 物流太慢了等了一个星期才到很不满意。, 手机一般般没有特别惊艳的地方。, 这个价格买到这个配置性价比无敌。 ] for text in test_texts: s SnowNLP(text) print(f{text}\n情感得分: {s.sentiments:.4f})但这里存在一个经典问题SnowNLP默认模型是基于购物评论训练的对某些特定领域的文本判断不够准确。比如正向情感表达“绝绝子”“YYDS”“爱了爱了”这些新网络热词旧模型很可能得分偏低而“气死我了”这种口语化表达可能被打出非常低的分数错判情绪强度。我实测下来SnowNLP在京东数码产品评论上的基础准确率大概在70%到75%左右不算差但不够好。后来我做了两步优化把准确率提升到了86%以上。4.3 优化方案一情感词典打分修正我自建了一个电商领域情感词典包含积极词、消极词、程度副词和否定词对SnowNLP结果进行修正。逻辑很直观统计评论中积极词和消极词的数量检测程度副词“非常”“极其”“有点”调整权重检测否定词“不”“没”“无”翻转情感方向# 情感词典节选 positive_words { 满意: 2, 喜欢: 2, 好用: 2, 流畅: 2, 好评: 2, 漂亮: 2, 性价比: 2, 推荐: 2, 值得: 2, 给力: 2, 正品: 1.5, 实惠: 1.5, 时尚: 1.5, 实用: 1.5, 惊喜: 2, 完美: 3, 赞: 2, 快: 1.5, 清晰: 1.5, 耐用: 1.5, 静音: 1.5, 高大上: 3 } negative_words { 差评: 2, 差: 1.5, 失望: 2, 不满意: 3, 退货: 2, 垃圾: 3, 太差: 3, 慢: 1.5, 卡顿: 2, 死机: 3, 后悔: 2, 不值: 2, 问题: 1.5, 瑕疵: 2, 破损: 2, 漏发: 2, 客服差: 2, 售后: 1.5, 旧: 1.5 } # 程度副词 degree_adverbs { 非常: 1.8, 特别: 1.8, 极其: 2.0, 十分: 1.8, 很: 1.5, 挺: 1.3, 有点: 0.7, 稍微: 0.6 } # 否定词 negation_words {不, 没, 无, 不是, 没有, 不太, 不怎么} def dictionary_score(word_list): score 0 negation_flag False for i, word in enumerate(word_list): degree 1.0 # 检测否定词 if word in negation_words: negation_flag True continue # 检测程度副词 if word in degree_adverbs: degree degree_adverbs[word] continue if word in positive_words: base positive_words[word] if negation_flag: score - base * degree negation_flag False else: score base * degree elif word in negative_words: base negative_words[word] if negation_flag: score base * degree negation_flag False else: score - base * degree return score这里的核心机制是先判断词的情感极性积极还是消极再乘上程度副词的权重如果之前遇到否定词则翻转极性。比如“不太满意”“不”触发否定标记“太”是程度副词到“满意”时基础分是2但因为否定标记变成-2*1.5-3很好体现了情感方向。4.4 优化方案二SnowNLP结果与词典打分融合词典打分有它的局限处理不了复杂句式、反讽覆盖的词汇也有限。SnowNLP的优势在于能基于整句上下文做判断。我最终采用的是加权融合策略def sentiment_analyze(text, word_listNone): if word_list is None: word_list segment(text).split() # SnowNLP情感得分[0,1] snownlp_score SnowNLP(text).sentiments # 映射到[-1,1]区间 snownlp_score_mapped (snownlp_score - 0.5) * 2 # 词典打分可能是任意范围的浮点数需要归一化到[-1,1] dict_raw dictionary_score(word_list) # 使用tanh将词典分数压缩到[-1,1] import math dict_score_normalized math.tanh(dict_raw / 5) # 加权融合权重根据实验调整 alpha 0.65 final_score alpha * snownlp_score_mapped (1 - alpha) * dict_score_normalized # 将[-1,1]映射回[0,1] final_score_01 (final_score 1) / 2 # 0.5为中性0.5-0.7算弱积极0.7以上算强积极反之同理 return final_score_01权重系数alpha 0.65不是拍脑袋定的我是用300条人工标注的样本做网格搜索从0.5到0.9每间隔0.05测试一次准确率最终确定0.65时综合准确率最高。这里顺便说一个做实验的小技巧保存300条标注样本让同一个结果在不同权重下反复评测而不是靠感觉调参这份数据在报告里作为“实验结果验证”也特别有说服力。融合后的分类效果对比测试样本SnowNLP得分词典打分融合得分人工判定这个手机太棒了用起来非常流畅0.9210.850.89积极物流太慢了等了一个星期才到0.188-0.720.16消极手机一般般用着还行吧0.6120.100.51中性价格虚高不太值得推荐0.431-0.580.28消极可以看到对“价格虚高不太值得推荐”这种SnowNLP判断接近中性的句子词典打分修正后正确归为消极了。这种细微修正正是准确率提升的关键。4.5 基于评分的情感标签划分结合原始评论的星级评分1-5分我在实验结果里做了“预测情感”和“用户真实评分”的对比验证。这样做的意义在于评分本身就是用户给的情感标签可以用来验证模型效果。def sentiment_label(score): if score 0.7: return 积极 elif score 0.4: return 中性 else: return 消极 df[情感得分] df.apply( lambda row: sentiment_analyze(row[清洗后评论], row[词列表]), axis1 ) df[情感标签] df[情感得分].apply(sentiment_label) # 统计各评分对应的情感预测准确率 result df.groupby(评分).apply( lambda x: (x[x[情感标签] ((积极 if x.name 4 else 消极 if x.name 2 else 中性))].shape[0] / len(x) * 100) )实测下来5星评论里预测为积极的比例约为93%1星评论里预测为消极的比例约为91%3星评论预测为中性的比例相对低一些约58%因为很多用户打3星但评论内容是积极的比如“手机整体还不错就是有点重”这种情况其实属于“有条件好评”模型输出的情感得分偏积极但不算高也符合预期。5. 结果可视化让数据自己说话可视化是整个项目里“看起来最专业”的部分也是答辩时最吸引眼球的部分。我做了四张图每一张都有明确的目的。5.1 情感分布饼图和柱状图先做一张最基础的情感分布图让人一眼看清整体口碑倾向import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False labels df[情感标签].value_counts().index.tolist() sizes df[情感标签].value_counts().values.tolist() colors [#4CAF50, #FFC107, #F44336] plt.figure(figsize(8, 6)) plt.pie( sizes, labelslabels, colorscolors, autopct%1.1f%%, startangle90, explode(0.02, 0.02, 0.02) ) plt.title(电商产品评论情感分布) plt.tight_layout() plt.savefig(output/情感分布饼图.png, dpi300) plt.show()这里有个非常容易踩的坑matplotlib默认字体不支持中文显示出来会是方框。所以必须设置plt.rcParams[font.sans-serif]。Windows下用SimHei或Microsoft YaHeiMac下用Arial Unicode MS或PingFang SC。如果还是乱码可以检查一下系统中文字体是否安装完整。情感分布的柱状图也建议做一张横轴是“积极/中性/消极”纵轴是对应数量或占比两者结合看起来更有数据感。5.2 词云图评论高频词的可视化呈现词云几乎是情感分析项目里“网红级别的必备图”。评分5星和1星的评论分别生成词云对比效果非常震惊——5星评论的大词是“质量”“流畅”“性价比”“值得”1星评论的大词是“差”“退货”“客服”“问题”“失望”。这种对比一眼就能传达“用户对什么满意、对什么不满”。import jieba.analyse from wordcloud import WordCloud from PIL import Image import numpy as np def generate_wordcloud(text, output_path, mask_imageNone): # 提取关键词及权重 keywords jieba.analyse.extract_tags(text, topK100) keyword_str .join(keywords) # 设置词云参数 wc WordCloud( font_pathC:/Windows/Fonts/simhei.ttf, # 中文字体路径Mac下换成 /System/Library/Fonts/PingFang.ttc background_colorwhite, width800, height600, max_words100, max_font_size150, maskmask_image # 可传图形蒙版 ) wc.generate(keyword_str) wc.to_file(output_path) return wc # 按情感标签分别生成词云 positive_text .join(df[df[情感标签] 积极][清洗后评论]) negative_text .join(df[df[情感标签] 消极][清洗后评论]) generate_wordcloud(positive_text, output/积极评论文云.png) generate_wordcloud(negative_text, output/消极评论文云.png)生成词云时有几个细节要注意font_path必须指向系统里存在的中文字体文件。Windows的simhei.ttf路径一般是C:/Windows/Fonts/simhei.ttfMac一般是/System/Library/Fonts/PingFang.ttc或/System/Library/Fonts/STHeiti Light.ttc。如果不需要蒙版形状可以不传mask参数默认是矩形。如果想做形状需要准备一张二值化的PNG图片白色部分是背景、黑色部分是形状区域。max_font_size决定最大字体词权重越高字体越大呈现出来的视觉层级越清晰。5.3 不同维度对比分析除了整体情感分布我还做了两个维度的交叉分析这两个维度在报告里很加分第一个是“不同评分下的情感得分均值折线图”。横轴是用户评分1到5纵轴是模型预测的情感得分平均值理论上应该呈现上升趋势。如果某个评分区间明显偏离趋势说明用户评分和评论内容存在不一致这个异常点值得分析。第二个是“时间维度情感趋势图”。把评论按周聚合计算每周的平均情感得分能看出商品口碑随时间的变化趋势。比如某款手机上市初期好评多但三个月后出现“电池续航不行”等负面反馈曲线就会下降。这个趋势分析虽然简单但在实训场景下非常讨喜因为老师很少看到学生做到时间维度。# 按周聚合情感得分 df[评论时间] pd.to_datetime(df[评论时间]) df[周] df[评论时间].dt.isocalendar().week.astype(int) weekly_sentiment df.groupby(周)[情感得分].mean().reset_index() plt.figure(figsize(10, 5)) plt.plot(weekly_sentiment[周], weekly_sentiment[情感得分], markero, linestyle-, color#2196F3) plt.xlabel(周数) plt.ylabel(平均情感得分) plt.title(商品口碑随时间变化趋势) plt.grid(True, alpha0.3) plt.tight_layout() plt.savefig(output/时间趋势图.png, dpi300) plt.show()6. Flask可视化展示系统从脚本到“产品”的进阶如果你只想完成实训及格要求到这里已经足够了。但如果目标是90分以上我强烈建议加一个Flask可视化展示系统把分析结果做成一个简单的网页界面。实际操作中我发现同样的分析结果放在Jupyter Notebook里展示和放在网页里展示老师的打分观感完全是两个量级。6.1 为什么选择FlaskFlask是Python里最轻量的Web框架简单到不需要额外学很多东西。只需几个小时就能把分析结果页面化。整个系统的核心功能就三个上传一个CSV文件评论数据后台自动完成情感分析前端展示分析结果图表这个场景不需要复杂的前后端分离Flask Jinja2模板 ECharts就能搞定。6.2 核心代码实现from flask import Flask, render_template, request, jsonify import pandas as pd import json app Flask(__name__) # 全局变量存储最近一次分析结果 analysis_result {} app.route(/, methods[GET]) def index(): 首页展示项目介绍和上传入口 return render_template(index.html) app.route(/analyze, methods[POST]) def analyze(): 分析接口接收上传文件执行情感分析 file request.files[file] df pd.read_csv(file, encodingutf-8-sig) # 执行情感分析复用之前的代码 df[清洗后评论] df[评论内容].apply(clean_text) df[词列表] df[清洗后评论].apply(lambda x: segment(x).split()) df[情感得分] df.apply( lambda row: sentiment_analyze(row[清洗后评论], row[词列表]), axis1 ) df[情感标签] df[情感得分].apply(sentiment_label) # 统计数据用于前端展示 global analysis_result analysis_result { total: len(df), positive: int((df[情感标签] 积极).sum()), neutral: int((df[情感标签] 中性).sum()), negative: int((df[情感标签] 消极).sum()), positive_ratio: round(float((df[情感标签] 积极).mean()), 4), data: df[[评论内容, 评分, 情感得分, 情感标签]].to_dict(orientrecords) } return jsonify(analysis_result) app.route(/result) def result(): 结果展示页 return render_template(result.html, resultanalysis_result) if __name__ __main__: app.run(debugTrue, host0.0.0.0, port5000)前端页面的逻辑用ECharts渲染饼图和柱状图数据从后端接口获取。这里只贴一个关键片段!-- templates/result.html -- !DOCTYPE html html langzh head meta charsetUTF-8 title情感分析结果/title script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script /head body div classstats div classstat-card h3评论总数/h3 p{{ result.total }}/p /div div classstat-card h3积极评论/h3 p{{ result.positive }} ({{ (result.positive_ratio * 100) | round(1) }}%)/p /div !-- 更多统计卡片... -- /div div idpieChart stylewidth: 600px; height: 400px;/div script var pieChart echarts.init(document.getElementById(pieChart)); pieChart.setOption({ title: { text: 情感分布占比 }, tooltip: { trigger: item }, series: [{ type: pie, radius: 60%, data: [ { value: {{ result.positive }}, name: 积极, itemStyle: { color: #4CAF50 } }, { value: {{ result.neutral }}, name: 中性, itemStyle: { color: #FFC107 } }, { value: {{ result.negative }}, name: 消极, itemStyle: { color: #F44336 } } ] }] }); /script /body /htmlFlask系统的加分点在于它不是重新实现分析逻辑而是把之前的脚本能力平移到Web端。我在实验报告里画了一个简版架构图数据层CSV→ 分析层清洗、分词、情感计算→ 展示层Flask ECharts三层结构清晰老师一看就明白。这个系统我当时现场演示了一下上传一份新的CSV评论数据点击分析按钮页面立刻显示出情感分布图。演示效果比干巴巴的代码讲解好太多。7. 实训报告写作与答辩准备从“做完”到“讲好”最后这部分我把自己在真正答辩中积累的经验完整写出来。代码写得好只占一部分还有一部分在于怎么把成果表达出来。7.1 实验报告结构模板我实验报告最终是8个章节这个结构参考了学校实训报告模板但内容全部按项目实际填写项目背景与意义——电商评价中的情感信息价值、人工处理的痛点需求分析——功能性需求采集、分析、展示和非功能性需求性能、易用性相关技术介绍——Python、jieba、SnowNLP、Flask、ECharts系统设计——架构图、模块划分、流程图用Visio或ProcessOn画系统实现——每个模块的核心代码和关键逻辑解释系统测试——不同商品数据实测、准确率验证、异常数据处理总结与展望——项目完成情况、存在的问题、后续改进方向参考资料——标注来源体现文献调研能力需要特别说明的是不要直接贴大段代码而是“关键代码 文字解释”组合。老师看报告最怕看到代码堆砌他们想看的是代码背后的思路。每个模块先写“这个模块要解决什么问题”再写“最终采用了什么方案”最后写“核心代码和关键注释”。7.2 高频答辩问题整理与应对思路我把答辩时被问到最多的几个问题整理一下结合我当时是实际回答整理出参考思路问为什么不用LSTM或BERT答实训环境下数据量约2000条深度学习模型需要大量数据才能训练充分容易过拟合。SnowNLP 情感词典加权融合的方案在轻量级数据上准确率达到86%以上兼顾了效果和可解释性。如果未来数据量扩展到数万条可以考虑引入BERT进行迁移学习。这种答题思路既承认了现有方案是合理的又展示了对进阶方案的了解老师很容易给高分。问情感词典是怎么构建的答基础词典参考了知网情感词典和现有中文情感词典的开源资源然后针对电商领域进行了扩充。我在实训前手动整理了200个高频电商评论词汇其中积极词约120个、消极词约80个、程度副词约20个、否定词约10个。同时通过词频统计发现新词迭代更新词典。这里要体现“迭代构建”的思路而不是一把梭一次性搞定。问准确率是怎么计算的答人工标注了300条评论作为测试集分别用SnowNLP原生模型、词典打分、加权融合三种方案进行测试融合方案准确率最高约为86.3%。每个评分档位的准确率分别是5星93%、1星91%、3星58%。这个数据一定要真实。我当时真的花了两个小时把300条评论一条条标注了虽然辛苦但老师问起来时回答得非常自信。问系统能处理多长的评论文本性能怎么样答经过清洗后2000条评论的完整分析时间约10秒左右平均每条评论耗时约5毫秒。超长评论超过500字在分词阶段耗时略有增加但不会影响整体性能。像这样的数据我建议提前跑一遍时间测试记录真实数据。答辩时脱口而出真实数字比“大概很快吧”这种模糊回答强太多。7.3 拿高分的一些“隐藏套路”除了技术实现我在实训中总结出来几个不太容易想到但特别有用的点第一个是数据预处理环节一定要保留“清洗前后对比”的素材。我做实验时专门保存了清洗前后的文本对比表格在报告里放出来既有说服力又显得重视细节。尤其是“原始评论的手机是正品很好物流快”清洗后变成“手机是正品很好物流快”这种对比看起来就非常专业。第二个是把自己定义的词典文件、停用词表、爬虫日志等过程性材料保留好答辩时可以主动展示。这些旁证材料能体现你是真做了而不是抄的代码。第三个是项目最后留一个“不足与改进方向”——不要写得像自我批评而是写“本项目验证了SnowNLP词典加权融合方法在电商评论场景下的有效性未来的工作可以引入BERT等预训练模型做迁移学习或者构建领域情感知识图谱进一步提升准确率”。这样既诚实又展现了前瞻性。第四个特别实用测试代码先跑通一遍。最好是在答辩前一天用虚拟机或全新环境重新跑一遍完整流程确保不依赖本机的自定义路径、特殊配置。我见过太多同学答辩现场跑代码时因为某个包没装或者路径不对而翻车。把依赖包用pip freeze requirements.txt导出这样换环境一键安装重新跑万无一失。8. 完整代码运行说明直接抄作业版最后我把整个main.py的完整流程串联起来确保你把之前的模块拼在一起能直接运行。需要说明的是以下代码整合了前面所有功能模块并加了步骤编号在实训报告里作为“系统主控模块”展示特别合适。# main.py - 电商产品评论情感分析完整流程 import pandas as pd import time from crawler.jd_crawler import JDCrawler from analysis.preprocess import clean_text, segment from analysis.sentiment import sentiment_analyze, sentiment_label from analysis.visualization import get_visualizations def main(): print( * 50) print(电商产品评论情感分析系统 v1.0) print( * 50) # 步骤1数据获取 print(\n[1/5] 开始爬取京东商品评论...) product_id 100012043978 # 替换为你需要分析的商品ID crawler JDCrawler(product_idproduct_id, max_pages50) df_raw crawler.crawl() df_raw.to_csv(data/raw/jd_comments_raw.csv, indexFalse, encodingutf-8-sig) print(f原始数据获取完成共 {len(df_raw)} 条评论) # 步骤2数据预处理 print(\n[2/5] 开始数据清洗和分词...) df_raw[清洗后评论] df_raw[评论内容].apply(clean_text) df_raw df_raw[df_raw[清洗后评论].str.len() 2] df_raw df_raw.drop_duplicates(subset[清洗后评论]) df_raw[词列表] df_raw[清洗后评论].apply(lambda x: segment(x).split()) print(f预处理完成有效数据 {len(df_raw)} 条) # 步骤3情感分析 print(\n[3/5] 正在进行情感分析...) start_time time.time() df_raw[情感得分] df_raw.apply( lambda row: sentiment_analyze(row[清洗后评论], row[词列表]), axis1 ) df_raw[情感标签] df_raw[情感得分].apply(sentiment_label) elapsed time.time() - start_time print(f情感分析完成耗时 {elapsed:.2f} 秒) # 步骤4结果保存 print(\n[4/5] 正在保存分析结果...) df_raw.to_csv(data/processed/final_result.csv, indexFalse, encodingutf-8-sig) df_raw.to_excel(data/processed/final_result.xlsx, indexFalse) print(分析结果已保存) # 步骤5可视化输出 print(\n[5/5] 正在生成可视化图表...) get_visualizations(df_raw) print(\n全部完成分析结果图表已保存到 output/ 目录) if __name__ __main__: main()运行前需要安装的依赖包用下面的命令pip install requests pandas jieba snownlp matplotlib wordcloud flask openpyxl其中openpyxl是pandas导出Excel所需的依赖。如果你在导出Excel时报错先确认这个包装了没有。最后补充一个特别容易忽略但影响体验的细节整个项目涉及的路径不要在代码里写死绝对路径比如C:/Users/xxx/Desktop/...尽量用相对路径。这样整个项目文件拷给任何人在任何目录下都能直接运行。我当时在报告里也特意说了这一点老师实际操作后发现“确实在任何位置都能跑通”这个体验细节也是一个隐形的加分项。本文还有配套的精品资源点击获取
返回列表