
做数据分析类项目的时候很多人容易陷入两个极端要么只写一个 Jupyter Notebook把数据下载下来随便画两张图要么纠结于爬虫能不能爬到数据、MySQL 怎么安装结果半天过去了还停留在环境搭建。真正能让简历加分的往往不是炫酷的算法而是“数据从哪里来、如何清洗、怎么入库、用什么思路分析、最终如何落到业务结论”这一整条链路。这篇文章我准备完整拆解一个基于 Python MySQL 的“泡泡玛特热搜评论数据分析”实战项目。项目会覆盖模拟数据生成、Pandas 数据清洗、MySQL 建库建表、Python 批量入库、SQL 分组统计、可视化输出等完整流程。学完后你不仅能独立跑通这个项目还能把里面的模块迁移到其他评论分析、舆情分析、商品反馈分析场景里。文章适合以下读者学过 Python 基础但还没真正做过一个完整数据分析项目的初学者熟悉 pandas/MySQL 单个组件但不清楚怎么组织项目结构的开发者准备把数据分析项目写进简历想找一个有业务感、有完整技术链路的案例的求职者。顺便说明一点真实业务中的“热搜评论”一般来自公开平台或企业授权的数据接口采集过程需要遵守平台规则和法律法规。本文为了让你能无门槛跑通项目会使用本地脚本生成模拟数据完整演示清洗、入库、查询与分析思路。如果后续有条件接入合规数据源只要替换数据文件即可。1. 项目背景与核心概念1.1 为什么要分析“热搜评论”热搜评论本质上是一种短文本舆情数据。对于一个消费品牌来说用户在某条热搜话题下的评论往往能直接体现大家对新品、价格、品控、联名、补货等问题的态度。比如“泡泡玛特新品首发”这条热搜下有人讨论外观设计有人吐槽线下排队也有人关心隐藏款概率。把这些零散评论汇集起来再做结构化分析就能得到几个很有业务价值的信息用户最关心什么关键词正面、负面、中性评价分别占多少点赞比较高的优质评论都在聊哪些内容不同时间段内评论数量的波峰和波谷分别什么时候出现。这类分析虽然不算高深但在品牌市场、电商运营、售后策略等场景里非常普遍也是数据分析岗位面试中经常出现的“业务分析题”。1.2 Python 和 MySQL 在项目里的分工很多初学者会把 Python 和 MySQL 对立起来认为既然 pandas 能做统计为什么还要装 MySQL其实在真实项目中两者不是替代关系而是分工关系。Python 擅长的是处理不规则的原始数据文本清洗、去重、分箱调用第三方分词库绘制可视化图表。MySQL 擅长的是对已经结构化后的明细数据做持久化存储支持多表关联查询按时间、来源、情感等维度快速聚合方便业务报表工具或后端程序直接读取数据。所以这个项目的定位是Python 负责“数据进”和“数据出”MySQL 负责中间过程的“数据存”和“数据算”。SQL 分析是项目亮点之一它比 Excel 筛选更适合表达多维统计逻辑。1.3 项目目标和简历价值本项目的目标可以拆成六个步骤生成一批包含热搜话题和评论内容的模拟数据用 Pandas 对原始评论做清洗设计 MySQL 表结构通过 PyMySQL 把数据写入 MySQL用 SQL 完成多维度统计分析用 Matplotlib 输出可视化图表。写成简历时你的描述可以更有业务感“基于 Python MySQL 完成泡泡玛特热搜评论数据清洗、入库与多维度分析设计话题、评论双层表结构独立完成情感标签、高频词、热门话题等指标统计并输出可视化看板。”这样写的好处是面试官既能看出你熟悉 Python也能看出你具备基本的数据库建模和 SQL 统计能力还知道你能把数据和业务场景结合。2. 项目总体设计与技术栈2.1 整体流程先不用着急写代码我们先把流程梳理清楚。整个数据分析链路可以分成下面几段生成原始数据模拟外部采集到的热搜话题和评论明细数据清洗处理重复值、缺失值、表情符号、无意义字符数据入库把清洗后的数据写入 MySQL 的目标表SQL 统计分析查询整体评论量、情感分布、话题热度、高频词数据可视化用 Python 读取 MySQL 聚合结果并画图。在实际开发中每一步都可能单独拆成一个脚本。脚本之间通过文件和数据库连接依赖这样做的好处是以后替换数据源时不需要重写整个项目。2.2 功能模块拆分模块功能设计如下模块对应脚本职责数据生成generate_demo_data.py生成模拟热搜话题和评论数据写入 CSV数据清洗clean_data.py读取原始 CSV执行清洗规则输出干净 CSV数据导入import_to_mysql.py读取清洗后数据写入 MySQL数据查询与可视化analysis.py执行 SQL 聚合查询生成图表建议你也按照这种方式组织代码。不要把所有逻辑都堆在一个文件里尤其是准备写进简历的项目代码结构本身就是面试官会关注的部分。2.3 技术栈和版本说明为了项目可复现建议环境如下Python 3.8 及以上版本MySQL 5.7 或 8.x 均可本文示例以 MySQL 8.x 默认字符集为例PyMySQL 1.xpandas、numpy、matplotlib、jieba 库操作系统不做限制Windows、macOS、Linux 都可以。需要注意如果你的 MySQL 是 8.0 之前的老版本字符集默认值和认证插件可能与新版本有差异。下文涉及版本敏感的地方我会单独说明。3. 环境准备与项目初始化3.1 安装 Python 依赖包在项目开始前打开终端进入你的虚拟环境先安装依赖。推荐用requirements.txt管理项目依赖包。先在项目根目录创建requirements.txtpymysql1.1.0 pandas2.0.3 numpy1.24.3 matplotlib3.7.2 jieba0.42.1然后执行pip install -r requirements.txt如果你使用的是较新版本的 Python可以去掉版本号直接安装最新版例如pip install pymysql pandas numpy matplotlib jieba“版本必须完全一致”不是这个项目的硬性要求重点是保证 PyMySQL 和 pandas 能正常配合使用。3.2 MySQL 环境准备确保本机已经安装并启动 MySQL 服务。不同系统的启动命令不一样这里举两个常见场景。Windows 下如果你的 MySQL 注册成了 Windows 服务可以在“服务”面板里手动启动或者在管理员终端执行net start mysqlLinux / macOS 下可以先查看 MySQL 运行状态systemctl status mysql如果没启动可以用下面命令尝试启动sudo systemctl start mysql启动成功后先在 MySQL 中创建一个专门用于本项目的数据库账号避免直接使用高权限账号跑业务脚本。这是一个很好的工程习惯。CREATE USER popmart_devlocalhost IDENTIFIED BY popmart123; GRANT ALL PRIVILEGES ON popmart_analysis.* TO popmart_devlocalhost; FLUSH PRIVILEGES;这里仅演示开发环境用法。真实生产环境中应当遵循最小权限原则只给账号授予业务所需的 SELECT、INSERT、UPDATE 权限并且不要使用弱密码。3.3 项目目录结构我建议你按下面的目录结构组织整个项目popmart-comment-analysis/ ├── config.py ├── requirements.txt ├── data/ │ ├── raw/ │ │ ├── hot_topic_raw.csv │ │ └── comment_raw.csv │ └── clean/ │ ├── hot_topic_clean.csv │ └── comment_clean.csv ├── scripts/ │ ├── generate_demo_data.py │ ├── clean_data.py │ ├── import_to_mysql.py │ └── analysis.py ├── output/ │ ├── sentiment_pie.png │ ├── topic_bar.png │ └── trend_line.png └── README.md下面开始逐步创建和编写。4. 模拟数据准备4.1 为什么要用模拟数据真实的热搜评论数据一般需要从公开平台页面、官方 API 或企业内部数据库中获取。这些数据往往带有版权、隐私、接口权限等问题不适合作为公开教程中直接下载的内容。为了让流程可复现我会使用固定随机种子生成一批贴近真实场景的中文评论数据。等你看懂整个流程后可以把自己拥有的合规评论数据替换成同结构的 CSV 文件。4.2 编写模拟数据生成脚本在scripts/generate_demo_data.py中写入以下内容# -*- coding: utf-8 -*- 模拟生成泡泡玛特热搜话题和评论数据 输出data/raw/hot_topic_raw.csv、data/raw/comment_raw.csv import csv import random from datetime import datetime, timedelta random.seed(42) # 1. 构造热门话题 topics [ {topic_id: 1, topic_name: 泡泡玛特LABUBU新系列首发, hot_rank: 12, hot_score: 860000}, {topic_id: 2, topic_name: 泡泡玛特城市乐园排队现场, hot_rank: 23, hot_score: 710000}, {topic_id: 3, topic_name: 泡泡玛特联名款被质疑溢价, hot_rank: 31, hot_score: 520000}, {topic_id: 4, topic_name: 泡泡玛特新品盲盒隐藏款概率, hot_rank: 45, hot_score: 430000}, {topic_id: 5, topic_name: 泡泡玛特端盒攻略分享, hot_rank: 56, hot_score: 360000}, {topic_id: 6, topic_name: 泡泡玛特门店补货后仍缺货, hot_rank: 67, hot_score: 290000}, {topic_id: 7, topic_name: 泡泡玛特售后问题引讨论, hot_rank: 74, hot_score: 230000}, {topic_id: 8, topic_name: 泡泡玛特MEGA系列价格走势, hot_rank: 88, hot_score: 185000}, ] # 2. 定义评论模板和情感词池 positive_words [喜欢, 好看, 惊喜, 值得, 期待, 不错, 满意, 开心, 抓到了, 真香] negative_words [太贵, 失望, 瑕疵, 不值, 难抢, 品控差, 售后慢, 排队太久] neutral_words [看看, 观望, 补货, 路过, 不了解, 明天去探店] templates [ 这个系列真的很让人{word}, 看了热搜过来的只能说{word}, 刚端了一盒整体感觉{word}, {#topic}这个话题下大家都在说{word}, 如果说体验我的感受是{word}, 等了好几天结果{word}, 对普通玩家来说最关心的还是{word}, ] def random_time(start, end): 生成随机时间 delta end - start return start timedelta(secondsrandom.randint(0, int(delta.total_seconds()))) # 3. 生成热话题文件 topic_output [] now datetime.now() for topic in topics: topic_output.append({ topic_id: topic[topic_id], topic_name: topic[topic_name], hot_rank: topic[hot_rank], hot_score: topic[hot_score], record_date: now.strftime(%Y-%m-%d), }) with open(data/raw/hot_topic_raw.csv, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnames[topic_id, topic_name, hot_rank, hot_score, record_date]) writer.writeheader() writer.writerows(topic_output) # 4. 生成评论文件 comments [] start_time datetime.now() - timedelta(days7) comment_id 10000 for topic in topics: # 每个话题生成不同数量的评论模拟真实热度差异 count random.randint(80, 180) for _ in range(count): comment_id 1 word_pool random.choices( [positive_words, negative_words, neutral_words], weights[0.4, 0.35, 0.25], k1 )[0] word random.choice(word_pool) text random.choice(templates).format(wordword, topictopic[topic_name]) comment_time random_time(start_time, now) like_count random.randint(0, 500) comments.append({ comment_id: comment_id, topic_id: topic[topic_id], comment_text: text, user_name: f用户_{random.randint(1000, 9999)}, comment_time: comment_time.strftime(%Y-%m-%d %H:%M:%S), like_count: like_count, sentiment: positive if word in positive_words else (negative if word in negative_words else neutral), }) with open(data/raw/comment_raw.csv, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter( f, fieldnames[comment_id, topic_id, comment_text, user_name, comment_time, like_count, sentiment] ) writer.writeheader() writer.writerows(comments) print(模拟数据生成完成) print(f热话题数量: {len(topic_output)}) print(f评论数量: {len(comments)})执行脚本cd popmart-comment-analysis python scripts/generate_demo_data.py脚本采用utf-8-sig编码是为了让 CSV 文件在 Excel 中打开时中文不乱码。这里生成的数据虽然不多但足够演示完整的数据分析链路。4.3 查看生成的原始数据生成后可以用 pandas 读取并预览import pandas as pd comment_df pd.read_csv(data/raw/comment_raw.csv) print(comment_df.head()) print(comment_df[sentiment].value_counts())预期会输出类似下面的结果comment_id topic_id comment_text user_name comment_time like_count sentiment 0 10001 1 这个系列真的很让人喜欢 用户_8245 2025-02-20 12:30:00 234 positive 1 10002 1 看了热搜过来的只能说难抢 用户_3342 2025-02-20 13:20:00 98 negative注意这里打印的时间会因为你的运行日期而不同这是正常的。5. 数据清洗与特征处理5.1 关键清洗规则原始评论数据通常不能直接入库分析。常见问题包括存在重复评论文本中出现多余空格、无意义的字符时间字段格式不统一情感标签可能缺失或写错点赞数存在异常值。本项目示例数据虽然已经比较规整但清洗代码不能省因为它体现的是处理真实脏数据的思路。5.2 编写清洗脚本在scripts/clean_data.py中实现以下逻辑# -*- coding: utf-8 -*- 数据清洗 输入 data/raw/ 下的 CSV 文件 输出 data/clean/ 下的清洗后 CSV 文件 import pandas as pd import re def clean_text(text): 清洗评论内容去除无用字符、多余空格、保留中文英文数字和常见标点 if not isinstance(text, str): return # 去除 用户、#话题# 中的 # 符号 text re.sub(r#\w#, , text) # 只保留中文、英文、数字、常用标点 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、...\s], , text) # 多个空格合并为一个 text re.sub(r\s, , text) return text.strip() # 1. 读取原始数据 topic_raw pd.read_csv(data/raw/hot_topic_raw.csv) comment_raw pd.read_csv(data/raw/comment_raw.csv) # 2. 话题数据清洗 topic_clean topic_raw.drop_duplicates(subset[topic_id]) topic_clean[record_date] pd.to_datetime(topic_clean[record_date]) topic_clean topic_clean.sort_values([record_date, hot_rank]) # 3. 评论数据清洗 comment_clean comment_raw.copy() # 3.1 删除完全重复的评论 comment_clean comment_clean.drop_duplicates(subset[comment_id]) # 3.2 删除没有正文或正文为空的数据 comment_clean comment_clean.dropna(subset[comment_text]) comment_clean[clean_text] comment_clean[comment_text].map(clean_text) comment_clean comment_clean[comment_clean[clean_text] ! ] # 3.3 统一时间格式无法解析的时间置为 NaT comment_clean[comment_time] pd.to_datetime(comment_clean[comment_time], errorscoerce) comment_clean comment_clean.dropna(subset[comment_time]) # 3.4 情感标签标准化统一小写 comment_clean[sentiment] comment_clean[sentiment].str.lower().str.strip() # 3.5 点赞数低于0的异常值置为0 comment_clean[like_count] comment_clean[like_count].apply(lambda x: max(x, 0)) # 4. 保存清洗后的文件 import os os.makedirs(data/clean, exist_okTrue) topic_clean.to_csv(data/clean/hot_topic_clean.csv, indexFalse, encodingutf-8-sig) comment_clean.to_csv(data/clean/comment_clean.csv, indexFalse, encodingutf-8-sig) print(数据清洗完成) print(f清洗前评论条数: {len(comment_raw)}) print(f清洗后评论条数: {len(comment_clean)})5.3 为什么清洗后要清理“时间字段”把时间字段转换成datetime类型是为了后续能够按日期或者小时粒度聚合比如统计每天评论量的变化趋势。如果你把时间保持成字符串SQL 里的日期函数就无法高效使用。另一个容易忽略的步骤是“清洗之后再入库”而不是把原始 CSV 直接 load 进数据库。先清洗再入库数据库里存放的数据质量会更高后续查询也更省心。6. 数据库设计与建表6.1 表结构设计为了体现真实的建模思维这里把热搜话题和评论设计成两张表。第一张表hot_topic存储热搜话题的维度信息包括热度排名、热度分数、记录日期。第二张表comment_info存储评论明细通过topic_id和话题表关联。设计理由一条热搜话题下有大量评论如果都塞在一张宽表里会发现话题信息被重复存储很多次拆成两张表后可以单独分析话题热度也能轻松做 join 聚合符合最常见的“维度表 事实表”分析模型。6.2 创建数据库连接信息文件在项目更目录创建config.py# -*- coding: utf-8 -*- 数据库连接配置 DB_CONFIG { host: localhost, port: 3306, user: popmart_dev, password: popmart123, charset: utf8mb4, } DB_NAME popmart_analysis这里需要提醒不要把你的真实数据库账号密码提交到公开仓库。如果是练习项目可以用本地密码如果是要开源的代码建议使用环境变量。后面“最佳实践”中会再展开。6.3 编写建库建表脚本在scripts/init_db.py中写入# -*- coding: utf-8 -*- 初始化数据库和表结构 import pymysql from config import DB_CONFIG, DB_NAME # 先连接 MySQL Server创建数据库 conn pymysql.connect(**DB_CONFIG, autocommitTrue) cursor conn.cursor() cursor.execute(fCREATE DATABASE IF NOT EXISTS {DB_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci) print(f数据库 {DB_NAME} 创建成功) # 切换到目标数据库 conn.select_db(DB_NAME) create_topic_sql CREATE TABLE IF NOT EXISTS hot_topic ( id INT NOT NULL COMMENT 话题ID来自上游数据源, topic_name VARCHAR(200) NOT NULL COMMENT 热搜话题名称, hot_rank INT DEFAULT NULL COMMENT 热搜排名, hot_score INT DEFAULT NULL COMMENT 热度值, record_date DATE DEFAULT NULL COMMENT 记录日期, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT泡泡玛特热搜话题表 create_comment_sql CREATE TABLE IF NOT EXISTS comment_info ( id INT NOT NULL AUTO_INCREMENT COMMENT 自增主键, comment_id BIGINT NOT NULL COMMENT 评论唯一ID, topic_id INT NOT NULL COMMENT 关联的话题ID, comment_text TEXT NOT NULL COMMENT 评论原文, clean_text TEXT COMMENT 清洗后的评论内容, user_name VARCHAR(64) DEFAULT NULL COMMENT 用户昵称, comment_time DATETIME DEFAULT NULL COMMENT 评论时间, like_count INT DEFAULT 0 COMMENT 点赞数, sentiment VARCHAR(16) DEFAULT NULL COMMENT 情感标签 positive/negative/neutral, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_comment_id (comment_id), KEY idx_topic_id (topic_id), KEY idx_sentiment (sentiment), KEY idx_comment_time (comment_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT评论明细表 cursor.execute(create_topic_sql) cursor.execute(create_comment_sql) print(数据表创建完成) cursor.close() conn.close()这段 SQL 需要注意几个点字符集使用utf8mb4因为评论中可能包含 emoji 表情传统utf8无法存储四字节字符comment_id加了唯一键避免同一评论被重复插入topic_id、sentiment、comment_time都加了普通索引后续按这些字段分组查询会更快这里没有添加物理外键约束原因是分析型项目更看重写入性能关联关系在 SQL 查询中用JOIN维护即可。真实业务中根据场景决定是否需要外键。执行初始化脚本python scripts/init_db.py看到数据库 popmart_analysis 创建成功和数据表创建完成就说明成功了。7. 数据写入 MySQL7.1 编写批量导入脚本数据清洗完成后需要写入 MySQL。这里推荐使用 PyMySQL 的executemany批量插入它比逐条execute快得多。在scripts/import_to_mysql.py中实现# -*- coding: utf-8 -*- 将清洗后的 CSV 数据批量写入 MySQL import pandas as pd import pymysql from config import DB_CONFIG, DB_NAME # 读取清洗后数据 topic_df pd.read_csv(data/clean/hot_topic_clean.csv) comment_df pd.read_csv(data/clean/comment_clean.csv) conn pymysql.connect( hostDB_CONFIG[host], portDB_CONFIG[port], userDB_CONFIG[user], passwordDB_CONFIG[password], databaseDB_NAME, charsetutf8mb4, autocommitTrue, ) cursor conn.cursor() # 插入话题数据使用 INSERT IGNORE 避免重复运行时报主键冲突 topic_sql INSERT IGNORE INTO hot_topic (id, topic_name, hot_rank, hot_score, record_date) VALUES (%s, %s, %s, %s, %s) topic_values [ (row[topic_id], row[topic_name], row[hot_rank], row[hot_score], row[record_date]) for _, row in topic_df.iterrows() ] cursor.executemany(topic_sql, topic_values) print(f话题表写入 {len(topic_values)} 条) # 插入评论数据 comment_sql INSERT IGNORE INTO comment_info (comment_id, topic_id, comment_text, clean_text, user_name, comment_time, like_count, sentiment) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) comment_values [ ( row[comment_id], row[topic_id], row[comment_text], row[clean_text], row.get(user_name, ), row[comment_time], row[like_count], row[sentiment], ) for _, row in comment_df.iterrows() ] # 划分批次避免单次写入数据量过大 batch_size 200 for start in range(0, len(comment_values), batch_size): batch comment_values[start:start batch_size] cursor.executemany(comment_sql, batch) print(f评论表写入 {len(batch)} 条) cursor.close() conn.close() print(数据导入完成)7.2 为什么使用 executemany 而不是逐条 insertexecutemany会把多条插入语句打包后发送给 MySQL 服务端大大减少 Python 与 MySQL 之间的网络通信次数。虽然本项目数据量不大但真实环境中可能每天有几十万甚至上百万条评论这个差别会非常明显。另外示例中的INSERT IGNORE会忽略主键或唯一键冲突的数据避免脚本重复执行时报错。这是测试环境里常用的幂等写入方式。注意生产环境不要随意使用 INSERT IGNORE因为它不止忽略重复键还可能忽略其他数据错误掩盖真实问题。数据分析教学项目这样用没问题工程上需要谨慎。运行导入脚本python scripts/import_to_mysql.py7.3 验证 MySQL 中的数据导入完成后可以用 MySQL 命令行快速验证USE popmart_analysis; SELECT COUNT(*) AS total_topics FROM hot_topic; SELECT COUNT(*) AS total_comments FROM comment_info; SELECT topic_id, COUNT(*) AS comment_cnt FROM comment_info GROUP BY topic_id ORDER BY comment_cnt DESC LIMIT 5;如果数据正确你会看到每个话题下的评论数量分布。8. SQL 多维分析与可视化8.1 整体评论量和情感分布数据分析的第一步通常先看总量和结构。下面这条 SQL 可以一次查出三个维度的情感分布SELECT sentiment, COUNT(*) AS cnt FROM comment_info GROUP BY sentiment ORDER BY cnt DESC;执行后会得到类似这样的结果sentimentcntpositive320negative256neutral190这说明在模拟数据中正面评论最多但负面和中性评价也占了较大比例。下一步可以继续看负面评论集中在哪些话题。8.2 热门话题下的评论量排行把comment_info和hot_topic关联起来可以看到“哪些热搜话题吸引了最多的评论讨论”。SELECT t.topic_name, COUNT(c.id) AS comment_cnt, ROUND(AVG(c.like_count), 2) AS avg_like FROM hot_topic t LEFT JOIN comment_info c ON t.id c.topic_id GROUP BY t.topic_name ORDER BY comment_cnt DESC LIMIT 10;这条 SQL 的亮点在于同时统计了评论量和平均点赞数。如果一个话题评论量很高但平均点赞很低说明讨论范围广但缺少高质量共鸣内容反之则可能出现“爆款评论”。8.3 不同话题的情感占比分析单纯统计评论总数不够还需要分析每个话题的情感结构。可以使用下面的查询SELECT t.topic_name, c.sentiment, COUNT(*) AS cnt FROM hot_topic t JOIN comment_info c ON t.id c.topic_id GROUP BY t.topic_name, c.sentiment ORDER BY t.topic_name, c.sentiment;在实际报表中这种数据适合用透视表进一步加工。8.4 分析脚本与图表生成为了让结果更直观我建议把 SQL 查询和 Python 可视化写到同一个脚本里。在scripts/analysis.py中实现# -*- coding: utf-8 -*- 从 MySQL 读取分析结果输出可视化图片 import os import pandas as pd import pymysql import matplotlib.pyplot as plt from config import DB_CONFIG, DB_NAME os.makedirs(output, exist_okTrue) # 设置中文字体避免图表中文乱码 plt.rcParams[font.sans-serif] [SimHei, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False conn pymysql.connect( hostDB_CONFIG[host], portDB_CONFIG[port], userDB_CONFIG[user], passwordDB_CONFIG[password], databaseDB_NAME, charsetutf8mb4, ) cursor conn.cursor() # 1. 情感分布 emotion_df pd.read_sql( SELECT sentiment, COUNT(*) AS cnt FROM comment_info GROUP BY sentiment , conn) plt.figure(figsize(8, 6)) plt.pie( emotion_df[cnt], labelsemotion_df[sentiment], autopct%.1f%%, startangle90, ) plt.title(泡泡玛特热搜评论情感分布) plt.savefig(output/sentiment_pie.png, dpi200, bbox_inchestight) plt.close() # 2. 话题评论量排行 topic_df pd.read_sql( SELECT t.topic_name, COUNT(c.id) AS comment_cnt FROM hot_topic t LEFT JOIN comment_info c ON t.id c.topic_id GROUP BY t.topic_name, t.hot_rank ORDER BY t.hot_rank ASC , conn) topic_df topic_df.sort_values(comment_cnt, ascendingTrue).tail(8) plt.figure(figsize(10, 6)) plt.barh(topic_df[topic_name], topic_df[comment_cnt], color#5B8FF9) plt.xlabel(评论数量) plt.title(各热搜话题产生的评论数量 TOP8) plt.tight_layout() plt.savefig(output/topic_bar.png, dpi200, bbox_inchestight) plt.close() # 3. 评论时间趋势 trend_df pd.read_sql( SELECT DATE(comment_time) AS ds, COUNT(*) AS cnt FROM comment_info GROUP BY DATE(comment_time) ORDER BY ds , conn) plt.figure(figsize(10, 5)) plt.plot(trend_df[ds], trend_df[cnt], markero) plt.xlabel(日期) plt.ylabel(评论量) plt.title(泡泡玛特热搜评论数量趋势) plt.grid(True, linestyle--, alpha0.5) plt.tight_layout() plt.savefig(output/trend_line.png, dpi200, bbox_inchestight) plt.close() cursor.close() conn.close() print(分析图表已输出到 output 目录)这段脚本里有几个值得关注的地方pd.read_sql可以直接把 SQL 查询结果读成 DataFrame省去手动 fetch 再构造 DataFrame 的步骤饼图适合展示占比结构柱状图适合展示排序结果折线图适合展示时间趋势选图要贴合分析目标bbox_inchestight可以减少图片周围的多余空白。执行分析脚本python scripts/analysis.py然后去output目录查看三张图片。如果只看到图片但没有中文标签说明系统字体里没有SimHei或Arial Unicode MS。你可以在本机安装对应中文字体或者把plt.rcParams[font.sans-serif]改成你系统中已有的中文字体名称。8.5 使用 jieba 做简单的高频词提取评论数据除了按情感和话题做统计还可以对正文做文本分析。常见做法是先用 jieba 分词过滤停用词再统计高频词。在分析脚本末尾追加import jieba from collections import Counter # 从 MySQL 中获取所有清洗后的文本 text_df pd.read_sql(SELECT clean_text FROM comment_info, conn) # 加载停用词实际项目中可以维护一个更完整的停用词表 stop_words {这个, 那个, 我们, 你们, 他们, 说, 还是, 真的, 感觉, 有点, 一下} words [] for text in text_df[clean_text].dropna(): for word in jieba.lcut(text): word word.strip() if len(word) 2 and word not in stop_words: words.append(word) word_counter Counter(words).most_common(20) print(高频词 TOP20) for word, count in word_counter: print(f{word}: {count})运行后你会看到类似“喜欢”“难抢”“太贵”“等待”“系列”这样的词出现频次较高。这些词往往就是用户讨论的核心话题。如果想输出为词云图可以继续安装wordcloud库pip install wordcloud然后绘制词云。要注意wordcloud默认不支持中文需要设置中文字体文件路径。如果你的系统没有现成中文字体文件可以先不追求词云效果先用柱状图展示高频词即可。8.6 结合分析结果形成结论一个完整的数据分析项目不能只输出图片还必须给出可读的结论。基于前面 SQL 的结果你可以这样组织分析报告评论情感以正面为主说明新品系列的整体关注度较高负面评论主要集中在“难抢、排队、溢价”等关键词反映用户对购买体验和价格较敏感从话题热度看“LABUBU 新系列首发”带来的评论量最高是近期品牌讨论的核心事件从时间趋势看话题发布当天评论量达到峰值之后逐日下降说明热搜话题的时效性很强品牌方需要在 24 小时内做好舆情跟进。真实项目中你还可以进一步做“负面评论的典型文本抽样”把高频负面问题反馈给对应部门。9. 常见问题与排查思路很多读者第一次跑这种 Python MySQL 项目时最容易遇到下面几个问题。问题现象常见原因解决思路Cant connect to local MySQL server through socket /tmp/mysql.sockMySQL 服务没有启动或连接地址不对先启动 MySQL 服务再用localhost连接Access denied for user popmart_devlocalhost数据库账号密码错误或账号没有权限检查账号密码确认授权库是否包含popmart_analysis中文写入数据库后变成乱码建表字符集不是 utf8mb4或者连接参数缺少 charset建表使用utf8mb4连接时设置charsetutf8mb4插入数据时提示Duplicate entrycomment_id 或主键冲突在测试场景可以使用INSERT IGNORE或者先清理测试表图表中文显示为方块系统缺少中文字体或 Matplotlib 字体配置不对设置本地已有的中文字体或者安装中文字体ModuleNotFoundError: No module named pymysql当前 Python 环境没有安装 PyMySQL运行pip install pymysql注意虚拟环境是否激活其中error 2002是最常见的问题。如果你在 Linux 上安装 MySQL 后没有启动服务PyMySQL 连接时就会失败。排查顺序如下执行systemctl status mysql查看服务状态如果未启动执行sudo systemctl start mysql再次运行导入脚本验证。还有一点需要注意如果连接字符串里的 host 写成localhostPyMySQL 可能会尝试通过 socket 连接如果写成127.0.0.1则通常走 TCP 连接。某些 MySQL 环境对这两种连接方式的权限配置不同。遇到连接问题后可以尝试把 host 改成127.0.0.1。10. 工程化建议与简历包装10.1 从“作业”到“项目”的关键差异很多初学者能跑通代码但简历通过率不高根源在于项目缺少工程化意识。下面几个细节能明显提升项目的完整度。第一代码分层。不要把 SQL、数据处理、可视化、配置全部塞进一个脚本里。按模块拆分后以后维护任何一环都会轻松很多。第二配置与代码分离。数据库地址、账号、密码尽量通过config.py或环境变量管理而不是散落在各类脚本里。如果项目要开源建议使用python-dotenv读取.env文件并在.gitignore中忽略敏感文件。第三SQL 执行环境要区分测试库和生产库。涉及批量写入或删除时先在测试库验证并提前备份原表数据。第四记录日志。数据量变大后靠print定位问题会非常困难。至少要在脚本里使用 Python 的logging模块记录关键步骤和数据量。10.2 数据质量与安全边界评论数据往往包含用户昵称、用户评论内容等信息。这类数据如果属于个人数据在采集和使用时要有合法授权。即便是模拟数据处理真实商业数据时也要注意匿名化与脱敏。在数据库操作层面DELETE和UPDATE语句必须带上WHERE条件且尽量先在测试环境验证影响范围。批量导入之前先做 COUNT 和抽样检查确认字段顺序和类型无误再执行避免污染整张表。10.3 简历表达和面试常见追问简历里的项目经验可以这样写“基于 Python 完成泡泡玛特热搜评论模拟数据采集与清洗设计话题、评论双表结构并写入 MySQL使用 Pandas 处理重复值与缺失值使用 PyMySQL 实现批量入库通过 SQL 完成话题评论量、情感占比、时间趋势等指标统计使用 Matplotlib 输出可视化图表。”这个写法的优点是每个动词后面都有明确产出。面试官看到后可能会追问清洗时遇到了哪些脏数据为什么用 MySQL 而不是直接存 CSV 或 Exceltopic_id 和 comment_id 的唯一键如何处理如果评论量每天达到百万级你的入库方案需要怎么优化情感标签是怎么得到的规则判断的准确率如何保证回答这些问题的核心不是背答案而是真正动手跑一遍项目记录自己遇到的问题。比如你可能会发现模拟数据中情感标签是预设好的但真实数据没有标签那就要考虑使用关键词规则或训练一个简单模型来打标。把这个思考过程写进项目说明里比“模型准确率 99%”更有说服力。10.4 后续可以扩展的方向如果你想把这个项目做得更深可以继续从几个方向扩展接入真实数据源在合法授权的前提下获取企业官方渠道或公开平台的评论数据增加关键词规则建立正负向情感词典用规则对无标签评论做初筛引入定时调度每天固定时间自动拉取数据并更新分析报表使用更高性能方案当数据量达到千万级后可以把 MySQL 的聚合结果缓存到 Redis或用 ClickHouse 等列式存储增加交互式可视化使用 Streamlit 或 Flask ECharts 做在线看板。扩展方向不用全做选一个和你的求职方向最匹配的即可。数据岗位更看重逻辑链路的完整性数据进来之后能变成什么样的决策依据这一点永远比工具本身更有价值。如果你在跑这个项目的过程中遇到坑欢迎在评论区留下具体报错信息我看到后会针对典型问题继续更新排错内容。