做数据分析练手项目,最怕两件事:第一是数据太“假”,洗来洗去没有业务感觉;第二是流程太散,爬虫是爬虫、可视化是可视化,最后简历上只能写“用过 Python”。这次我们看的这个项目,用 Python + MySQL 做了一个泡泡玛特主题的数据可视化分析,整体走的是「建库 → 入库 → SQL 分析 → Python 处理 → 可视化出图 → 结论沉淀」的完整链路,练完之后是一份能直接写进简历里的数据项目经历。
先给这个项目做个定位:它不是一个算法项目,也不是一个模型部署项目,而是一个典型的数据分析全流程项目。技术栈是 Python + MySQL + pandas + pyecharts / Matplotlib 这类常见组合。业务对象是泡泡玛特的盲盒产品数据、门店渠道数据、销售订单数据。核心成果是几张能说明业务问题的可视化图表,例如热门 IP 排行、价格带分布、销量趋势、渠道对比。整个过程不依赖高算力设备,普通笔记本就能跑,重点是让你把 MySQL 建表、SQL 查询、Python 数据清洗、可视化输出串起来。
这篇文章会给出完整的项目拆解、数据库表设计、Python 入库脚本、SQL 分析思路和可视化代码,同时会把“怎么看数据、怎么找业务结论、怎么写进简历”也讲清楚。如果你正缺一个 Python 数据分析方向的项目经验,这篇文章可以直接照做。
1. 项目核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目类型 | Python + MySQL 数据分析可视化练手项目 |
| 技术栈 | Python、MySQL、pandas、SQLAlchemy / PyMySQL、pyecharts / Matplotlib |
| 业务对象 | 泡泡玛特盲盒产品、IP、价格、渠道、销售订单 |
| 核心产出 | 数据库表、清洗后的数据集、可视化图表、业务分析结论 |
| 运行环境 | Windows / macOS / Linux 均可,普通笔记本即可 |
| 硬件门槛 | 无 GPU 要求,8G 内存及以上足够 |
| 启动方式 | 命令脚本 / Jupyter Notebook / PyCharm 逐步执行 |
| 接口 API | 不需要,侧重内部数据分析流程 |
| 批量任务 | 支持批量导入 CSV / Excel 数据入库 |
| 适合场景 | 数据分析简历项目、毕设、课程设计、Python + MySQL 综合练习 |
| 数据合规注意 | 真实业务数据需授权,练习建议使用模拟或已公开脱敏数据 |
这个项目的价值点不是“泡泡玛特”这三个字本身,而是它覆盖了数据分析岗面试里最常见的十几道题:数据表怎么设计、SQL 怎么取数、重复值和缺失值怎么处理、销售额怎么聚合、Top N 怎么算、时间趋势怎么做、结果怎么用图表表达。
2. 适用场景与使用边界
2.1 适合谁做
- Python 基础已经学完,但缺一个能串起 pandas + MySQL 的完整案例。
- 求职数据分析 / 商业分析 / 运营分析岗位,简历缺项目经验。
- 正在准备毕业设计,想做一个“数据采集 + 存储 + 可视化”的课题。
- 学过 SQL 但没在真实业务表上写过聚合查询。
- 用 Matplotlib 画过图,但没做过完整的分析型可视化报告。
2.2 能解决什么问题
这个项目帮你解决的核心问题是:从一堆看起来杂乱的数据中,梳理出能支撑业务决策的指标和图表。
练完你能掌握这些具体能力:
- 设计维度表和事实表,理解为什么订单表要单独的 id 而不是直接存 IP 名称。
- 用 SQL 做多表关联查询,例如把订单表关联产品表,统计某个 IP 的销量。
- 用 pandas 处理 MySQL 查出来的结果,补齐缺失值、转换时间格式、计算客单价。
- 用 pyecharts 生成柱状图、折线图、饼图、地图或仪表盘。
- 写出一份可汇报的数据分析结论:「谁卖得好、为什么卖得好、下阶段看什么指标」。
2.3 使用边界与合规提醒
必须强调一点:泡泡玛特是真实存在的商业公司,其销售数据、内部经营数据、未公开产品数据属于公司资产。做练手项目时,建议使用以下数据:
- 公开财报中的脱敏数据。
- 自己爬取公开电商页面得到的有限字段(并注意遵守目标网站协议)。
- 自行模拟构造的示例数据。
本文后续代码基于“模拟示例数据”来演示,所有数字仅用于跑通流程,不代表真实经营情况。项目可以命名为「模拟盲盒零售数据分析」「示例版潮流 IP 商品分析」,这样写进简历更稳妥,也能避开数据合规问题。
3. 环境准备与前置条件
这个项目不需要 GPU,不用配 CUDA,环境要求很亲民。
3.1 基础环境清单
| 环境项 | 建议版本 | 说明 |
|---|---|---|
| 操作系统 | Windows 10/11、macOS、Linux | 任选 |
| Python | 3.9 及以上 | 建议 3.10 或 3.11 |
| MySQL | 5.7 及以上 | 8.0 更佳 |
| IDE | PyCharm / VS Code / Jupyter | 任选 |
| 数据库客户端 | Navicat / DBeaver / MySQL Workbench | 可选,用于检查数据 |
3.2 Python 依赖库安装
建议先建虚拟环境,避免和系统 Python 包冲突。下面的命令按常见流程执行即可。
python -m venv venv source venv/bin/activate # Windows 下为 venv\Scripts\activate激活虚拟环境后,执行安装:
pip install -U pip pip install pymysql sqlalchemy pandas openpyxl pip install pyecharts matplotlib说明一下每个库的用途:
pymysql:Python 连接 MySQL 的驱动。sqlalchemy:统一的数据库连接引擎,pandasread_sql/to_sql经常配合它使用。pandas:数据清洗和分析。openpyxl:让 pandas 能读写 Excel 文件。pyecharts:生成交互式图表。matplotlib:生成静态图表,适合快速出图。
3.3 MySQL 环境检查
如果本机还没安装 MySQL,先确认几个基础问题:
- MySQL 服务是否已启动。
- root 用户密码是否知道。
- 端口是否默认 3306。
- 是否允许本机连接。
快速测试连接的方式是直接命令行登录:
mysql -uroot -p能进入 MySQL 交互界面就说明服务正常。如果报Can't connect to local MySQL server through socket '/tmp/mysql.sock',多数情况是 MySQL 服务没启动,先启动服务再重新连接。Linux 下常见命令是systemctl start mysqld,macOS 可以用brew services start mysql。
如果平时用 Navicat 之类的客户端比较多,提前在客户端里新建一个连接测试一下也行。
4. 数据库设计与表结构
这个项目的数据库设计是整个分析的地基。模拟一个简化但完整的盲盒零售业务:
- 门店维表:存放门店信息。
- 产品维表:存放产品的 IP 系列、价格、发售日期。
- 订单事实表:存放每一笔销售记录,关联门店和产品。
4.1 创建数据库
登录 MySQL 后执行:
CREATE DATABASE IF NOT EXISTS popmart_sales DEFAULT CHARSET utf8mb4; USE popmart_sales;使用utf8mb4是为了正确存储中文和 emoji 字符,盲盒产品名经常包含 IP 名和系列名,中文支持必须稳定。
4.2 门店信息表 store
CREATE TABLE store ( store_id INT PRIMARY KEY COMMENT '门店ID', store_name VARCHAR(100) COMMENT '门店名称', city VARCHAR(50) COMMENT '城市', channel VARCHAR(20) COMMENT '渠道类型:线下门店/电商平台/机器人商店' ) COMMENT '门店维度表';4.3 产品信息表 product
CREATE TABLE product ( product_id INT PRIMARY KEY COMMENT '商品ID', product_name VARCHAR(200) COMMENT '商品名称', ip_name VARCHAR(100) COMMENT 'IP名称,如 MOLLY、SKULLPANDA、DIMOO', series_name VARCHAR(100) COMMENT '系列名称', price DECIMAL(10,2) COMMENT '零售价', sale_date DATE COMMENT '发售日期' ) COMMENT '产品维度表';字段ip_name是这次分析里非常重要的维度,因为盲盒业务高度依赖 IP。价格字段用DECIMAL(10,2),不要用FLOAT,避免金额出现精度问题。
4.4 订单事实表 sale_order
CREATE TABLE sale_order ( order_id VARCHAR(50) PRIMARY KEY COMMENT '订单号', store_id INT COMMENT '门店ID', product_id INT COMMENT '商品ID', sale_qty INT COMMENT '销售数量', sale_amount DECIMAL(10,2) COMMENT '销售金额', order_time DATETIME COMMENT '下单时间' ) COMMENT '订单事实表';订单号虽然看起来是一串数字,但通常不适合直接用 INT,因为可能很长且包含特殊字符,所以这里定义为VARCHAR。sale_amount可以冗余存储,因为订单金额可能在商品价格基础上受活动影响,冗余后查询更方便。
4.5 表关系说明
sale_order.store_id关联store.store_idsale_order.product_id关联product.product_id
这就是最典型的星型模型:中间一张订单事实表,周边挂维表。后续做多表查询时,SQL 的JOIN条件非常直观。
4.6 外键要不要加
练手项目里,外键可加可不加。建议项目初期不加物理外键,而是通过在代码中控制数据一致性。原因有两个:
- 后期批量导入数据时,物理外键会消耗校验时间。
- 数据量大了以后,加了外键的表做
DELETE/UPDATE会遇到更多约束问题。
从数据分析的角度看,更常用的是“逻辑外键”,也就是字段存在但不在数据库层面强制约束。面试官问到这一点时,你可以回答:为了提高批量写入和查询效率,这里采用逻辑外键设计,在应用层保证数据完整性。这个回答比“我不会加外键”专业得多。
5. 数据准备与入库
这个环节是整个项目里最“真实”的一步。真实业务场景里,数据分析师拿到的第一份数据往往不是直接写进 MySQL 的,而是 CSV 文件、Excel 表、甚至 PDF 里的表格。所以这个过程应该这样设计:
- 准备原始 CSV / Excel 文件。
- 用 pandas 读取并预览。
- 做基础清洗。
- 写入 MySQL。
5.1 准备模拟数据文件
为了让项目能独立运行,可构造三个模拟数据文件:
store.csv:50 行左右,字段包括门店 ID、名称、城市、渠道。product.csv:100 行左右,字段包括商品 ID、名称、IP、系列、价格、发售日期。order_data.csv:2000 行左右,模拟一段时间内的订单流水。
由于没有真实接口,文件内容可以按业务规则生成。参考生成逻辑如下:
# 模拟参考代码:只展示生成思路,实际文件请自行构造 import pandas as pd import numpy as np np.random.seed(2024) ip_names = ['MOLLY', 'SKULLPANDA', 'DIMOO', 'LABUBU', 'HIRONO'] series_names = ['星座系列', '温度系列', '动物王国系列', '西游系列'] stores = [{'store_id': i, 'city': city, 'channel': channel} for i, (city, channel) in enumerate(...)]这里要特别说明:生成模拟数据时,要控制每个 IP 的销量差异、价格带差异,不要生成完全均匀的随机数据。否则后面分析时,图表没有业务层次感,做出来的 Top 5 排名也不具有解释空间。
另外在生成order_data.csv时,建议加入一些“脏数据”,比如:
- 空门店 ID。
- 重复订单号。
- 销售数量为 0。
- 时间格式不统一。
这也是一个加分设计:面试官如果问你“数据清洗怎么做的”,你能当场演示出至少三种清洗操作,而不是只会说dropna()。
5.2 用 pandas 清洗数据
读入 CSV 后,先看整体情况:
import pandas as pd orders = pd.read_csv('data/order_data.csv', encoding='utf-8') print(orders.shape) print(orders.head()) print(orders.info())处理常见问题的参考代码:
# 1. 删除重复订单号,保留第一条 orders = orders.drop_duplicates(subset=['order_id'], keep='first') # 2. 删除门店 ID 为空的数据 orders = orders.dropna(subset=['store_id']) # 3. 删除销售数量 <= 0 的数据 orders = orders[orders['sale_qty'] > 0] # 4. 统一时间格式 orders['order_time'] = pd.to_datetime(orders['order_time'])上述每一步都可以单独统计影响行数,这样写简历时就能写:“清洗 2000 行原始数据,去重 12 条,去空 8 条,有效数据 1980 条”,数据意识一下子就出来了。
5.3 写入 MySQL
写入 MySQL 的代码不需要手动拼接 SQL,直接用pandas.to_sql就能完成。
from sqlalchemy import create_engine engine = create_engine('mysql+pymysql://root:你的密码@127.0.0.1:3306/popmart_sales?charset=utf8mb4') store_df.to_sql('store', con=engine, if_exists='replace', index=False) product_df.to_sql('product', con=engine, if_exists='replace', index=False) orders.to_sql('sale_order', con=engine, if_exists='replace', index=False)if_exists='replace'表示如果表已存在,先删除再重建。练习阶段用这个比较方便。如果已经手工建好表,希望保留表结构、只追加数据,要改成if_exists='append'。
这里有个连接细节:create_engine的charset=utf8mb4一定要带上,否则写入中文时可能出现乱码。如果用 pymysql 原生连接,则要写:
import pymysql conn = pymysql.connect( host='127.0.0.1', user='root', password='你的密码', database='popmart_sales', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor )写入后,建议用 SQL 验证一下行数:
USE popmart_sales; SELECT COUNT(*) FROM store; SELECT COUNT(*) FROM product; SELECT COUNT(*) FROM sale_order;如果三个表都能查到数据,说明入库阶段已经完成。如果报ModuleNotFoundError: No module named 'pymysql',回到 3.2 节把依赖装好;如果报连接超时或密码错误,优先检查 MySQL 服务状态、密码、端口。
5.4 批量导入 Excel 多文件的思路
如果需要扩展成批量任务,可以用一个目录存放多个 Excel 文件,循环读取后写入同一张 MySQL 表,方便大量积累数据再统一分析,适合以后接更多数据源时快速起步。
import os file_dir = './batch_data' for file_name in os.listdir(file_dir): if file_name.endswith('.xlsx'): file_path = os.path.join(file_dir, file_name) temp_df = pd.read_excel(file_path) temp_df.to_sql('sale_order', con=engine, if_exists='append', index=False) print(f'{file_name} 导入完成')6. 基于 MySQL + Python 的分析维度设计
数据库已经就绪,接下来进入核心部分:用 SQL 做快速聚合,再用 Python 做更灵活的分析。下面从数据分析师面试最常遇到的几个角度出发,规划 6 类分析思路,每类都会给出 SQL 示例和 Python 可视化结果说明。
6.1 热门 IP 销售排行
盲盒行业最重要的指标之一就是 IP 热度。
标准 SQL:
SELECT p.ip_name, COUNT(o.order_id) AS order_cnt, SUM(o.sale_qty) AS total_qty, SUM(o.sale_amount) AS total_amount FROM sale_order o LEFT JOIN product p ON o.product_id = p.product_id GROUP BY p.ip_name ORDER BY total_amount DESC;这个查询解决两个点:第一,练习了GROUP BY和JOIN;第二,产出真实业务里最常见的“销售排行表”。用 pandas 执行同样的关联逻辑可以这样写:
order_df = pd.read_sql('SELECT * FROM sale_order', con=engine) product_df = pd.read_sql('SELECT * FROM product', con=engine) merged = pd.merge(order_df, product_df, on='product_id', how='left') ip_rank = merged.groupby('ip_name').agg( order_cnt=('order_id', 'count'), total_qty=('sale_qty', 'sum'), total_amount=('sale_amount', 'sum') ).reset_index().sort_values('total_amount', ascending=False) print(ip_rank)这一步可以同时验证 SQL 取数和 pandas 取数结果是否一致,如果不一样,往往是因为JOIN时存在非规范化字段,数据清洗没做彻底。
6.2 月度销售趋势
很多公司用“同比、环比”看趋势,数据库里不一定做了后续处理。最开始的月粒度计算公式是这样的:
SELECT DATE_FORMAT(order_time, '%Y-%m') AS month, COUNT(order_id) AS order_cnt, SUM(IFNULL(sale_amount, 0)) AS total_amount FROM sale_order GROUP BY month ORDER BY month;注意DATE_FORMAT(order_time, '%Y-%m')是一种常用取年月方式。此时提取出来是字符串,转成日期后图表呈现更自然。Python 里普通写法是:
merged['order_time'] = pd.to_datetime(merged['order_time']) merged['month'] = merged['order_time'].dt.strftime('%Y-%m') monthly = merged.groupby('month').agg( total_amount=('sale_amount', 'sum'), order_cnt=('order_id', 'count') ).reset_index()后续你可以计算环比增长:
monthly['pct_change'] = monthly['total_amount'].pct_change() * 100环比结果出来以后,就可以观察哪个月的增长异常,为结论段准备素材。
6.3 价格带分布
盲盒产品单价有比较明确的阶梯,比如 59、69、79、89、99 甚至更高。价格带分布可以直接反映产品结构。
SQL 分段聚合可以这么写:
SELECT CASE WHEN price < 60 THEN '0-60' WHEN price < 80 THEN '60-80' WHEN price < 100 THEN '80-100' ELSE '100以上' END AS price_range, COUNT(*) AS product_cnt, AVG(sale_amount) AS avg_order_amount FROM product p LEFT JOIN sale_order o ON p.product_id = o.product_id GROUP BY price_range;用 Python 分箱也常见。更推荐先用 pandas 查看分布再定断点:
bins = [0, 60, 80, 100, 10000] labels = ['0-60', '60-80', '80-100', '100以上'] merged['price_range'] = pd.cut(merged['price'], bins=bins, labels=labels, right=False) range_stat = merged.groupby('price_range').agg( product_cnt=('product_id', 'nunique'), total_qty=('sale_qty', 'sum') ).reset_index()6.4 渠道与城市对比
企业一般会关心线下门店、电商、机器人商店的贡献差异,以及哪些城市销量靠前。SQL 代码:
SELECT s.channel, COUNT(o.order_id) AS order_cnt FROM store s LEFT JOIN sale_order o ON s.store_id = o.store_id GROUP BY s.channel ORDER BY order_cnt DESC;如果表里还包含城市信息,城市维度可以这样看:
SELECT s.city, SUM(o.sale_amount) AS total_amount FROM store s LEFT JOIN sale_order o ON s.store_id = o.store_id GROUP BY s.city ORDER BY total_amount DESC LIMIT 10;很多情况下城市维度的结果不一定体现平衡分布,不要为了做图而硬画雷达图,直接按 Top 10 排序后画柱状图,更能尽快体现你的数据对比能力。
6.5 客单价与订单金额分布
在订单行,如果一笔订单是当时多件商品组合,分析客单价时需要将订单拆到订单明细行。可以按订单号聚合金额,然后得到客单价分布。先用 SQL:
SELECT order_id, SUM(sale_amount) AS order_amount FROM sale_order GROUP BY order_id ORDER BY order_amount DESC;接着用 pandas 求四分位数和均值:
order_amount_df = merged.groupby('order_id')['sale_amount'].sum().reset_index() desc = order_amount_df['sale_amount'].describe() print(desc)这样可以判断:是少部分大额订单贡献了较高营收,还是绝大多数订单集中在某个价格区间。
6.6 周期内单店产出
渠道和城市之外,门店产出值得单列。计算每家店平均每个月贡献金额,顺便发现异常值:
SELECT store_id, COUNT(DISTINCT DATE_FORMAT(order_time, '%Y-%m')) AS active_months, SUM(sale_amount) AS total_amount, SUM(sale_amount) / COUNT(DISTINCT DATE_FORMAT(order_time, '%Y-%m')) AS avg_month_amount FROM sale_order GROUP BY store_id ORDER BY avg_month_amount DESC;某些店如果活跃月份很少但总金额很高,可能意味着渠道差异或数据质量问题。这一个发现就可以作为分析报告里的一个小亮点。
7. 可视化图表展示
分析结果如果只停留在数字层面,还看不出项目完整度。下面给出几种目前最容易出效果的可视化思路与参考代码,均使用 pyecharts 的交互式图库。图表输出的最终目的不只是“好看”,而是让看报告的人一眼得出业务结论。
7.1 热门 IP 金额 Top10 柱状图
适合:Bar
from pyecharts.charts import Bar from pyecharts import options as opts top10 = ip_rank.head(10) bar = ( Bar() .add_xaxis(top10['ip_name'].tolist()) .add_yaxis('销售额', top10['total_amount'].round(2).tolist()) .set_global_opts( title_opts=opts.TitleOpts(title='热门IP销售额Top10'), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=15)), yaxis_opts=opts.AxisOpts(name='销售额') ) ) bar.render('output/ip_top10.html')render方法会生成本地 HTML 文件,双击就能在浏览器打开。
7.2 月度销售趋势折线图
适合:Line,同时展示销售额变化。
from pyecharts.charts import Line from pyecharts import options as opts line = ( Line() .add_xaxis(monthly['month'].tolist()) .add_yaxis('月度销售额', monthly['total_amount'].round(2).tolist()) .set_global_opts( title_opts=opts.TitleOpts(title='月度销售额趋势'), tooltip_opts=opts.TooltipOpts(trigger='axis') ) ) line.render('output/monthly_trend.html')曲线出现明显波动时,结合业务猜测原因,比如“新系列发售 / 营销节点”。即便只是模拟数据,也需要养成给结论找解释的习惯。
7.3 渠道占比饼图
适合:Pie
from pyecharts.charts import Pie from pyecharts import options as opts channel_data = ... pie = ( Pie() .add('', channel_data) .set_global_opts(title_opts=opts.TitleOpts(title='渠道销售结构')) .set_series_opts(label_opts=opts.LabelOpts(formatter='{b}: {d}%')) ) pie.render('output/channel_pie.html')饼图的适用前提是分类数不多,渠道一般只有 3~5 类,比较合适。如果分类超过 8 个,不要用饼图,用柱状图更清晰。
7.4 城市地图
如果希望继续扩展,还可以用 pyecharts 的地图组件画出不同城市的销量热力。地图数据通常需要城市名称与官方地理数据完全一致,否则不显示。模拟数据阶段不必强求,除非你手头确实有全国多城市的门店分布。
7.5 可视化项目目录结构建议
到这里,完整的项目目录可以统一为一个结构清晰的仓库,方便以后写进简历和提交 GitHub。
popmart-analysis/ ├── data/ │ ├── raw/ │ │ ├── store.csv │ │ ├── product.csv │ │ └── order_data.csv │ └── cleaned/ ├── sql/ │ ├── create_table.sql │ └── analysis.sql ├── scripts/ │ ├── 01_clean_data.py │ ├── 02_import_to_mysql.py │ ├── 03_analysis.py │ └── 04_visualization.py ├── output/ │ ├── ip_top10.html │ ├── monthly_trend.html │ └── channel_pie.html └── README.md这种结构在面试的时候很有优势,因为面试官只需看 README 和脚本命名,就能理解整个数据流。
8. 接口与扩展能力:如何升级成“可调用”项目
有读者会问:这个项目是纯分析脚本,不是服务,能不能做成接口?能。
练完基础版后,可以加一条扩展线:用 FastAPI 把分析结果包装成 HTTP 接口,这样别人可以通过 URL 直接拿到处理后的 JSON,也更有“工程感”。
实现思路:
from fastapi import FastAPI import pandas as pd from sqlalchemy import create_engine app = FastAPI() engine = create_engine('mysql+pymysql://root:你的密码@127.0.0.1:3306/popmart_sales?charset=utf8mb4') @app.get('/api/ip_rank') def ip_rank(): df = pd.read_sql(''' SELECT p.ip_name, SUM(o.sale_amount) AS total_amount FROM sale_order o LEFT JOIN product p ON o.product_id = p.product_id GROUP BY p.ip_name ORDER BY total_amount DESC ''', con=engine) return df.to_dict(orient='records')启动服务:
pip install fastapi uvicorn uvicorn main:app --host 127.0.0.1 --port 8001 --reload启动后访问http://127.0.0.1:8001/api/ip_rank就能看到 JSON 数据。这个扩展的实际意义在于,把“数据分析脚本”升级为“可复用的数据查询服务”,简历上可以多写一句:“实现了基于 FastAPI 的指标查询接口”。
但注意,如果你还没掌握基础版的分析流程,不要为了炫技先把接口写了。接口只是结果展示的通道,分析质量才是这个项目的核心。
9. 资源占用与运行性能观察
这个项目不需要 GPU,运行时的资源占用主要来自 MySQL 服务和 Python IDE。
9.1 数据量不大时怎么观察性能
模拟数据一共几千行,本机跑分析基本是秒级完成。那你运行脚本的时候看什么?重点看执行的稳定性:
pandas.read_sql每次查询会不会因为网络中断或连接占用导致报错。- 连接用完是否关闭,如果使用
create_engine,没有显式dispose时是否会影响后续脚本。 - 大批量写入时,MySQL 的
max_allowed_packet是否够用。
推荐在执行关键函数时打印耗时:
import time start = time.time() # 执行 SQL 查询 print('查询耗时:', time.time() - start)这样面试时你能说出:“在几千行数据量下,SQL 聚合查询耗时约 0.3 秒,可视化渲染耗时约几秒”,这比空谈“性能不错”有说服力。
9.2 如何防范连接泄漏
长时间运行 Jupyter Notebook 时,频繁执行read_sql可能留下空闲连接。建议写一个统一的连接获取函数,并在任务结束后释放。
from sqlalchemy.orm import sessionmaker from contextlib import contextmanager Session = sessionmaker(bind=engine) @contextmanager def get_session(): session = Session() try: yield session finally: session.close()当然对本项目,简单使用engine.dispose()也足够。
10. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 安装 pymysql 后 import 仍报错 | 未激活虚拟环境或安装路径不同 | pip list查看是否包含 pymysql | 激活正确的虚拟环境后重装 |
| 连接 MySQL 报密码错误 | root 密码设置错误 | 用命令行mysql -uroot -p测试 | 重置密码或修改连接串 |
数据库连接出现Unknown database 'popmart_sales' | 未建库或库名拼写错误 | SHOW DATABASES;查看 | 重新执行建库 SQL |
| 中文乱码 | 连接串或建表字符集不是 utf8mb4 | 查看表字符集 | 建表统一使用 utf8mb4,连接串加charset=utf8mb4 |
| 日期类型报错 | CSV 中日期格式不统一 | 打印orders['order_time'].head() | 先用pd.to_datetime统一格式 |
to_sql写入报错 Duplicate entry | 主键重复 | 检查 order_id 是否唯一 | 先清洗去重或换用if_exists='replace' |
| pyecharts 生成的 HTML 打开无内容 | 图表数据为空或浏览器安全限制 | 检查数据 DataFrame 大小 | 确保传入的是 list 而不是空数据 |
| 虚拟环境已存在但 IDE 未识别 | PyCharm 解释器配置错误 | 查看设置中的 Python Interpreter | 切换到虚拟环境 python 路径 |
| DataGrip/Navicat 连不上本地 MySQL | 3306 端口被占或用户权限 | `netstat -ano | findstr 3306(Windows)或lsof -i:3306`(Linux/macOS) |
| Excel 文件读取报缺少 openpyxl | 未安装 openpyxl | pip install openpyxl | 安装后重新运行 |
项目实际运行时,还有一个常见问题:MySQL 8.0 默认使用 caching_sha2_password 认证方式,比较老的 Python 驱动可能不兼容。如果连接时报Authentication plugin 'caching_sha2_password' cannot be loaded,处理办法有几种:
- 升级驱动:
pip install -U pymysql - 或者改用本地开发时常见的密码插件:运行
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;
这个坑概率很高,提前了解能省很多时间。
11. 最佳实践与使用建议
11.1 数据先备份,再清洗
原始 CSV / Excel 文件不要直接覆盖。整个流程建议分成 raw、cleaned、output 三个目录。清洗后的数据单独保存一份orders_clean.csv,以后重新分析时不必每次都跑一遍清洗脚本。
orders.to_csv('data/cleaned/orders_clean.csv', index=False, encoding='utf-8-sig')编码使用utf-8-sig是因为用 Excel 打开 CSV 时不会出现中文乱码,这是 Windows 场景下的实际经验。
11.2 SQL 脚本保存下来
不建议只在 Python 脚本里拼接字符串。把建表和核心分析 SQL 统一放到sql/目录,每次修改用版本管理,并在文件头注释需求背景。
11.3 分析结论要能讲成故事
图表是结果,不是目的。你要能说出类似这样的话:
- “从 IP 维度看,MOLLY 销量最高,但环比增速已经放缓。”
- “从渠道看,电商占比超过线下,但客单价低于机器人商店。”
- “从价格带看,69 元价格带的产品数量最多,说明品牌主攻入门级。”
面试官不会只看图表代码,真正拉开差距的是你有没有业务解释能力。
11.4 合规与授权
无论是自己构造模拟数据,还是使用网络公开数据,都要确保来源合法、用途合规。个人练手项目尽量避免包含完整的真实用户订单、会员信息、未公开商品计划。简历上展示时,建议标注“模拟数据 / 脱敏数据”。
11.5 从练手到简历项目
练完后,简历可以这样写:
- 项目名称:基于 Python + MySQL 的盲盒零售数据分析
- 项目描述:设计星型模型数据仓库,完成门店、产品、订单三张表的建库与数据清洗,使用 SQL 完成多维度聚合查询,基于 pandas 进行分析并输出可视化报告。
- 技术要点:PyMySQL、SQLAlchemy、pandas、pyecharts、MySQL、FastAPI(可选)。
- 分析成果:输出 IP 热度排行、月度销售趋势、渠道结构、价格带分布等 6 类分析图表,定位主要增长 IP 和主力价格带。
这个写法能让面试官快速判断你掌握了从数据获取到数据展示的完整链路。
12. 总结与下一步
这个项目的核心价值在于:用最常见的 Python + MySQL 技术栈,完成了一个有业务数据、有多维分析、有可视化输出的完整项目。它没有依赖特殊硬件,也没有复杂框架,适合作为数据分析方向的第一份落地产物。
做完基础版后,如果你还想继续扩展,可以优先尝试这几个方向:
- 接入真实公开数据源(需确认授权与合法性),把模拟数据换成真实数据。
- 增加更多维度,比如用户留存分析、复购周期分析。
- 把 pyecharts 图表升级成 Web 大屏,结合 FastAPI 展示结果。
- 把分析流程写成定时任务,定期从数据目录拉取数据并更新图表。
建议先按文章顺序把建库、洗数、SQL 分析和可视化完整跑通一遍,再考虑扩展。跑通一遍之后,把项目文档和脚本整理到 GitHub 上,简历里的项目经验就扎实了。