ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于Python的大众点评情感分析与数据可视化系统

2026/10/3 7:47:50 拓冰建站 浏览量
基于Python的大众点评情感分析与数据可视化系统 简介这套基于Python的大众点评数据可视化和情感分析系统是一份面向高校毕业设计、期末大作业及课程设计的项目源码与答辩PPT合集。它覆盖从大众点评数据采集清洗、可视化展示到情感分析建模的完整流程特别适合需要快速搭建可演示项目的Python学习者。资源压缩包约26.35MB以Python代码文件与PPT演示文稿为主要构成代码内附详细注释从数据读取、图表绘制到情感判别都有清晰标注新手也能对照理解每一步实现逻辑部署门槛较低。项目描述中标明为个人手打的98分高分项目并有导师认可度背书目前已有207人学习可直接用于毕设开题、答辩演示、期末大作业或课程设计的参考与二次开发。对于正在准备毕业设计或课程答辩的学习者这份资料能同时覆盖代码实现与汇报展示省去从零搭建的重复工作。1. 这套基于 Python 的点评情感分析系统难的不是模型而是把数据串起来看到“基于 python 的大众点评数据可视化和情感分析系统的设计与实现代码PPT”这个标题第一反应是又一个标准的毕设选题爬一批评论算个情感分画几张图套个 Web 壳子答辩时能跑起来就行。但我带过的学生里十个有八个卡死在同一个地方——不是情感分析模型不会调而是爬下来的数据根本不能用或者图表在前端渲染不出来最后只能拿一份假数据硬演。这套系统的本质是一个数据闭环爬虫把公开评论抓下来清洗后进数据库情感分析模型给每条评论打分再通过 ECharts 把维度变成可视化图表最后由 Flask 把这些环节串成一个网页应用。它解决的是“从数据到结论”的完整链路适合正在做毕设、课设需要一份能演示、能答辩、代码结构清晰的同学参考。接下来我按自己实际做过的方式把这条链路拆开讲。2. 技术选型先于编码Flask、SnowNLP、ECharts 各自管哪一段2.1 这套系统的四层架构每一层为什么选它先把系统拆成四层数据采集层、数据处理层、分析层、展示层。每一层的选型决定了后面代码的写法和踩坑位置。数据采集层用 requests BeautifulSoup 就够。大众点评的页面是服务端渲染和接口渲染混合requests 拿到页面后解析 HTML比 Selenium 快也稳。Selenium 能躲过部分动态加载问题但启动浏览器吃内存、运行慢答辩演示时一旦无头模式崩了很难看。用 requests 的代价是部分评论藏在异步接口里需要抓包找接口这个后面在爬虫章节细说。数据处理层用 pandas没有第二个选择。去重、过滤、文本清洗、分组统计pandas 的 DataFrame 操作比纯 Python 列表省一半代码。数据库用 SQLite不是 MySQL——毕设系统并发量接近零SQLite 单文件备份方便答辩前把数据库文件拷到演示机器就能跑不用装服务。如果你导师要求 MySQL就把连接方式换掉SQL 语句几乎不用改。情感分析层用 SnowNLP 而不是深度学习模型。很多人一上来就上 BERT、LSTM结果环境配置就耗掉两周显卡也没有最后跑出来效果还不如 SnowNLP。SnowNLP 是纯 Python 实现pip 安装即可自带基于电商语料训练好的模型也支持用自己的语料重新训练。它输出 0 到 1 之间的情感得分接口简单适合这个量级的项目。精度确实有上限但毕设要求的是完整流程不是论文级准确率。展示层用 ECharts。对比 Matplotlib 和 PlotlyECharts 是前端图表库图表是 JavaScript 渲染的能缩放、能下钻、能联动交互感和答辩效果完全不是一个级别。Flask 后端把统计数据输出成 JSON前端 jQuery 拉数据、填 ECharts 配置项整个套路在中文博客里资料极多卡住了基本都能搜到答案。这套组合的主线是Python 写爬虫做数据清洗pandas 做统计snownlp 算情感分Flask 提供查询接口ECharts 画图。系统设计上坚持“每层只干一件事”答辩时被问到扩展性你可以说把 SnowNLP 换成深度学习模型不影响上面图表层这句话在答辩现场很加分。2.2 数据库表设计与前后端数据流先把数据流画出来再写代码我见过太多学生一上来就写爬虫爬到一半发现字段不够用回头改表结构爬虫重写。正确顺序是先设计表再写爬虫。两张表足够店铺表和评论表。店铺表存店铺基本信息评论表存评论文本、评分、发布时间和情感分析结果。两张表通过 shop_id 关联统计维度就齐了。建表语句直接写在 SQLite 命令行或 Python 里都行。下面是用 sqlite3 建表的脚本核心是字段类型的选择——评论 ID 用 TEXT 而不是 INTEGER因为大众点评的评论 ID 是字符串评分字段用 REAL因为爬下来的评分可能带 .5 这样的半星。CREATE TABLE shop ( shop_id TEXT PRIMARY KEY, name TEXT NOT NULL, address TEXT, avg_price REAL, rating REAL, review_count INTEGER ); CREATE TABLE review ( review_id TEXT PRIMARY KEY, shop_id TEXT NOT NULL, user_id TEXT, content TEXT NOT NULL, rating REAL, publish_time TEXT, sentiment_score REAL DEFAULT 0.5, sentiment_label TEXT DEFAULT 中评, FOREIGN KEY (shop_id) REFERENCES shop(shop_id) );注意 review 表里我加了 sentiment_score 和 sentiment_label 两个字段。很多实现把情感分析结果单独建一张表但单机 SQLite 里没必要——评论表加两个字段分析完 UPDATE 写回去查询时直接取和店铺表 JOIN 一次就能出全部维度。表结构越简单后面可视化查询就越省事。前后端数据流是这样的Flask 路由收到前端请求 → 查询 SQLite → pandas 做聚合 → 转 JSON 返回 → 前端 ECharts 渲染。举个例子要画“好评率随时间变化”的折线图Flask 返回的 JSON 结构是{ dates: [2024-01, 2024-02, 2024-03], positive_rate: [0.62, 0.58, 0.71] }ECharts 需要的就是这种两个对齐数组的结构。你在写后端接口之前先想清楚前端需要什么结构能把联调时间省掉一半。这是我从翻车经历里总结的教训——之前我把聚合结果塞进嵌套对象里前端 JS 解了两天才对上线。3. 爬虫与数据清洗从大众点评拿到一批能用的评论3.1 请求与解析一条评论怎么从页面变成 DataFrame大众点评的反爬在业内是出了名的这决定了爬虫部分只能讲思路和最小实现你要做好“改参数、试阈值、被限制就停一停”的准备。先说合规前提只爬公开评论控制频率用于学习研究不二次分发这是底线。请求层的关键参数是 headers 里的 User-Agent、Cookie以及请求间隔。大众点评对无 Cookie 的匿名请求基本直接弹验证码所以正确姿势是从浏览器登录后复制 Cookie 到代码里模拟已登录用户访问。User-Agent 别用默认的 Python-requests拼一个完整的浏览器 UA。下面是按店铺 ID 抓取全部评论的简化版核心逻辑是翻页循环 失败重试 间隔等待。大众点评的评论列表是动态加载直接解析 HTML 翻页会失效常见做法是先打开浏览器的开发者工具在 Network 面板里找到返回评论 JSON 的接口再让代码模拟请求那个接口。下面代码里我按常见的 review_all 页面结构写的你实际开发时需要按抓到的接口替换 URL 和字段名。import requests import time import pandas as pd from bs4 import BeautifulSoup session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Cookie: 你的登录 Cookie从浏览器开发者工具里复制, Referer: https://www.dianping.com/, }) def fetch_reviews(shop_id, max_pages10, delay3): 抓取店铺评论max_pages 控制最大页数delay 为请求间隔秒数 rows [] for page in range(1, max_pages 1): url fhttps://www.dianping.com/shop/{shop_id}/review_all/p{page} try: resp session.get(url, timeout10) if resp.status_code ! 200: print(f第 {page} 页请求失败状态码: {resp.status_code}) break soup BeautifulSoup(resp.text, html.parser) # 解析评论内容选择器以实际页面为准这里演示常用路径 for item in soup.select(div.review-item): content item.select_one(div.review-text span) rating item.select_one(span.review-score) if content: rows.append({ review_id: item.get(data-reviewid), content: content.get_text(stripTrue), rating: float(rating.get_text()) if rating else None, }) except requests.RequestException as e: print(f第 {page} 页异常: {e}, 准备重试) time.sleep(delay * 2) continue time.sleep(delay) # 核心参数控制爬虫频率别调太小 return pd.DataFrame(rows) df fetch_reviews(shop123456, max_pages5, delay3) print(f共抓取 {len(df)} 条评论)逻辑说明用同一个 requests.Session 保持连接和 Cookie避免每次请求重新握手time.sleep(delay) 是防止被封的关键参数3 秒只是下限你如果发现被封就加到 8 秒以上页面解析用 BeautifulSoup 的 select 方法写 CSS 选择器比 find 链式调用可读性好。这里还有两个容易被忽略的细节一是 max_pages 要按评论总数动态算不要写死二是失败重试时 sleep 要加倍给风控降火的时间。3.2 清洗四步去重、去标签、转简体、过滤短文本爬下来的是原始文本直接拿去算情感分数会翻车。我遇到过的真实情况同一条评论被不同入口重复抓了三次评论里夹着 【已屏蔽】 这样的系统提示营销账号刷的短评“好好好好”全被分成好评。清洗流程我固定跑四步。第一步是去重按内容哈希去重比按文本完全匹配更可靠因为同一句话可能多了个空格就是两条。第二步是去 HTML 标签和不可见字符BeautifulSoup 解析过的文本里还经常残留 \u3000 全角空格和尾部换行。第三步是繁体转简体部分老用户写繁体SnowNLP 对繁体效果差。第四步是过滤去掉内容少于 5 个字的去掉纯表情、纯数字的。下面是清洗函数注意参数都是按大众点评评论的实际噪声调的。import re def clean_review(text): 清洗单条评论文本返回干净的中文文本 if not isinstance(text, str): return # 第一步去 HTML 标签 text re.sub(r[^], , text) # 第二步去全角空格、零宽字符、换行合并 text text.replace(\u3000, ).replace(\u200b, ).strip() # 第三步只保留中英文、数字、常用标点其余一律删掉 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、%n一-龥], , text) # 第四步合并连续空白和换行 text re.sub(r\s, , text) return text def clean_review_df(df): 对评论 DataFrame 执行完整清洗流程返回去重后的表 df df.dropna(subset[content]) df[content] df[content].apply(clean_review) # 过滤短文本少于 5 个有效字符的评论去掉 df df[df[content].str.len() 5] # 去重按内容去重保留第一条 df df.drop_duplicates(subset[review_id], keepfirst) df df.drop_duplicates(subset[content], keepfirst) return df.reset_index(dropTrue)参数说明正则里 \u4e00-\u9fa5 保留中文a-zA-Z0-9 保留英文数字过滤阈值 min_len5 是经验值低于 5 个字的评论大多是“不错”“很好”这类噪声去掉的 \u200b 是零宽空格浏览器粘贴文本时经常带进来。核心思路是清洗宁可多删不可漏删脏数据进模型会把整个情感分布拉偏。清洗完建议立刻做一次分布检查打印评论长度直方图、重复率、空值数量。如果重复率超过 5%说明爬虫去重逻辑有问题回查上一步。这一步多花 10 分钟能省后面分析阶段两小时的定位时间。4. 情感分析不是调一下库SnowNLP 训练、调参与打分映射4.1 默认模型在大众点评场景的偏差与验证方法SnowNLP 默认模型基于购物评论训练用在大众点评会遇到一个尴尬问题餐饮场景里“分量少”“上菜慢”这些词模型可能给出偏正面的分数因为购物语料里“少”出现在“活动少领一次”这类语境中。先做一个baseline拿清洗后的 100 条评论人工打标再跑默认模型看准确率。我跑过的结果是默认模型在餐饮评论上准确率大概 60%乍看还能接受但坏在系统性偏差——对“环境好”给高分对“味道一般”也给高分因为“一般”在购物语料里不算强负向。做可视化的情感分布图时你会发现好评率普遍偏高这就是模型和场景不匹配的表现。验证方法的代码很简单用人工标注的少量样本去对from snownlp import SnowNLP def annotate_sample(labeled_reviews): labeled_reviews: [(评论, 人工标签), ...]标签取 0 或 1 preds, truths [], [] for text, truth in labeled_reviews: score SnowNLP(text).sentiment pred 1 if score 0.6 else 0 preds.append(pred) truths.append(truth) accuracy sum(p t for p, t in zip(preds, truths)) / len(truths) return accuracy逻辑说明把情感得分阈值暂定 0.6高于算正面低于算负面再和人工标注比。这个函数的核心价值是量化默认模型的偏差。如果准确率低于 70%就走下一步重训模型如果高于 75%说明你的数据场景和购物语料比较接近可以继续用默认模型。4.2 用自定义语料重训模型训练代码与参数说明SnowNLP 的情感模块自带训练和保存接口不需要改源码。你需要准备两个文件pos.txt 和 neg.txt一行一条评论UTF-8 编码。正样本可以从你清洗好的数据里挑人工标注为好评的评论负样本同理。数量上各有 500 条就够起步我建议至少有 1000 条效果会明显稳定。注意一个参数训练语料要覆盖目标场景的典型表达。餐饮场景的正样本里要有“入味”“新鲜”“分量足”负样本要有“排队太久”“服务员态度差”“吃完拉肚子”。如果语料里全是抽象的“好”“差”模型学不到场景特征训练完效果可能比默认模型还差。训练和保存代码from snownlp import sentiment # 训练模型输入正负样本文件路径 sentiment.train(data/pos.txt, data/neg.txt) # 保存到指定路径生成 .marshal 文件 sentiment.save(data/sentiment.marshal) # 下次启动系统时直接加载不用重新训练 sentiment.load(data/sentiment.marshal)参数说明train 方法读取两个文件逐行作为训练样本内部使用贝叶斯分类器没有额外超参数可调。关键参数其实是文件本身——编码必须是 UTF-8否则中文会乱码每条样本不要带换行和多余空格正负样本量尽量均衡我试过正样本 2000 条、负样本 300 条训练出来的模型严重偏向好评。保存后生成的 .marshal 文件可以随项目分发答辩机器上不用重新训练。训练完立刻验证拿训练集里你没见过的新评论试比如“这家店换了厨师味道大不如前”。如果分数明显偏高说明负样本不够或不够典型补充后再训练。4.3 从情感得分到可视化评分阈值映射与星级交叉验证模型输出 0 到 1 的连续值做可视化之前要映射成类别。常见做法是设两个阈值0.6 以上好评、0.4 以下差评、中间中评。这两个阈值不是拍脑袋定的我建议根据验证集分数分布来调画出所有评论的情感得分直方图找两个明显的分界点。如果你的数据分布右偏阈值可以往 0.65 上调。映射代码如下def sentiment_to_label(score): 把情感连续值映射为三档标签阈值是调出来的 if score 0.6: return 好评 elif score 0.4: return 差评 else: return 中评 # 批量计算并写回 DataFrame df[sentiment_score] df[content].apply(lambda x: SnowNLP(x).sentiment) df[sentiment_label] df[sentiment_score].apply(sentiment_to_label) # 按店铺聚合用于可视化 shop_stats df.groupby(shop_id)[sentiment_label].value_counts(normalizeTrue).unstack()参数说明这里有个容易踩的坑——不要在清洗后的 DataFrame 里反复调 SnowNLP先一次性算完分数存回 review 表之后所有查询直接读 sentiment_score 字段。评论多的时候一条条调 SnowNLP 很慢1000 条评论可能要几十秒先算后存是常规操作。星级交叉验证是我强烈建议加的一步。大众点评每条评论自带用户打的星级1-5星星级和情感分数理论上应该一致。你可以画一个散点图横轴是星级纵轴是情感分数。如果大量 1 星评论的情感分数超过 0.6说明模型偏差还很大反过来如果 5 星评论情感分数普遍低于 0.5也要警惕。这个交叉验证在答辩时很有说服力导师问“你凭什么相信你的情感模型”你就可以指着这张图说模型的输出和用户真实评分趋势一致。5. 系统联调的六个坑从数据采集到图表展示的排查清单5.1 爬虫被封Cookie 过期与请求频率过高现象爬虫跑了几分钟后请求返回的页面里没有评论内容取而代之的是验证码跳转页或者 HTTP 状态码变成 403。原因最常见的是两种。第一种是 Cookie 过期大众点评的登录态有效期短爬取时间超过几十分钟就会失效。第二种是频率过高连续请求间隔小于 1 秒时IP 会被风控标记表现就是突然所有请求都返回验证码页。解决Cookie 过期就把浏览器重新登录后复制新 Cookie写进配置文件而不是硬编码在代码里方便替换。频率问题把 time.sleep 加到 3 秒以上每次只跑一个店铺跑完休息 30 秒。还有一招是随机化请求头每次请求用不同的 UA。我不会建议你用代理那类的服务控制频率才是治本的做法。5.2 SnowNLP 训练后分数全部接近 0.5现象用自定义语料训练、加载后所有评论的情感分数都在 0.45 到 0.55 之间模型完全失效。原因pos.txt 和 neg.txt 内容高度相似或者加载路径不对。我遇到过学生把正负样本放在了同一个目录两个文件内容几乎一样分类器学不到特征还有人是保存后没退出 Python 进程直接加载加载的老模型还在内存里。解决先检查样本文件随机打印各 20 条确认正负语义明显对立。然后确认 save 和 load 用的是同一个 .marshal 文件路径。最后做一次最简单的冒烟测试用逻辑明确的句子验证比如“这家的菜非常难吃不会再来了”要输出低分。如果这条都不过直接删掉 .marshal 文件重新训练别在坏模型上继续调。5.3 ECharts 图表不显示白屏且无报错现象打开页面图表区域是空的浏览器控制台也没有明显报错。原因最常见的是数据接口返回的不是合法 JSON或者返回结果里字段名和 ECharts 配置里不一致。Flask 返回的 JSON 里如果包含 NaN 这种 Python 浮点值前端 JSON.parse 会直接失败而无报错白屏往往是因为你用了 data 属性但变量没赋值。解决先把后端接口在浏览器地址栏直接打开确认返回结构是否和预期一致。然后给前端 jQuery 的 ajax 回调里加 console.log 打印返回数据。核心排查逻辑是分开验证先单独把 JSON 数据粘贴进 ECharts 官方示例里如果图表正常说明问题是前后端数据传递如果不正常说明后端数据结构有问题。NaN 的问题可以在 Flask 里把聚合结果填充为 0 后再转 JSON。5.4 Flask 返回中文乱码现象接口返回的中文评论显示成 \u5e97\u5bb6 这样的 unicode 转义序列或者浏览器直接显示问号。原因Flask 的 jsonify 在默认配置下会把中文转成 unicode 转义这是正常的前端拿到后能正确解析。真正的乱码多半是数据进了 MongoDB 或 SQLite 时编码没统一读取出来就是乱码。解决先确认数据库连接字符串里加了 charsetutf8SQLite 不需要。再确认前端页面 meta 标签声明了 charsetutf-8如果 HTML 里没这个声明现代浏览器默认按 UTF-8 解析但有些老版本会按 GBK 解析导致乱码。最稳的做法是 Flask 侧用 jsonify 返回前端用 JSON.parse 解析不手工拼接字符串。如果实在要在 Python 侧手动构造 JSON 字符串记得加 ensure_asciiFalse。5.5 数据库里出现重复评论现象爬虫跑完两轮后数据库里同一 review_id 出现多条统计结果比实际评论量虚高。原因爬虫在翻页时页面前半部分和上一页后半部分重叠如果程序重跑中间断了再续跑重复数据就进去了。解决把 review_id 设置为主键插入用 INSERT OR IGNORE让数据库天然去重。下面是修正后的插入逻辑import sqlite3 conn sqlite3.connect(data/reviews.db) for _, row in df.iterrows(): conn.execute( INSERT OR IGNORE INTO review (review_id, shop_id, content, rating) VALUES (?, ?, ?, ?), (row[review_id], row[shop_id], row[content], row[rating]) ) conn.commit()参数说明INSERT OR IGNORE 是 SQLite 的特性遇到主键冲突就跳过不报错。这样即使爬虫重复请求同一页也不会产生重复数据。如果你用 MySQL对应的语法是 INSERT IGNORE。这个改动成本极低但能避免后续所有统计问题。5.6 数据量大时情感分析跑得极慢现象爬了上万条评论调 SnowNLP 算分数跑了十几分钟。原因SnowNLP 是纯 Python 实现每条评论要做分词、词性标注、贝叶斯推断单条几十毫秒一万条就是几分钟到十几分钟。解决三步——先只对清洗后的去重数据计算别对原始表跑其次用多进程加速进程数设为 CPU 核心数最后把计算结果直接写库不要每次启动系统都重新算一遍。下面给一个多进程示例from multiprocessing import Pool def calc_score(text): 多进程工作函数必须放在模块顶层 return SnowNLP(text).sentiment with Pool(processes4) as pool: df[sentiment_score] pool.map(calc_score, df[content].tolist())逻辑说明Pool.map 会把内容列表分块交给多个进程并行处理进程数 processe4 表示用 4 个核。注意工作函数必须放在模块顶层不能写在 main 函数里否则多进程会报错。Pool 的进程数不要超过 CPU 物理核数否则内存会翻车。6. 演示与验收答辩前按这个清单走一遍能少丢一半分最后一章不聊新功能讲怎么把前面做的东西变成一个能打动人、能扛住提问的完整演示。很多学生代码写完了答辩现场却被问得说不出话问题大多出在演示路径和验证逻辑上。第一件事演示环境要准备一份固定数据。我见过最惨的情况是答辩现场爬数据爬到一半被验证码拦了整场演示变成看进度条。正确做法是提前把清洗好的数据导入 SQLite答辩时全程用本地数据不触发任何网络请求。被打断问“这是实时数据吗”你可以诚实地说这是前期采集并清洗好的数据样本系统也支持增量更新。第二件事设计演示顺序时先做错位对比。先展示“好评率最高店铺排行”这个一眼能得出结论的图表然后展示同一家店铺在不同月份的情感得分变化曲线最后再说“我们把模型输出的情感分数和用户星级做了交叉验证”。这个顺序是在讲故事先看整体再看趋势最后证明模型可信。不要在演示开场就讲情感分析原理那会把场面弄昏。第三件事给 PPT 放系统架构图不要放代码截图。架构图四层就能说清楚每层标上你用的技术名字评论区经常有要架构图的这张图同时也能帮你兜底——导师问任何一层的技术细节你都能顺着图展开讲。收尾前做一个简单验证把验证集的评论按人工标签和模型预测算准确率哪怕只做 50 条的小样本也能算出大概的准确率数字这是导师最爱问的问题之一。我习惯在答辩前自己当评委把系统从头到尾走一遍流程每个按钮点一次每个接口用浏览器访问一次确认没有白屏、没有接口报错。这套流程帮我避开了至少三次当场翻车希望也能帮到你。本文还有配套的精品资源点击获取