在实际的商业分析、游戏运营和毕业设计选题中,评论数据一直是被反复提起但又不好落地的数据源。以“黑悟空评论数据分析系统(项目编号 0180)”为例,这个课题并不是简单写一个爬虫,而是要把“评论采集、数据清洗、情感分析、统计聚合、可视化展示”串成一条完整的处理链路。整条链路跑通之后,用户可以看到评论量趋势、好评率变化、玩家高频关注点和情感倾向分布,进而为游戏口碑跟踪和运营决策提供依据。
这篇文章围绕一个可演示、可复现的评论数据分析系统展开,重点讲清四个问题:系统由哪些模块组成、每条数据从采集到展示经过哪些环节、关键代码和参数怎么设计、演示环境出问题时从哪里查起。整篇文章使用的技术方案适合本科毕业设计、课程实训和小型数据分析项目参考,读者可以按顺序把系统搭出来,并在答辩时解释清楚每个模块的设计理由。
1. 评论数据分析系统的需求拆解与技术选型
1.1 毕业设计演示要解决什么问题
毕业设计项目的演示效果,往往不在功能列表有多长,而在数据是否能从原始状态变成有价值的分析结果。很多同学拿到“评论数据分析系统”这个题目后,第一反应是“先写爬虫抓评论”,但抓下来之后发现数据是 JSON、是英文、是重复的,甚至夹杂大量无意义短文本,根本没法直接分析。真正的问题不是“缺数据”,而是“数据到知识的转换链路不完整”。
这个项目的核心目标是:把《黑神话:悟空》在 Steam 平台上的玩家评论作为分析对象,完成以下闭环:
- 采集评论原始数据,保存到关系型数据库。
- 对评论文本做去重、过滤、长度筛选。
- 统计评论的时间分布、推荐状态占比、评论长度等基础画像。
- 对中文评论做情感打分,判断玩家对游戏的整体情绪。
- 使用分词和关键词提取,找到玩家讨论的高频主题。
- 通过 Web 页面展示统计指标和图表,形成看板。
整个系统的亮点不在某一个算法有多深,而在每一层都有明确产出。评审老师问“数据从哪里来、结果怎么算出来、图表怎么渲染”时,你能从数据库表一路讲到 ECharts 页面,这比只贴一个爬虫代码要完整得多。
1.2 技术栈选型
技术选型的核心原则是“学习成本低、演示效果好、每个组件都能说明白”。本系统建议使用 Python 生态,原因有三:采集脚本容易写,pandas 处理表格数据方便,SnowNLP、jieba、wordcloud 这些中文文本处理库的安装和使用都很直接。
| 模块 | 技术选型 | 作用 |
|---|---|---|
| 数据采集 | Python + requests | 调用 Steam 官方评论接口,获取评论 JSON |
| 数据存储 | MySQL 8.0 | 保存评论明细和去重标记 |
| 数据处理 | pandas + numpy | 清洗、去重、聚合统计 |
| 情感分析 | SnowNLP | 对中文评论计算情感得分 |
| 分词与关键词 | jieba + jieba.analyse | 提取评论高频词与主题关键词 |
| 词云生成 | wordcloud + matplotlib | 生成词云图片用于看板展示 |
| Web 服务 | Flask | 提供统计接口和页面渲染 |
| 前端图表 | ECharts | 展示折线图、柱状图、饼图、仪表盘 |
这里需要解释一下为什么选择 MySQL 而不是 SQLite。SQLite 在本地调试时确实省事,但毕业设计评阅时,老师通常希望看到“数据库设计”和“SQL 语句”,MySQL 的多表查询、索引、字符集配置也更容易讲出工程味道。如果演示机器上没有 MySQL 服务,可以使用 Docker 运行一个 MySQL 8.0 容器,这样环境干净、卸载方便。
1.3 数据流与模块划分
系统的数据流向按“源数据 -> 明细数据 -> 特征数据 -> 展示数据”四层划分,每一层对应独立的代码目录和功能模块。
第一层是采集层。采集器请求 Steam 评论接口,拿到评论列表后解析出作者、正文、时间、推荐状态、点赞数等字段,写入 MySQL 的wukong_comments表。
第二层是清洗层。使用 pandas 从数据库读入 DataFrame,删除评论字段为空的记录,按评论内容的 MD5 值去重,过滤过短文本,最后把清洗结果写回分析表,或者直接在 DataFrame 中继续分析。
第三层是分析层。对日期列做按月或按周聚合,统计推荐与不推荐的数量,计算好评率,运行 SnowNLP 对每条评论做情感打分,再使用 jieba 抽取 TOP 关键词。
第四层是展示层。Flask 提供/api/overview、/api/trend、/api/keywords等 JSON 接口,前端页面通过 ECharts 请求这些接口并渲染图表。
这样划分之后,每一层都能独立测试。采集层跑完,先看看数据库里有多少条记录;分析层跑完,先打印几个统计数字;展示层做出来之后,整个系统的演示流程就自然成立了。
2. 环境准备与项目骨架搭建
2.1 环境要求
开发机建议使用 Windows 10/11 或 Linux,Python 版本选择 3.9 或 3.10。这里要特别提醒:SnowNLP 在更高版本 Python 下通常也能用,但为了避免依赖编译问题,建议使用 3.10 以下版本。如果原始部署环境只支持 Python 3.11 以上,先测试pip install snownlp是否能成功,再继续后面的环节。
| 依赖 | 建议版本 | 用途 |
|---|---|---|
| Python | 3.9 / 3.10 | 主开发语言 |
| requests | 2.31.x | 调用评论接口 |
| pandas | 2.0.x | 数据处理 |
| PyMySQL | 1.1.x | MySQL 连接 |
| Flask | 3.0.x | Web 服务 |
| snownlp | 0.12.3 | 中文情感分析 |
| jieba | 0.42.1 | 中文分词与关键词 |
| wordcloud | 1.9.x | 词云生成 |
| matplotlib | 3.7.x | 词云底图渲染 |
创建虚拟环境并安装依赖的命令如下:
mkdir wukong-comment-analysis cd wukong-comment-analysis python -m venv venv # Windows venv\Scripts\activate # Linux / macOS source venv/bin/activate pip install requests pandas pymysql flask snownlp jieba wordcloud matplotlib安装完成后,用pip list检查版本。如果 wordcloud 安装失败,通常是本机缺少 C 编译器,可直接下载对应 Python 版本的.whl文件离线安装,也可以退回1.8.1版本重试。
2.2 初始化数据库
MySQL 中需要创建数据库和分析表。这里使用 Docker 启动 MySQL 最省事:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -e MYSQL_DATABASE=game_comment \ mysql:8.0等容器启动后,连接数据库执行建表 SQL。评论表的字段设计要覆盖采集接口能提供的全部关键信息,同时额外增加review_md5字段用于去重。
CREATE DATABASE IF NOT EXISTS game_comment DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; USE game_comment; CREATE TABLE IF NOT EXISTS wukong_comments ( id INT AUTO_INCREMENT PRIMARY KEY, appid INT NOT NULL COMMENT 'Steam 游戏 ID', author_id VARCHAR(64) NOT NULL COMMENT '评论者 ID', author_name VARCHAR(128) DEFAULT NULL, language VARCHAR(32) DEFAULT NULL COMMENT '评论语言', review_text TEXT NOT NULL COMMENT '评论正文', review_md5 CHAR(32) NOT NULL COMMENT '评论去重标识', timestamp_created DATETIME NOT NULL COMMENT '评论时间', voted_up TINYINT NOT NULL DEFAULT 0 COMMENT '是否推荐', votes_helpful INT DEFAULT 0 COMMENT '有帮助数', votes_funny INT DEFAULT 0 COMMENT '有趣数', weighted_vote_score FLOAT DEFAULT 0 COMMENT '加权评分', sentiment_score FLOAT DEFAULT NULL COMMENT '情感得分', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_review_md5 (review_md5), KEY idx_time (timestamp_created), KEY idx_voted_up (voted_up), KEY idx_sentiment (sentiment_score) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='黑神话悟空评论明细表';关键点是review_md5的唯一索引。评论接口在翻页过程中可能出现重复数据,靠这条唯一索引可以保证重复记录直接插入失败,配合INSERT IGNORE或ON DUPLICATE KEY UPDATE实现幂等写入。
2.3 项目目录与配置
目录结构按“采集、清洗、分析、展示、脚本”拆分,每个模块职责清晰:
wukong-comment-analysis/ ├── config.py # 数据库连接配置 ├── scripts/ │ ├── fetch_comments.py # 采集评论 │ └── generate_sample.py # 生成模拟评论数据 ├── analysis/ │ ├── clean_data.py # 清洗与统计 │ ├── sentiment_analysis.py # 情感分析 │ └── keyword_extract.py # 关键词提取与词云 ├── webapp/ │ ├── app.py # Flask 应用 │ ├── static/ # ECharts 和自定义 JS │ └── templates/ │ └── index.html # 看板页面 └── requirements.txt数据库连接配置单独放到config.py,避免每个脚本都写一遍连接参数:
DB_CONFIG = { "host": "127.0.0.1", "port": 3306, "user": "root", "password": "123456", "database": "game_comment", "charset": "utf8mb4", }实际项目如果使用远程数据库,把host和password改掉即可。生产环境不要把这些参数硬编码到代码里,建议读取环境变量,但没有项目背景时可以先把配置集中管理。
3. 评论采集:官方接口、翻页逻辑与入库
3.1 接口返回结构
Steam 提供的是公开评论接口,路径格式为:
https://store.steampowered.com/appreviews/{appid}?json=1《黑神话:悟空》的 Steam appid 是1245620。接口返回的 JSON 中包含success、cursor、reviews等字段。其中cursor是下一页的游标标记,reviews是评论数组。
单个评论对象的关键字段如下:
| 字段 | 含义 |
|---|---|
| author.steamid | 评论者 Steam ID |
| author.name | 评论者昵称 |
| language | 评论语言,如 schinese |
| review | 评论正文 |
| timestamp_created | 评论创建时间戳 |
| voted_up | 是否推荐该游戏 |
| votes_helpful | 被认为有帮助的次数 |
| votes_funny | 被认为有趣的次数 |
| weighted_vote_score | 评论加权投票分数 |
如果请求参数中设置language=schinese,接口会优先返回中文评论。但 Steam 评论接口在不同网络环境下的可达性并不稳定,如果演示机器无法访问该域名,先用本地模拟数据把流程跑通,这一步在后面会专门说明。
3.2 编写采集器
采集器要完成三件事:请求接口、解析字段、写入 MySQL。为了降低对官方接口的压力,每翻一页后休眠 1 秒。
import hashlib import time import pymysql import requests from config import DB_CONFIG APPID = 1245620 BASE_URL = "https://store.steampowered.com/appreviews/{appid}" HEADERS = { "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0 Safari/537.36" ) } def review_md5(text: str) -> str: return hashlib.md5(text.encode("utf-8", errors="ignore")).hexdigest() def insert_review(cursor, item: dict) -> None: author = item.get("author", {}) text = item.get("review", "") ts = item.get("timestamp_created", 0) created = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts)) sql = """ INSERT IGNORE INTO wukong_comments (appid, author_id, author_name, language, review_text, review_md5, timestamp_created, voted_up, votes_helpful, votes_funny, weighted_vote_score) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """ cursor.execute(sql, ( APPID, author.get("steamid", ""), author.get("name", ""), item.get("language", ""), text, review_md5(text), created, 1 if item.get("voted_up") else 0, item.get("votes_helpful", 0), item.get("votes_funny", 0), item.get("weighted_vote_score", 0), )) def fetch_comments(max_pages=20): conn = pymysql.connect(**DB_CONFIG) cursor = conn.cursor() start_cursor = "*" for page in range(max_pages): params = { "json": 1, "filter": "all", "language": "schinese", "num_per_page": 20, "cursor": start_cursor, "purchase_type": "all", } resp = requests.get(BASE_URL.format(appid=APPID), params=params, headers=HEADERS, timeout=10) data = resp.json() reviews = data.get("reviews", []) or [] for item in reviews: insert_review(cursor, item) conn.commit() print(f"page {page + 1}: fetched {len(reviews)} reviews") next_cursor = data.get("cursor") if not reviews or next_cursor == start_cursor: break start_cursor = next_cursor time.sleep(1) cursor.close() conn.close() if __name__ == "__main__": fetch_comments(max_pages=20)运行脚本后,在 MySQL 中查看数据量:
SELECT COUNT(*) FROM game_comment.wukong_comments;如果返回 0,先检查请求 URL 是否真实可达、返回 JSON 中success是否为 1。不要急着改代码,可以用下面这条命令确认接口内容:
curl "https://store.steampowered.com/appreviews/1245620?json=1&language=schinese&num_per_page=1"3.3 入库去重策略
评论去重是必须处理的细节。原因有两个:第一,官方接口在翻页时可能存在重复返回;第二,同一条评论如果多次运行采集脚本,不处理会变成脏数据。
这里的方案是给评论内容计算 MD5,作为数据库唯一键。INSERT IGNORE在遇到重复主键或唯一键时不会报错,也不会写入,所以重复采集时数据量不会虚增。
另一个细节是时间解析。接口返回的timestamp_created是 Unix 时间戳,采集时直接转成 MySQL 的DATETIME格式。如果项目后续要按周或按月聚合,日期字段必须是数据库可排序的类型,不能保留为字符串。
4. 评论清洗与基础画像分析
4.1 清洗规则
采集到原始评论后,不能直接进分析层。评论数据常见的问题包括重复文本、过短评论、非中文评论、空作者等。清洗阶段的目标是生成一个干净、可统计的 DataFrame。
清洗规则如下:
| 检查项 | 清洗策略 | 原因 |
|---|---|---|
| review_md5 重复 | 保留第一条 | 防止重复评论影响统计 |
| review_text 为空 | 删除 | 无法参与文本分析 |
| 评论长度小于 5 | 删除 | 短文本情感判断不准确 |
| 时间字段为空 | 删除 | 无法参与时间趋势分析 |
| 超出业务时间范围 | 保留并检查 | 可能是指定时间窗口外的评论 |
清洗代码放在analysis/clean_data.py:
import pandas as pd import pymysql from config import DB_CONFIG def load_data(): conn = pymysql.connect(**DB_CONFIG) sql = "SELECT * FROM wukong_comments" df = pd.read_sql(sql, conn) conn.close() return df def clean_data(df: pd.DataFrame) -> pd.DataFrame: print(f"原始数据量: {len(df)}") df = df.drop_duplicates(subset=["review_md5"], keep="first") print(f"去重后数据量: {len(df)}") df = df[df["review_text"].notna()] df = df[df["review_text"].str.strip() != ""] df["review_len"] = df["review_text"].apply(len) df = df[df["review_len"] >= 5] df["timestamp_created"] = pd.to_datetime(df["timestamp_created"]) df = df[df["timestamp_created"].notna()] print(f"清洗后数据量: {len(df)}") return df if __name__ == "__main__": raw = load_data() cleaned = clean_data(raw) print(f"清洗后剩余 {len(cleaned)} 条评论")4.2 画像指标
基础画像指标不需要太复杂,但要能从业务角度讲清楚意义。通常输出四类指标:
第一类是总体量。评论总数、清洗后评论数、采集覆盖率,用来判断样本是否足够。
第二类是推荐率。按voted_up分组统计,voted_up=1表示玩家推荐,voted_up=0表示不推荐。好评率等于推荐数除以有效评论总数。
第三类是时间分布。按天、周、月统计评论量,可以观察游戏发售、版本更新等时间点的讨论热度变化。
第四类是评论长度分布。评论长度可以粗略反映玩家表达意愿,长评论往往带有更具体的体验细节。
统计代码:
def overview_stats(df: pd.DataFrame) -> dict: total = len(df) recommended = int(df["voted_up"].sum()) not_recommended = total - recommended rate = round(recommended / total * 100, 2) if total else 0 return { "total": total, "recommended": recommended, "not_recommended": not_recommended, "recommend_rate": rate, } def trend_stats(df: pd.DataFrame) -> pd.DataFrame: df["date"] = df["timestamp_created"].dt.strftime("%Y-%m-%d") trend = df.groupby("date").agg( total=("id", "count"), recommended=("voted_up", "sum"), ).reset_index() trend["not_recommended"] = trend["total"] - trend["recommended"] return trend这段代码输出的overview_stats可以直接给 Flask 接口使用。trend_stats生成折线图所需的日期、总数和推荐数三列。
4.3 输出示例
清洗和统计完成后,可以在控制台看到类似输出:
原始数据量: 487 去重后数据量: 461 清洗后数据量: 428 清洗后剩余 428 条评论 总体概览: - 评论总数: 428 - 推荐: 366 - 不推荐: 62 - 好评率: 85.51%这里的好评率只是基于评论的统计值,不等同于游戏真实好评率,因为采集数量有限,而且语言过滤后样本并不完整。答辩时一定要说明样本口径,可以在页面标题下标注“样本数据,仅用于系统演示”。
5. 情感分析、关键词提取与领域修正
5.1 情感分析的基本思路
评论分析系统如果只统计推荐和不推荐,说服力不够。很多玩家会写“游戏很好但优化有问题”这样的句子,单纯二分类会丢失情绪倾向。所以需要给每条评论计算一个情感得分,范围在 0 到 1 之间,越接近 1 表示情感越正面,越接近 0 表示情感越负面。
这里采用 SnowNLP。它的实现原理是基于朴素贝叶斯分类器训练出的中文情感分析模型,使用非常方便:
from snownlp import SnowNLP s = SnowNLP("画面非常震撼,期待很久了") print(s.sentiments)输出通常大于 0.5,说明模型判断为正面情感。
但需要注意:SnowNLP 的训练语料以电商购物评论为主,对游戏领域词汇的覆盖不一定准确。直接用于游戏评论时,可能把“打击感不错但难度太高”判成负面。因此本系统在基础情感得分上增加领域修正规则,保证演示效果更贴近真实阅读感受。
5.2 基于 SnowNLP 的情感打分实现
打分函数放在analysis/sentiment_analysis.py:
from snownlp import SnowNLP POSITIVE_WORDS = ["惊艳", "震撼", "好玩", "推荐", "神作", "满分", "满意"] NEGATIVE_WORDS = ["卡顿", "闪退", "难受", "失望", "优化差", "退款", "差评", "bug"] def adjust_score(text: str, base_score: float) -> float: score = base_score for word in POSITIVE_WORDS: if word in text: score = min(1.0, score + 0.1) for word in NEGATIVE_WORDS: if word in text: score = max(0.0, score - 0.1) return round(score, 4) def score_comment(text: str) -> float: base_score = SnowNLP(text).sentiments return adjust_score(text, base_score) def add_sentiment_scores(df): df["sentiment_score"] = df["review_text"].apply(score_comment) return df这段代码的解释分两层:第一层是基础模型打分,SnowNLP 已经给出了 0 到 1 的分数;第二层是领域词典修正,游戏评论里的特定词能明显影响情绪判断,通过关键词加权让分数更接近玩家真实态度。
情感得分区间可以直接用于页面上的仪表盘:
| 情感得分区间 | 判断倾向 | 示例场景 |
|---|---|---|
| 0.0 - 0.4 | 偏负面 | “优化太差,频繁闪退” |
| 0.4 - 0.6 | 中性 | “还行,就是难度有点高” |
| 0.6 - 0.8 | 偏正面 | “画面不错,战斗系统有特色” |
| 0.8 - 1.0 | 正向积极 | “年度最佳,值得购买” |
5.3 关键词提取与词云
关键词提取使用 jieba 自带的关键词接口。需要过滤掉“真的、这个、一个、什么、游戏、还是”这类无信息量的词。这里准备一个自定义停用词集合,然后再调用jieba.analyse.extract_tags。
import jieba.analyse STOP_WORDS = {"这个", "一个", "没有", "什么", "就是", "真的", "可以", "还是", "我们", "你们"} def extract_keywords(df, top_n=20): text_all = " ".join(df["review_text"].tolist()) jieba.analyse.set_stop_words_from_offline_text = None keywords = jieba.analyse.extract_tags( text_all, topK=top_n, withWeight=True, allowPOS=("n", "v", "vn", "a", "ad"), ) result = [{"name": k, "value": round(w, 4)} for k, w in keywords if k not in STOP_WORDS] return result值得注意的一点:如果直接把全部评论拼接成一个超长字符串再提取关键词,高频词可能偏向“悟空”“游戏”“黑神话”这类反复出现的词。这其实是业务上的正常结果,但看板演示时重点应该是“画面、优化、战斗、剧情、打击感”这些有业务含义的词。因此可以在分析前把review_text中的“悟空”“黑神话”“游戏”“这款”等词加入停用词表,让关键词分布更真实。
词云生成部分依赖 wordcloud,中文字体在 Linux 下经常出问题,推荐在代码中显式指定字体路径:
from wordcloud import WordCloud import matplotlib.pyplot as plt import jieba def generate_wordcloud(text: str, output_path: str): wc = WordCloud( font_path="C:/Windows/Fonts/simhei.ttf", # Windows 示例路径 width=1200, height=600, background_color="white", max_words=100, ).generate(text) plt.figure(figsize=(12, 6)) plt.imshow(wc, interpolation="bilinear") plt.axis("off") plt.savefig(output_path, dpi=150, bbox_inches="tight")Linux 服务器如果没有中文字体,可以先执行fc-list :lang=zh查看是否安装了中文字体。没有的话,可以把 Windows 下的simhei.ttf上传到项目字体目录,再用绝对路径引用。这一步虽然小,但很容易在演示现场变成空白方块图。
6. 可视化看板:Flask 接口与 ECharts 页面
6.1 数据接口设计
分析结果需要通过 Web 页面呈现。Flask 的作用不是复杂业务,而是把数据库中的统计结果转换成前端可以消费的 JSON 接口。这里设计 4 个核心接口:
| 接口 | 返回内容 | 对应图表 |
|---|---|---|
/api/overview | 评论总数、推荐数、好评率、情感均值 | 顶部指标卡片 |
/api/trend | 按日期的评论量和推荐量 | 折线图 |
/api/sentiment | 情感得分区间分布 | 柱状图 |
/api/keywords | TOP 20 关键词及权重 | 横向条形图或词云 |
app.py的核心实现如下:
from flask import Flask, jsonify, render_template import pandas as pd from analysis.clean_data import load_data, clean_data, overview_stats, trend_stats app = Flask(__name__) @app.route("/") def index(): return render_template("index.html") @app.route("/api/overview") def api_overview(): df = clean_data(load_data()) return jsonify(overview_stats(df)) @app.route("/api/trend") def api_trend(): df = clean_data(load_data()) trend = trend_stats(df) data = { "date": trend["date"].tolist(), "total": trend["total"].tolist(), "recommended": trend["recommended"].tolist(), } return jsonify(data) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)接口每次请求都重新加载数据,在演示场景下够用,但生产环境必须考虑缓存。不要在前端页面刷新时反复执行全量清洗逻辑,应该把统计结果提前计算好,存入一张聚合表或 Redis 缓存中。
6.2 页面配置
前端页面采用静态 HTML 加 ECharts 的方式,避免引入大型前端构建工具。页面在templates/index.html中加载本地 ECharts 文件,再依次请求接口。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>黑悟空评论数据分析看板</title> <script src="/static/js/echarts.min.js"></script> </head> <body> <div id="overview-cards"></div> <div id="trend-chart" style="height: 400px;"></div> <div id="sentiment-chart" style="height: 400px;"></div> <div id="keyword-chart" style="height: 400px;"></div> <script src="/static/js/dashboard.js"></script> </body> </html>在dashboard.js里,用fetch拿接口数据后交给 ECharts 渲染。折线图示例:
fetch('/api/trend') .then(res => res.json()) .then(data => { const chart = echarts.init(document.getElementById('trend-chart')); chart.setOption({ title: { text: '评论量趋势' }, tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: data.date }, yAxis: { type: 'value' }, series: [ { name: '评论总数', type: 'line', data: data.total }, { name: '推荐数', type: 'line', data: data.recommended } ] }); });这里要注意:浏览器访问页面时,如果 Flask 运行在0.0.0.0:5000,演示环境可以先用浏览器打开http://127.0.0.1:5000。如果是远程服务器,要使用服务器 IP 替换127.0.0.1,并确保防火墙放行 5000 端口。
6.3 演示环境的启动顺序
演示时不要直接运行python webapp/app.py,应该按固定顺序启动,避免现场因为顺序错误导致数据为空。
第一步,确认 MySQL 容器运行,并且wukong_comments表有数据:
docker ps mysql -h127.0.0.1 -uroot -p123456 -e "select count(*) from game_comment.wukong_comments;"第二步,检查关键 Python 依赖是否齐全:
pip list | grep -E "flask|snownlp|jieba|pandas"第三步,启动 Flask:
cd wukong-comment-analysis python webapp/app.py第四步,浏览器验证:
- 打开
/api/overview,确认返回 JSON。 - 打开
/api/trend,确认时间序列有数据。 - 打开首页,确认图表正常渲染。
注意:演示时最好在本地准备模拟数据。如果现场网络无法访问 Steam 评论接口,至少保证系统界面和统计流程可以展示。
7. 常见问题、排查链路与答辩准备
7.1 高频问题对照表
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 采集脚本返回 0 条评论 | 网络不可达、语言参数无数据、appid 错误 | 用 curl 请求接口查看 JSON | 改用本地模拟数据;确认接口返回中 reviews 数组长度 |
| MySQL 插入中文乱码 | 数据库字符集不是 utf8mb4 | show variables like 'character_set_server'; | 创建数据库时指定 utf8mb4;连接参数添加 charset |
| 评论数很少,只有几十条 | 接口默认只返回当前游标前数据,采集页数太少 | 查看采集日志每页获取数量 | 调大 max_pages;使用 filter=all 获取全部类型 |
| 情感得分几乎全是 0.9 | 修正词加权过大或样本以正面评论为主 | 抽查 10 条评论的 base_score | 检查自定义词典,避免得分溢出 |
| wordcloud 生成空白方块 | 系统缺少中文字体 | fc-list :lang=zh | 指定 simhei.ttf 等中文字体路径 |
| Flask 页面图表不显示 | JS 文件路径错误、接口返回非 JSON | 打开浏览器 F12 查看 Network | 确认 static 目录和接口路径一致 |
| 重复运行采集后数据量翻倍 | 唯一键未生效或去重字段写错 | 查看 review_md5 是否唯一 | 确认建表唯一索引 uk_review_md5 |
7.2 排查链路顺序
遇到系统不工作,不要东翻西找,按以下顺序排查:
- 先确认接口数据是否正常。用 curl 或浏览器直接访问
/api/overview,如果接口返回报错,问题在后端逻辑。 - 再确认数据库数据是否为空。执行
SELECT COUNT(*) FROM wukong_comments;,如果为空,问题是采集或清洗环节。 - 检查清洗代码是否误删了大部分数据。在控制台打印清洗前后的数据量,确认过滤条件是否过严。
- 检查 Flask 启动日志。留意
ModuleNotFoundError、OperationalError、ConnectionRefusedError,第一行异常通常就是根因。 - 最后检查前端控制台。如果接口正常但页面空白,优先查看 JS 报错和请求路径。
一个非常容易踩的坑是:在清洗代码中把timestamp_created列直接覆盖成pd.Timestamp对象,然后写回数据库时失败。如果只是展示统计结果,不需要把清洗后的 DataFrame 写回数据库;如果必须写回,需要先把时间列格式化为字符串。
7.3 答辩讲解主线
答辩时,老师通常不会只问“做了哪些功能”,更关心设计决策和问题意识。建议按以下主线讲解:
先讲背景。“黑神话:悟空”上线后讨论热度很高,玩家评论是口碑分析的重要信息来源,但评论量大、文本不规范,需要一套自动化系统处理。
再讲数据链路。从 Steam 评论接口采集到 MySQL,通过 pandas 清洗,使用 SnowNLP 打分,再通过 jieba 提取关键词,最终用 Flask 和 ECharts 展示。
接着讲关键技术点。重点解释review_md5的去重逻辑、SnowNLP 情感得分如何修正、时间序列怎么聚合。
然后讲实验过程。说明采集了多少条评论、清洗后剩多少、好评率是多少、情感分布长什么样,并坦白说明样本限制。
最后讲不足与改进。可以提到深度学习情感分类、多平台数据接入、自动定时增量采集等方向。这样回答既有深度,又显得诚恳。
7.4 可扩展的方向
这个系统改成毕业设计论文后,仍然有明显的扩展空间。
第一个方向是数据源扩展。当前只接了 Steam 评论,后续可以接入 TapTap、贴吧等渠道,不过要确认平台是否允许采集,避免合规风险。多源数据需要先统一字段格式,再进入同一套分析流程。
第二个方向是增量采集。当前脚本是手动一次性运行,生产环境应使用定时任务每日采集新增评论,并记录最近一次采集游标,避免重复拉全量数据。
第三个方向是模型升级。SnowNLP 属于基础模型,准确率有限。可以收集一批游戏领域标注数据,微调文本分类模型;也可以接入大模型做摘要和细粒度情感识别,但生产成本会明显增加。
第四个方向是报表能力。可以把每日统计结果写入 MySQL 聚合表,加入日期筛选、对比分析和 PDF 导出,让系统从个人分析工具变成运营报表平台。
建议:毕业设计阶段先把“数据采集、清洗、情感分析、可视化展示”这条主线做扎实,不要同时铺开太多功能。功能太多反而会让系统看起来像功能堆砌,缺少核心分析深度。
整个项目的核心收获不在于“爬了多少条评论”,而在于建立了一个完整的数据分析流程思维。采集、清洗、分析、展示中的任何一环出问题,都可能让最终看板失真。把每一步的输入输出都确认清楚,这个系统就能稳定复现。后续如果要在真实运营场景中投入使用,可以再补充监控、缓存、增量调度和生产级部署,当前版本的架构已经为这些扩展预留了清晰位置。