简介:面向毕业设计、期末大作业与课程设计场景的完整Python项目,聚焦大众点评数据采集后的可视化展示与评论情感倾向分析。代码全程附带注释,从爬虫数据清洗、情感建模到图表展示均有清晰模块划分,新手可参照注释理解关键实现,部署门槛较低。压缩包采用ZIP格式,资源共0个文件,涵盖项目代码、说明文档与答辩PPT等主要类型,其中代码对应系统功能实现,PPT适合汇报演示,整体包体大小26.35MB,便于快速下载与本地运行。目前已有207人学习下载,对于需要完成同类型数据分析或文本挖掘项目的读者,可作为一套完整可运行的高分参考方案,也可直接作为课设、毕设的代码基础扩展使用。
1. 大众点评数据可视化与情感分析:毕设项目从零到答辩的完整拆解
毕业设计选大众点评数据可视化与情感分析,是我带过的学生里通过率比较高的题目之一。原因很直接:本地生活评论数据量大、业务语义清晰,图表做出来效果直观,情感分析又有实实在在的计算过程,不是那种「看起来工作量很大、其实全是套模板」的题目。很多人一开始担心两个问题:一是数据从哪来,二是情感分析准不准。这个项目实际跑下来,pandas 清洗数据、SnowNLP 做情感打分、ECharts 出图表,难度曲线很平缓,真正考验人的反而是数据预处理和演示时的稳定度。这篇按项目代码的实际拆解顺序来写:从技术选型到情感分析,从可视化看板到最后的答辩避坑,完整还原一套能直接演示、能经得住导师追问的毕设系统。
2. 技术选型与数据预处理:Flask、ECharts、SnowNLP 落地前的准备工作
2.1 技术选型:为什么是 Flask + ECharts + SnowNLP 而不是别的组合
毕设场景的技术选型,原则和工业项目不完全一样。工业项目看扩展性、并发、可维护性,毕设看的是三点:演示时不出错、代码量足够、被追问时能解释清楚。这个项目用的组合是 Flask + ECharts + SnowNLP,在三者之间做了明确分工。
| 模块 | 选型 | 理由 |
|---|---|---|
| Web 框架 | Flask | 路由简单,单个 Python 文件就能启动,答辩现场部署成本最低 |
| 可视化 | ECharts(HTML 页面 + 前端 JS) | 图表交互效果好,饼图、柱状图、折线图开箱即用 |
| 情感分析 | SnowNLP | 中文评论不需要自己训练模型,装好包直接打分 |
| 分词 | jieba | 与 SnowNLP 配合成熟,词云和高频词统计都靠它 |
| 数据库 | SQLite | 单文件数据库,零配置,避免 MySQL 环境不一致导致演示翻车 |
为什么不换其他方案?这里说几个常见误选。Django 功能强但自带 admin 和 ORM 体系,答辩时多出很多「为什么要这样配」的问题,对纯毕设来说偏重了。Vue + 前后端分离看着时髦,但涉及跨域、打包、node 环境,增加的是部署环节的不可控因素。还有同学想上 Spark 做情感分析,本科毕设的数据量撑不起来这个架构,导师一眼就看出来是凑数。Flask 最诚实,每个页面由后端渲染还是前端请求数据,清清楚楚。
这个项目里 Flask 承担的角色很纯粹:提供数据接口、渲染页面入口、调度分析模块。数据量级在几千到一两万条评论时,SQLite 完全够用,查询响应在毫秒级,演示时不会有任何卡顿感。
2.2 数据集字段与预处理流程:清洗不到位,后面全白费
大众点评评论数据的常见字段,这个项目里基本都覆盖了。店铺角度有店名、行政区、星级评分、点评数、人均价格;评论角度有条件、口味分、环境分、服务分、评论文本、评论时间。拿到原始数据后第一件事不是做分析,而是清洗,这一步直接决定情感分析的准确率上限。
import pandas as pd df = pd.read_csv("dianping_reviews.csv", encoding="utf-8") # 去除完全重复的记录:同一用户对同一店铺的重复点评没有分析价值 df = df.drop_duplicates(subset=["user_id", "shop_id", "comment_time"]) # 评论文本中的全角空格、零宽字符清理,这类字符会让分词结果出现空 token df["comment"] = df["comment"].astype(str).str.replace( "[\u3000\xa0\u200b-\u200d\ufeff]", "", regex=True ) # 过滤过短评论:低于 4 个字符的文本无法提供有效情感信号 raw_len = len(df) df = df[df["comment"].str.len() >= 4] print(f"过滤前 {raw_len} 条,过滤后 {len(df)} 条")这里几个参数值得说明。drop_duplicates的subset指定了去重的判断依据,只用user_id会误删不同店铺的真实评论,所以必须把三个字段组合起来。正则里的\u3000是中文全角空格,\xa0是不换行空格,这两类字符在复制粘贴的数据里非常常见,不清理的话分词阶段会产生孤立空串。评论长度阈值取 4,是因为「好吃」「还行」这类两字评论虽然也有情感倾向,但数量过多会把情感分布拉向两极,对整体分布图产生误导。
清洗完数据后,还有一个值得做的步骤:保存一份清洗后的副本。因为情感分析脚本可能要反复跑,每次都从头读原始 CSV 清洗一遍很浪费时间。项目代码里一般会有cleaned_data.csv这个中间产物,后续的分析模块统一从清洗后的文件读取。
2.3 数据库表设计与项目目录结构:让答辩时能随手指出每个文件的作用
数据清洗完,下一步就是建库导数据。这个项目用 SQLite 的好处,在演示场景下体现得特别明显:不需要额外启动数据库服务,不必担心实验室电脑没装 MySQL 或者密码不对。答辩老师问起数据库设计时,你可以直接说表结构,还能现场用 Navicat 或命令行打开.db文件验证数据,比空口描述有说服力得多。
CREATE TABLE shop ( id INTEGER PRIMARY KEY AUTOINCREMENT, shop_name TEXT NOT NULL, district TEXT, star_rating REAL, review_count INTEGER, avg_price REAL ); CREATE TABLE review ( id INTEGER PRIMARY KEY AUTOINCREMENT, shop_id INTEGER NOT NULL, user_id TEXT, comment_time TEXT, rating_taste REAL, rating_env REAL, rating_service REAL, comment_content TEXT );两张表的结构设计遵循一个原则:店铺维度和评论维度分开。shop表存聚合类的数据,review表存明细类的数据,通过shop_id关联。这样在做可视化时,饼图和柱状图从shop表查聚合值,词云和情感分布从review表查文本内容,各司其职,不需要做复杂的多表连接。
项目目录结构通常是这样的:
project/ ├── app.py # Flask 主入口,路由与接口定义 ├── analysis/ │ ├── sentiment.py # 情感分析核心模块 │ └── wordcloud.py # 词云生成模块 ├── data/ │ ├── dianping_reviews.csv │ └── cleaned_data.csv ├── templates/ │ └── index.html # 可视化展示页面 ├── static/ │ └── js/ # ECharts 配置脚本 └── requirements.txt目录拆分不需要太复杂,但analysis模块单独拎出来是必要的。答辩时导师通常会问「情感分析这块代码在哪」,你直接指向sentiment.py并打开文件现场讲解,比在主应用文件里翻半天效果要好得多。这个项目的代码注释本身就比较完整,每个函数上方都有说明,新手对着目录结构就能理清执行流程。
3. 情感分析实现:jieba 分词、SnowNLP 打分与阈值调优
3.1 评论情感分析的核心思路:从词表匹配到概率模型
大众点评评论的情感分析,本质上是一个中文短文本二分类问题:判断一条评论表达的是正面还是负面情绪。实现路径有两条。其一是词表匹配法,维护一个正面词表和一个负面词表,统计评论里命中各个词表的词数,哪边多就归哪边。优点是简单透明,缺点是对「不太行」「一般般」这类带有否定词和转折语义的句子束手无策。
其二是用训练好的模型直接打分,这个项目选的就是基于朴素贝叶斯的中文情感分析工具 SnowNLP。它的原理并不复杂:模型在大量带标注的中文语料上训练过,输入一段文本后,sentiments属性会返回一个 0 到 1 之间的浮点数,越接近 1 表示正面倾向越强,越接近 0 表示负面倾向越强。朴素贝叶斯假设词与词之间相互独立,计算的是文本属于正类还是负类的后验概率。
为什么毕设用 SnowNLP 而不是自己训练模型?最现实的原因是数据标注成本。要自己训练一个情感分类器,需要几千条人工标注好的评论,对毕设周期来说根本不现实。SnowNLP 开箱即用,三行代码出结果,而且语义判断能力比简单词表法强不少,能识别出「环境不错但服务太慢」这种混合情感的句子——虽然它只能给出一个综合分数,不会拆解出分方面情感,但对毕设来说已经足够了。这个项目在情感分析模块的代码注释里也说明了这一点,方便答辩时解释选型动机。
3.2 情感打分的具体实现:批量处理百万字符也不怕
情感打分模块的代码不长,但有几个细节写不对,结果就会偏。先看核心实现:
from snownlp import SnowNLP import jieba import pandas as pd def analyze_comment(text: str) -> float: """对单条评论进行情感打分,返回 0~1 之间的情感倾向值""" if not text or len(text) < 4: return 0.5 # 过短文本返回中性值,避免干扰统计分布 s = SnowNLP(text) return s.sentiments df = pd.read_csv("data/cleaned_data.csv", encoding="utf-8") # 批量计算情感分数,apply 循环比 for 逐行遍历更简洁 df["sentiment_score"] = df["comment"].apply(analyze_comment) # 按 0.6 / 0.4 分界划分情感类别 df["sentiment_label"] = pd.cut( df["sentiment_score"], bins=[0, 0.4, 0.6, 1.0], labels=["负面", "中性", "正面"], ) print(df["sentiment_label"].value_counts())这里的逻辑说明一下。SnowNLP(text)在内部会先做分词,再计算贝叶斯概率,所以严格来说不需要手动调用 jieba 就能得到情感分数。但项目中仍然引入 jieba,是因为后面生成词云、提取高频特征词时需要独立的 jieba 分词结果。如果你对单条评论的准确率有更高要求,可以改成先按标点切句再逐句打分取均值,不过对大众点评这种短评论为主的数据,整段打分已经够用。
pd.cut的三个参数是调优的核心。bins定义了分数区间,labels定义了每个区间对应的情感类别。0.6 和 0.4 是最常见的分界点,但实际使用时你会发现,直接用这两个阈值会有问题,具体情况在 3.3 节细说。value_counts()输出的分布结果会直接供饼图数据使用,所以这一步的输出格式要提前确认好。
3.3 阈值调优与结果验证:0.5 不一定是好的分界点
跑完一遍情感分析,先别急着做图,看一眼分数分布。我当时的做法是输出描述性统计和直方图数据,结果发现一个典型现象:大多数评论的分数集中在 0.8 以上或 0.2 以下,0.4 到 0.6 之间反而没什么样本。这其实不是 bug,而是 SnowNLP 模型在电商和影评语料上训练的特性——它对态度鲜明的文本打分很果断,对中性文本反而会偏向某一端。
这带来一个实际问题:如果你用 0.5 作为正负分界,绝大多数评论都是正的或负的,中性比例极低,饼图上「正面」占比会显得夸张。项目里的做法是上调正面阈值、下调负面阈值,中间留出 0.4 到 0.6 的灰色地带,让中性类别至少占 10% 以上的样本。这不仅仅是让图表更好看,在答辩时也更好解释:「中性区间是情感不明确的评论,模型对这类文本没有足够信心」。
为了验证阈值的合理性,我写了一个简单的抽样脚本,随机取出每个类别的几条样本人工检查:
import random # 从每个情感类别中随机抽取 10 条,输出原文和分数,人工判断是否符合直觉 sample = df.groupby("sentiment_label").apply( lambda x: x.sample(10, random_state=42) ) for _, row in sample.iterrows(): print(f"[{row['sentiment_label']}] {row['sentiment_score']:.3f} | {row['comment']}") # 建议输出到文件而不是终端,方便答辩前整理成验证截图 sample.to_csv("data/sentiment_sample_check.csv", index=False, encoding="utf-8-sig")random_state=42是为了让抽样结果可复现,你演示给别人看的时候,每次跑出的样例一致,讲解时更有条理。人工检查重点看两类边界样本:分数在 0.4 到 0.6 之间的评论,和分数在 0.6 到 0.7 之间的评论。前者看是否真的语义含混,后者看是否明显是负面却被判成正面。大众点评里「口味不错但上菜太慢」这类转折句,经常会被判成中性偏正面,这是模型能力的边界,答辩时主动说出来反而是加分项——说明你理解模型的局限性,而不是把它当黑匣子。
4. 数据可视化看板搭建:ECharts 图表与 Flask 数据接口对接
4.1 图表选型:每张图回答一个具体问题
可视化看板不是把图表堆上去就完事,每张图要对应一个业务问题。这个项目里图表和问题的对应关系,我建议你和代码里保持一致,答辩时讲「为什么要放这张图」会非常顺。
| 图表类型 | 回答的问题 | 数据来源 |
|---|---|---|
| 饼图 | 用户评价整体偏正面还是负面 | review 表按 sentiment_label 分组计数 |
| 柱状图 | 哪个行政区的负面评论占比最高 | review 表 join shop 表按 district 分组 |
| 折线图 | 评论量和情感均值随时间的变化趋势 | review 表按月份聚合 |
| 词云 | 用户最常提到的关键词是什么 | review 表 comment 字段分词取高频词 |
柱状图用负面评论占比而不是评论总量,这个细节很重要。如果一个区店铺多,它的评论总量天然就高,直接画总量柱状图看不出情感质量的差异。算占比,让数据说话,答辩时也能多讲一句「这里用的是相对指标而不是绝对指标」。
4.2 Flask 接口设计:后端查询与 JSON 返回的规范写法
可视化页面需要的数据,统一通过 Flask 接口提供,前端页面启动时并行请求几个接口拿到数据后渲染图表。这种模式的好处是前端和后端解耦,调试时可以直接在浏览器访问/api/sentiment_distribution查看返回的 JSON 结构。
from flask import Flask, jsonify import sqlite3 import pandas as pd app = Flask(__name__) DB_PATH = "data/dianping.db" @app.route("/api/sentiment_distribution") def sentiment_distribution(): """情感分布饼图接口:返回正面/中性/负面三类评论的数量""" conn = sqlite3.connect(DB_PATH) query = """ SELECT sentiment_label, COUNT(*) as cnt FROM review GROUP BY sentiment_label """ df = pd.read_sql_query(query, conn) conn.close() data = [ {"name": row["sentiment_label"], "value": int(row["cnt"])} for _, row in df.iterrows() ] return jsonify({"code": 0, "data": data})这里有几个写法上的讲究。GROUP BY sentiment_label是在数据库层完成聚合,而不是把所有评论 load 到 Python 里再分组,数据量到上万条时性能差距就出来了。int(row["cnt"])是必须的,因为 SQLite 的 COUNT 返回的是整型,但 pandas 读出来可能是 int64 类型,不转的话 JSON 序列化会报错。每个接口的返回结构统一用{"code": 0, "data": [...]}包裹,前端判断code是否为 0 决定要不要渲染,这个约定在多个图表接口之间保持一致,省去很多调试时间。
4.3 前端页面集成:ECharts 渲染与异步数据加载
前端部分用的是 ECharts 的常规引入方式。在templates/index.html里通过 CDN 引入 ECharts,页面加载时用 fetch 请求上面的接口,拿到数据后初始化图表。先说通用写法,再指出坑。
async function loadSentimentPie() { const response = await fetch("/api/sentiment_distribution"); const result = await response.json(); if (result.code !== 0) return; const chart = echarts.init(document.getElementById("sentimentPie")); chart.setOption({ title: { text: "评论情感分布", left: "center" }, tooltip: { trigger: "item" }, series: [{ type: "pie", radius: ["35%", "65%"], data: result.data, label: { formatter: "{b}: {c} ({d}%)" } }] }); } window.addEventListener("load", loadSentimentPie);{b}: {c} ({d}%)是 ECharts 的模板字符串,{b}是名称,{c}是数值,{d}是百分比。如果你发现 label 显示不对,通常是这里写错了。radius: ["35%", "65%"]是环形图的半径范围,内半径 35% 外半径 65%,中间留空可以后续放总评论数,视觉上比实心饼图更专业。window.addEventListener("load", ...)确保页面完全渲染后再发请求,避免图表容器还没生成就初始化导致报错。
词云图不在 ECharts 自带图表里,这个项目用的是pyecharts的 WordCloud 或者前端 wordcloud2.js。如果后端生成词云图,要注意中文字体问题,这一步是毕设演示翻车的高发区,具体排查方法放在第 5 章避坑里说。前端拿到词云数据后,常见的做法是后端返回关键词列表和权重,前端渲染成词云,或者后端直接生成图片传给前端展示,两种方式这个项目里都有代码注释说明,建议用后端生成图片的方案,前端只要一个<img>标签,省掉一堆 JS 依赖。
5. 常见问题排查与避坑:演示前最容易翻车的五个位置
5.1 SnowNLP 评分两极分化严重,中性评论几乎为零
现象:跑完情感分析,发现分数不是接近 0.95 就是接近 0.05,0.4 到 0.6 之间的样本不到 5%,饼图正面占比特别夸张。
原因:SnowNLP 的训练语料以电商购物评论为主,这类评论本身态度鲜明,模型对中性表达没有足够的训练样本。再加上大众点评评论普遍短小,「环境好」「价格实惠」这类短语在贝叶斯模型下会得到非常高的正面概率。
解决:不要用 0.5 作为唯一分界,上调正面阈值到 0.6,下调负面阈值到 0.4,中间区间单独归为中性。同时在答辩材料里主动说明「模型对中性文本区分能力有限,所以采用更保守的双阈值方案」,这句话能让导师觉得你做过充分的调优思考。
5.2 词云图中文全部显示为方框
现象:词云生成的图片里,中文全部变成一个个方块,英文和数字正常。
原因:WordCloud 库默认使用英文字体,中文字符无法在默认字体下渲染,就会画成方框。这个问题在 Windows 和 Linux 的表现一样,只是系统字体路径不同。
解决:生成词云时显式指定font_path参数。
from wordcloud import WordCloud # Windows 一般用 SimHei.ttf(黑体),Linux 可指定 /usr/share/fonts 下的中文字体 wordcloud = WordCloud( font_path="C:/Windows/Fonts/simhei.ttf", width=800, height=600, background_color="white", max_words=200, )答辩现场的电脑如果不是你自己常用的那台,font_path要提前确认目标机器的字体路径。最好把字体文件复制到项目static/fonts目录下,用相对路径引用,这样换机器演示不会因为系统字体差异翻车。
5.3 Flask 页面在本地能开,换到实验室电脑打不开
现象:在自己电脑运行python app.py,浏览器访问http://localhost:5000一切正常。到答辩现场用实验室电脑跑同一份代码,浏览器提示无法访问。
原因:Flask 默认只监听127.0.0.1,只有本机能访问。如果其他电脑想通过局域网访问你的服务,必须显式指定监听地址,同时 Windows 防火墙可能拦截 5000 端口的入站请求。
解决:
if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=False)host="0.0.0.0"不明思议,就是监听所有网络接口。debug=False是必须的,调试模式下的 Werkzeug 调试器存在安全风险,而且会自动重载,答辩演示过程中如果代码被意外修改会当场重启。访问时用http://你的IP:5000,IP 用ipconfig查。如果还是不通,检查防火墙——Windows 下在控制面板里放行 5000 端口,或者干脆在演讲开始前找管理员关掉防火墙。
5.4 Python 3.12 环境下依赖库安装报错
现象:按requirements.txt用 pip 安装,lxml或snownlp报编译错误,提示Microsoft Visual C++ 14 is required。
原因:部分依赖包的新版本还不适配最新的 Python 3.12,或需要本地编译环境。SnowNLP 的底层依赖scikit-learn在旧版本上对 Python 版本有上限要求。
解决:建议直接用 Python 3.9 或 3.10 新建虚拟环境,避开编译问题。
conda create -n dianping python=3.9 -y conda activate dianping pip install flask snownlp jieba pandas pyecharts wordcloud装了之后先跑一个最小验证,确认from snownlp import SnowNLP能正常 import,再做后续工作。这一步能省掉大量排查时间,我见过太多因为环境不一致导致项目跑不起来的案例了。
5.5 情感分析结果导入数据库后,图表接口返回的数据不对
现象:图表接口能访问,但饼图比例和情感分析脚本输出的value_counts()结果不一致。
原因:数据库里的sentiment_label字段是在情感分析阶段写入的,如果你调整过阈值代码,但数据库里的旧数据没有重新更新,就会出现两边对不上的情况。常见做法是清洗脚本、打分脚本、入库脚本分开执行,某一步忘记重跑就会出这种问题。
解决:项目里加一个统一的运行脚本,按顺序执行清洗、打分、入库三步,并保证每次调整阈值后强制全量重跑。
python analysis/clean_data.py python analysis/sentiment.py python analysis/load_to_db.py也可以更简单,在sentiment.py里直接连数据库 UPDATE,而不是先输出 CSV 再手动导入。重要的是记住一条:改了情感阈值必须从第二步开始跑,图表数据才能跟着更新。这类问题最容易在答辩前一晚出现,拷到演示电脑上跑出来的图和 U 盘里截图对不上,当场就尴尬了。
6. 效果验证与答辩演示:让情感分析结果经得起追问
情感分析的准确率,是答辩时导师最可能深挖的点。你不能只说「用了 SnowNLP」,而要给出一套验证过程和效果数据。我的做法是准备了 100 条人工标注样本,其中 50 条从情感分析结果的「正面」类别中随机抽取,50 条从「负面」类别中抽取,自己手工判断标注后与模型结果对比,计算不一致的数量。这个验证不是为了发论文,而是为了回答「你这个情感分析到底准不准」的问题,把截图和对比表放进 PPT,导师看到你是做过验证的,这一环节就过了。
验证脚本的核心逻辑不复杂,把每条评论的人工标签和模型标签放在一张表里,计算一致性比例。我当时跑出来的准确率在 80% 上下,这个数字不算惊艳但完全够用,关键在于你能够解释清楚误差来源和局限。
答辩演示时还有两个技巧值得照做。其一是演示前把数据的查询结果缓存住,不要现场跑完整的分析流程——如果你从清洗到情感分析到图表展示都现场执行,一旦某一步因为电脑性能或环境问题慢了几秒,注意力就被打断了。其二是对比图表的顺序安排,先展示整体情感分布的饼图,再展示负面评论占比最高的区域柱状图,最后落到词云和具体评论案例,形成一个从宏观到微观的讲述线。导师顺着这条线走,每一个问题你都有对应的图表和数据支撑。
另外在演示中建议主动提一句「项目代码里包含了较为完整的注释,方便后续扩展功能」,这句话成本低但效果好。导师如果课后想翻代码,注释完整度直接影响他对你这个毕设工作量的评价。
我从那次答辩以后养成了一个习惯:任何分析类的项目,跑通主流程之后,第一件事就是固化运行脚本的顺序并做全量重跑验证,确认每一步的输出和数据库、图表完全一致后再考虑打包交付。大众点评这个项目从数据清洗到情感分析再到可视化看板,完整流程并不复杂,真正的功夫都花在细节上。希望这份拆解能帮到正在做这个题目或者准备选这个题目的你。
本文还有配套的精品资源,点击获取