ARTICLE DETAIL

建站实战干货

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

Python+MySQL电商数据分析全流程:从建库导数到可视化看板

2026/9/28 13:45:00 拓冰建站 浏览量
Python+MySQL电商数据分析全流程:从建库导数到可视化看板 这段时间我在整理一套数据分析全流程的实战项目核心就是把 Python 生态里的 Pandas、Matplotlib、Numpy 和 MySQL 数据库全部串起来跑一遍。为什么想写这个因为很多朋友手里攒着数据库里的业务数据但做分析的时候经常在 SQL、Excel、Python 之间来回倒腾导来导去慢不说还容易把数据弄脏。这篇文章会基于一份可下载的电商订单 CSV 数据源把“建库导数 → 清洗加工 → 指标计算 → 多图形绘制”的完整链路走一遍。适合已经学过 Python 基础、想搞懂这几个库到底怎么协作的人也适合需要快速把 MySQL 数据变成可视化结论的分析师。1. 项目拆解这套全流程到底在解决什么问题1.1 为什么会把 Python 和 MySQL 绑在一起先说结论SQL 擅长的是“取数”和“基础聚合”Python 擅长的是“复杂清洗”和“自由可视化”。单一工具都有盲区。如果你只用 MySQL那么去重、补缺失值、正则提取字段、做矩阵运算、画多子图这种事会非常痛苦。比如你想从“华东-上海”这种字符串里拆出大区和城市SQL 要写一堆 SUBSTRING_INDEX维护起来很费劲。如果你只用 Python那每次都要从 CSV 或 Excel 导入数据数据源一更新就得手动重复一遍没有数据库的版本管理和权限控制。这套全流程的真实价值是把 MySQL 当数据仓库把 Python 当加工车间用 SQL 完成复杂查询和初步聚合用 Pandas 处理剩余的脏数据用 Numpy 做高效数值计算最后用 Matplotlib 把结论变成老板看得懂的图。整个过程可以脚本化下次数据来了改个日期参数就能重跑。1.2 一份能直接复现的数据源长什么样为了让大家真正跟着做而不是只看代码片段我准备了一份模拟电商订单数据order_data.csv放在项目仓库的data/目录下。如果你不想手动下载仓库里也提供了一个generate_data.py脚本运行一下就能生成同样的数据保证可复现。字段结构如下字段类型示例说明order_idVARCHAROD20230615001订单编号order_dateDATE2023-06-15下单日期user_idINT10235用户IDregionVARCHAR华东-上海大区-城市categoryVARCHAR数码商品品类amountDECIMAL5999.00订单金额quantityINT2商品数量statusVARCHAR已完成订单状态这份数据故意做了一些“业务常态脏乱差”的问题有缺失的 user_id、存在重复订单、少量金额为负数、region 里混进了拼音和多余空格。别觉得这是故意为难人真实业务数据只会更脏。分析的第一步从来不是画图而是把数据擦干净。1.3 分析流程的四个阶段整个项目就是一条流水线我习惯把它拆成四个阶段环境与数据准备装好 Python、MySQL建库建表把 CSV 导入数据库。Pandas 数据清洗去重、补缺失、格式转换、正则提取字段。Numpy 指标计算向量化算客单价、环比增长率跑一些简单统计。Matplotlib 多图绘制生成折线图、柱状图、饼图、散点图的组合面板并输出高清图片。这篇文章的每个章节都会对应这四步中的一部分最后你能得到一张四合一的分析看板以及一份清洗后的结果数据表。2. 环境准备从零搭建 Python MySQL 分析环境2.1 Python 环境和必备库安装如果你是从头开始我建议别一上来就纠结“Anaconda 还是原生 Python”。能跑通才是第一优先级。个人经验是用 Anaconda 最省心因为 Pandas、Numpy、Matplotlib 通常已经预装好了如果你喜欢清爽环境也可以装官方 Python再用 pip 补齐依赖。需要安装的库主要这几个pip install pandas numpy matplotlib pymysql sqlalchemy openpyxl这里有几个容易忽略的细节pymysql是 Python 连接 MySQL 的驱动后面用create_engine也依赖它。sqlalchemy是 ORM 工具pd.read_sql配合它连数据库非常稳。openpyxl是后期要把结果导出成 Excel 时用的别省。装完之后可以用这段代码验证一下版本省得后面因版本不一致踩坑import pandas as pd import numpy as np import matplotlib print(pd.__version__) print(np.__version__) print(matplotlib.__version__)在 PyCharm 里安装库的话直接去File - Settings - Project - Python Interpreter点加号搜索或者直接在下方 Terminal 里执行 pip 命令。用 VSCode 的话记得先选对解释器CtrlShiftP输入Python: Select Interpreter否则会碰到“明明装了库却 import 失败”的尴尬。2.2 MySQL 安装与基础配置MySQL 建议安装社区版Windows 下用安装包一路下一步即可但有两个点要特别留意root 密码设置后尽量记在密码管理器里忘密码的代价比想象中高。安装器有个“选择加密方式”的步骤如果选了默认的caching_sha2_password后续用 pymysql 连接时需要额外处理。为了减少麻烦可以在这一步选Use Legacy Authentication或者后面建用户时换成mysql_native_password。Linux 服务器上安装就用系统包管理工具sudo apt update sudo apt install mysql-server sudo systemctl enable mysql sudo systemctl start mysql装完后建议把数据库默认字符集设置成utf8mb4否则中文数据极容易乱码。修改/etc/mysql/mysql.conf.d/mysqld.cnf在[mysqld]下加两行character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ciWindows 用户在 my.ini 里也是同样配置。然后重启 MySQL 服务这一步能避免后续导入中文数据时出现“问号”和“乱码”。2.3 建库建表并导入 CSV 数据我习惯把分析库和业务库分开单独建一个sales_analysis库。执行如下 SQLCREATE DATABASE IF NOT EXISTS sales_analysis DEFAULT CHARSET utf8mb4; USE sales_analysis; CREATE TABLE IF NOT EXISTS orders ( order_id VARCHAR(32) PRIMARY KEY, order_date DATE, user_id INT, region VARCHAR(50), category VARCHAR(20), amount DECIMAL(10,2), quantity INT, status VARCHAR(10) );导入数据有三种方式按推荐程度排序Navicat 导入向导右键表名选择“导入向导”选 CSV 文件字段映射确认一下就行。注意第一列如果是表头就勾选“首行为标题”。这是最直观的方式试用版也够用。MySQL 命令行LOAD DATA LOCAL INFILE /data/order_data.csv INTO TABLE orders FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY IGNORE 1 LINES;用 Pandas 写入适合你已经把 CSV 读进内存的情况df.to_sql(orders, engine, if_existsreplace, indexFalse)也能完成导入速度稍慢但非常灵活。我建议前期用 Navicat跑通之后再用代码自动化替换这样你对数据长什么样才有体感。3. 核心细节Pandas、Numpy、Matplotlib 逐个击破3.1 Pandas 数据类型转换与正则清洗Pandas 的astype()和to_datetime()是我用得最多的两个手艺人操作。拿到原始 CSV 后第一步永远是df.info()看 dtypeimport pandas as pd df pd.read_csv(data/order_data.csv) print(df.info())你会看到order_date是 object 而不是 datetime64amount可能因为混入了空值而变成 float64 或 object。这种时候直接排序、聚合都可能出错。日期转换的标准姿势df[order_date] pd.to_datetime(df[order_date], format%Y-%m-%d)关于format我建议能写就写。虽然 Pandas 能自动推断日期格式但遇到“2023/6/15”和“2023-06-15”混排时自动解析会变得很慢甚至报错。明确格式既提速又减少歧义。正则清洗在 Pandas 里的用法非常顺手。比如 region 字段是“华东-上海”想拆出大区df[city] df[region].str.extract(r[-](.)) df[region] df[region].str.extract(r^(.?)[-])这里的str.extract会返回括号里的匹配内容如果没有匹配就是 NaN。配合dropna()或fillna()就能把乱七八糟的字符串整理干净。如果你要提取的是数字、邮箱、手机号思路完全一样。再强调一次正则表达式匹配的是模式不是死在字符串上清洗时多写几个测试样例没坏处。3.2 Numpy 数组运算与性能对比很多人第一次接触 Numpy 时觉得“这不就是 list 吗”但业务量一大差别立刻出来。Numpy 底层是连续内存的 C 数组支持向量化计算而 Python list 是对象数组每个元素都要走一遍解释器循环。用timeit看这个例子import numpy as np import timeit lst list(range(1000000)) arr np.arange(1000000) # 列表逐元素平方 def list_square(): return [x**2 for x in lst] # Numpy 向量化平方 def np_square(): return arr ** 2 print(timeit.timeit(list_square, number10)) print(timeit.timeit(np_square, number10))实测 Numpy 通常会快一个数量级以上。这不是炫技是真实场景中“报表跑 5 分钟”和“报表跑 20 秒”的差距。还有两个高频操作三维数组相乘和矩阵逆。三维数组相乘要明确 Broadcasting 规则我常用的写法是np.matmul(a, b)或者直接a b。矩阵求逆用np.linalg.inv(a)但记得先用np.linalg.det(a)看一下是否接近 0行列式为 0 的矩阵没有逆强行算会报 LinAlgError。3.3 Matplotlib 的 figure、axes、axis 核心概念这三个概念是 Matplotlib 最容易绕晕的地方一定要分清figure是整张画布相当于一张白纸。axes是画布上的一个绘图区域相当于白纸上画的一条矩形容器所有数据图都绘制在 axes 里。axis是 axes 里的坐标轴即 x 轴和 y 轴包含刻度、标签、网格线。一句话记忆figure 里可以放多个 axes每个 axes 都有独立的 axis。代码对应关系fig plt.figure() # 创建画布 ax fig.add_subplot(1, 1, 1) # 在画布上添加唯一一个绘图区域 ax.plot([1, 2, 3], [4, 5, 6]) # 在 axes 中绘图 ax.set_xlabel(x轴) # 设置 axis 的标签更推荐的方式是用plt.subplots()一次性把画布和子图都建好fig, axes plt.subplots(2, 2, figsize(12, 8)) # axes 是二维数组axes[0][0] 表示左上角子图这个 API 之所以好用是因为它把“画布管理”和“子图操作”直接绑定不用反复plt.subplot()切换当前视图。3.4 多图形绘制的三种姿势实战中画多图最常用的三种姿势姿势一plt.subplot(2, 2, 1)按序号切换plt.figure(figsize(10, 6)) plt.subplot(2, 2, 1) plt.plot(...) plt.subplot(2, 2, 2) plt.bar(...)优点是简单缺点是绘图代码里每一段都要带上plt.subplot子图多了容易乱。姿势二先subplots拿到 axes 数组再逐个操作fig, axes plt.subplots(2, 2, figsize(12, 8)) axes[0, 0].plot(...) axes[0, 1].bar(...) axes[1, 0].scatter(...) axes[1, 1].pie(...)这种方式最推荐因为每个子图的对象引用非常明确不会画着画着不小心覆盖掉前一张图。姿势三用GridSpec自定义不规则的子图布局fig plt.figure(figsize(10, 8)) gs fig.add_gridspec(2, 3) ax1 fig.add_subplot(gs[0, :]) # 第一行横向跨三列 ax2 fig.add_subplot(gs[1, 0]) # 第二行第一列 ax3 fig.add_subplot(gs[1, 1:]) # 第二行后两列适合做复杂看板比如上面一条月度趋势下面左边品类占比右边散点图。多图绘制时有几个参数我每次都会调alpha散点图、柱状图都建议设置 0.5~0.7防止图形重叠看不清。gridax.grid(True, linestyle--, alpha0.6)让数据读数更容易。legendax.legend(locbest)同时给 label 加中文时注意字体设置。color除了默认颜色可以传十六进制比如color#2c7fb8或者用内建 colormapcmapplt.cm.Blues。另外一定要记住多子图共用颜色时建议用plt.subplots统一设置风格避免每张图风格割裂。4. 全流程实操从数据库查询到可视化成品4.1 用 PyMySQL 连接 MySQL 并读取数据代码开头先把连接串准备好。我用 SQLAlchemy 的create_engine因为pd.read_sql对它的支持最完善连接池管理也更省心from sqlalchemy import create_engine engine create_engine( mysqlpymysql://root:你的密码localhost:3306/sales_analysis?charsetutf8mb4 ) df pd.read_sql(SELECT * FROM orders, engine) print(df.shape)如果你的密码里有、#这类特殊字符需要做 URL 编码比如换成%40。这个细节能省半个小时的排查时间。charsetutf8mb4必须加上它是中文不乱码的底线。如果你本身已经通过 SQL 把要分析的数据聚合好了pd.read_sql依然能非常高效地拿到 DataFrame。4.2 Pandas 清洗去重、补缺、过滤异常拿到df后我先按下面的顺序处理# 1. 去除完全重复的记录 df df.drop_duplicates() # 2. 处理缺失值user_id 缺失的先补一个占位值 df[user_id] df[user_id].fillna(0).astype(int) # 3. 过滤无意义数据金额必须大于 0 df df[df[amount] 0] # 4. region 字段统一去除空格提取大区和城市 df[region] df[region].str.replace( , , regexFalse) df[region_name] df[region].str.extract(r^(.?)[-]) df[city] df[region].str.extract(r[-](.)$) # 5. 状态字段只保留“已完成”只看有实际成交意义的订单 df df[df[status] 已完成]每一步都要检查df.shape或者df.isnull().sum()别一股脑跑完再看结果。清洗过程中最常见的坑是fillna(0).astype(int)之前没确认列里没有逗号之类的字符串比如1,234。遇到这种先用df[user_id] df[user_id].astype(str).str.replace(,, )再转换。4.3 用 Numpy 计算客单价和环比增速清洗干净后就可以用 Numpy 做数值计算。先算客单价import numpy as np amount_arr df[amount].to_numpy() quantity_arr df[quantity].to_numpy() df[unit_price] amount_arr / quantity_arr这里用to_numpy()拿到 Numpy 数组除法和后续统计都是向量化计算比用 lambda 逐行 apply 快不少。如果你要算月度销售额环比增速可以先用 Pandas 聚合再用 Numpy 计算monthly df.groupby(df[order_date].dt.to_period(M))[amount].sum() # 拿到销售金额数组 sales monthly.to_numpy() # 环比增速 (本期 - 上期) / 上期 growth np.diff(sales) / sales[:-1] * 100np.diff是相邻元素求差sales[:-1]是去掉最后一个元素的上期数组。这样算增速一行代码搞定循环逻辑而且对空值敏感度更高逼着你先把数据补好。4.4 用 Matplotlib 绘制四合一分析看板这是整个项目里最出效果的一步。我用plt.subplots(2, 2)一次性创建四个子图分别展示左上月度销售额折线图反映趋势。右上区域销售额 Top10 柱状图反映结构。左下品类占比饼图反映构成。右下数量 vs 金额散点图反映相关性。完整代码长这样import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(2, 2, figsize(14, 10)) # 左上月度销售额折线图 monthly.plot( axaxes[0, 0], markero, color#2c7fb8, linewidth2 ) axes[0, 0].set_title(月度销售额趋势) axes[0, 0].set_xlabel(月份) axes[0, 0].set_ylabel(销售额) axes[0, 0].grid(True, linestyle--, alpha0.5) # 右上区域销售额 Top10 柱状图 region_sum df.groupby(region_name)[amount].sum().sort_values(ascendingFalse).head(10) region_sum.plot( axaxes[0, 1], kindbar, color#fd8d3c, alpha0.8 ) axes[0, 1].set_title(区域销售额 Top10) axes[0, 1].set_xticklabels(region_sum.index, rotation45, haright) axes[0, 1].grid(axisy, linestyle--, alpha0.5) # 左下品类占比饼图 category_sum df.groupby(category)[amount].sum() axes[1, 0].pie( category_sum.values, labelscategory_sum.index, autopct%.1f%%, startangle90, wedgeprops{edgecolor: w} ) axes[1, 0].set_title(品类销售额占比) # 右下数量 vs 金额散点图 axes[1, 1].scatter( df[quantity].sample(500, random_state42), df[amount].sample(500, random_state42), alpha0.5, s30, c#31a354 ) axes[1, 1].set_title(购买数量与订单金额关系) axes[1, 1].set_xlabel(下单数量) axes[1, 1].set_ylabel(订单金额) axes[1, 1].grid(True, linestyle--, alpha0.5) fig.suptitle(电商订单数据分析看板, fontsize16) plt.tight_layout() plt.savefig(analysis_dashboard.png, dpi200, bbox_inchestight) plt.show()这里有几个小细节我再强调一下折线图用markero数据点更明显。柱状图横标签特别多时设置rotation45, haright不然 tick 会叠成黑疙瘩。散点图为了防止图片太大或过密可以df.sample(500, random_state42)抽样既保持趋势又让渲染更快更清晰。tight_layout()一定要在savefig之前调不然标题和标签容易被截断。bbox_inchestight让保存图片时自动收边导出后四周不会留大片白边。4.5 保存清洗后的数据分析完顺手把结果落盘这是好习惯df.to_csv(data/orders_clean.csv, indexFalse, encodingutf-8-sig) monthly.to_frame().to_excel(data/monthly_sales.xlsx)这里我推荐encodingutf-8-sig因为直接用utf-8导出的 CSV 用 Excel 打开时中文容易乱码。utf-8-sig带 BOMExcel 能正确识别。表格文件用to_excel导出方便给不写代码的同事。5. 常见问题速查表与排障笔记5.1 MySQL 连接失败的几个典型原因我把实战中遇到的连接问题整理成一张速查表错误现象可能原因处理方式Access denied for user用户名或密码错误检查连接串注意特殊字符编码Cant connect to MySQL serverMySQL 服务没启动或端口不是 3306启动服务检查端口Unknown database数据库名写错SHOW DATABASES;确认库名Authentication plugin...加密方式与 pymysql 不兼容改用mysql_native_password中文乱码连接字符串没加 charset或建库字符集不对统一 utf8mb4pd.read_sql返回空SQL 查询条件太严格先去掉 WHERE 测试逐步缩小范围连接库这件事我一直建议先写在 Python 文件顶部每次跑之前确认一次这些参数别等报错再猜。5.2 中文字体和坐标轴重叠问题Matplotlib 默认字体对中文不友好最容易出现的现象是标题变成了方框。解决方式我已经写在代码里了plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False第二行尤其重要。如果不设置坐标轴上的负号会显示成乱码方块。画完坐标轴标签后如果发现 x 轴文字挤在一起就用旋转plt.xticks(rotation45, haright)图例如果被压住就用locupper left之类的参数微调或者bbox_to_anchor(1.05, 1)把图例放到图外右侧看板会清爽很多。5.3 Numpy 版本不匹配与性能对比的坑Numpy 版本出问题通常表现为 import 报错或者AttributeError: module numpy has no attribute xxx。这多半是环境里安装了多个 Python或者某个库的依赖版本过老。建议先固定一个虚拟环境然后pip install numpy --upgrade如果升级后出现与 Pandas 不兼容的警告可以回退到 Pandas 官方要求的 Numpy 版本。通常pip install pandas numpy matplotlib --upgrade一起升不会有大问题。还有人会把 Numpy 和 list 的速度对比理解成“Python 一定很慢”。其实要分场景小规模数据下差异可忽略十万级以上元素的数值运算才能体现 Numpy 的价值。实证对比很有必要但别为了对比而对比。5.4 Pandas 类型转换与正则提取翻车记录类型转换最经典的坑是字段里肉眼看着是数字实际带着逗号或空格astype(float)直接报错。比如1,234.56要先清理千分位df[amount] df[amount].astype(str).str.replace(,, , regexFalse).astype(float)另一个高频坑是to_datetime遇到无法解析的格式。此时除了指定format还可以先用pd.to_datetime(df[date], errorscoerce)把解析失败的变成NaT再统一处理会比直接崩掉友好得多。正则提取同样要小心 NaN因为NaN不是字符串很多正则方法会直接抛错。稳妥写法df[city] df[region].str.extract(r[-](.)$, expandFalse).fillna(未知)先补缺失再提字段能少掉一把眼泪。5.5 把连接和清洗封装成可复用函数最后分享一个我自己的习惯把最稳定的步骤封装成函数这样换数据源、换库表时只需要改参数不用改逻辑。def load_orders(table_nameorders): engine create_engine(...) return pd.read_sql(fSELECT * FROM {table_name}, engine) def clean_orders(df): df df.drop_duplicates() df[user_id] df[user_id].fillna(0).astype(int) df df[df[amount] 0] df[region_name] df[region].str.extract(r^(.?)[-]) df[city] df[region].str.extract(r[-](.)$) return df这样做的好处是你沉淀了一套“数据清洗模板”下次换一套订单数据流程还是那几步只是字段名可能变了。分析工作最值钱的部分从来不是某段代码而是你反复踩坑后形成的稳定方法论。我个人实际操作中最深刻的体会是先把“取数 → 清洗 → 计算 → 画图”最小闭环跑通再去追求复杂模型和炫酷图表。这个项目看着简单但它把数据库和 Python 生态串成了生产线后续你想加预测、加漏斗、加自动报告都是在同一套框架上增砖添瓦。最后再分享一个小技巧每隔几步就把中间结果打印成 CSV 或 Pickle 存一份既方便回滚又能在画图出错时快速定位是哪一步污染了数据。项目代码和数据源都放在仓库里照着跑一遍你就能拥有一张属于自己的数据分析看板。