
做美食菜谱的数据分析听起来好像不如电商、金融那么“高大上”但真上手之后你会发现这是一个被严重低估的练手场景。菜谱数据天然带有多维度的结构化信息菜系、口味、食材、烹饪时长、热量、难度这些字段组合在一起足够你把 Python 数据分析、MySQL 存储、可视化展示这一整条链路跑得明明白白。这个项目我从头到尾做了一遍折腾了大概两个周末最后交出来的是一套可以本地直接跑起来看效果的系统Python 负责抓数、清洗和分析MySQL 负责落库存储可视化部分用 pyecharts 生成交互图表再拼出一个完整的可视化大屏。整套东西带着源码、数据库文件和说明文档拿去做课程设计、毕业设计或者单纯想练手数据分析实战都非常合适。1. 项目概述与整体设计思路1.1 这个项目到底做了什么先梳理一下这个系统的完整能力边界。它不是一个简单的“画几个图表”的 Demo而是一个从数据采集、数据存储、数据清洗到数据分析、可视化展示全流程覆盖的小型数据分析系统。整个流程是这样的先从公开的菜谱网站上把菜谱的基础信息抓下来包括菜名、所属菜系、口味标签、主要食材、烹饪时间、难度、热量这些字段。数据经过清洗之后存入 MySQL 数据库后续分析全部基于数据库里的数据来进行。分析部分包括菜系分布、口味偏好、食材使用频次、烹饪时长分布、热量区间统计等维度。最后通过 pyecharts 生成图表用 Flask 搭一个轻量级的 Web 页面把这些图表拼成一个可视化大屏点开浏览器就能看到分析结果。这个项目最有价值的地方在于它把数据分析的完整流程都走了一遍。很多人学数据分析只会用 pandas 读个 CSV 文件然后画图一旦涉及到数据入库、SQL 取数、Web 展示就懵了。这个项目恰好把这几块缺口补上了。1.2 为什么选 Python MySQL 这个组合技术选型上Python MySQL 是最经典、最不容易出错的数据分析组合没有之一。为什么这么说拆开来看。Python 在数据分析领域几乎是统治级的存在。pandas 处理表格数据、PyMySQL 连接数据库、pyecharts 做可视化每个环节都有成熟的库可以用资料多、踩坑少。对于初学者来说这套技术栈的学习曲线是最平缓的。MySQL 则是关系型数据库里的常青树。菜谱数据虽然字段不算复杂但天然带有结构化特征一个菜谱有名称、分类、口味、食材等多个属性这种数据放在 MySQL 里再合适不过。更关键的是MySQL 是所有后端开发、数据分析岗位的标配技能把这个项目做完SQL 的增删改查、聚合查询、多表关联这些能力都能得到实打实的锻炼。相比直接用 CSV 文件存数据MySQL 的好处在于第一数据量大了之后查询效率高得多第二支持复杂的 SQL 聚合和联表分析分析维度更丰富第三数据独立于代码存在系统重启、代码改动都不会影响数据。这对一个完整的项目来说是很重要的。1.3 系统架构与数据流整个系统的架构可以分成四层来看数据采集层负责从菜谱网站抓取原始数据。考虑到数据量和反爬压力抓取的目标不需要贪多控制在几千条有代表性的菜谱就足够做分析了。数据存储层就是 MySQL 数据库。这一层要做的事情是表结构设计、数据入库、数据清洗后写入。表设计要考虑菜谱信息如何存、食材如何存、菜谱和食材的多对多关系怎么表达。数据分析层使用 pandas 从 MySQL 中读取数据围绕菜系、口味、食材、时长、热量这几个维度做聚合统计形成分析结果数据集。可视化展示层用 pyecharts 把分析结果渲染成图表再通过 Flask 整合成一个可视化页面用户只需要在浏览器里访问本地地址就可以看到所有图表的组合展示。这个分层思路是很标准的数据分析项目架构好处是每一层职责清晰、可以独立调试。数据没抓到可以先手动造数据测存储层图表想单独调样式也可以绕过数据库直接拿 CSV 来测试开发效率高很多。2. 数据库设计让数据先落得稳2.1 数据从哪来采集方案的取舍做数据分析项目第一步不是建表而是先把数据源搞定。菜谱数据属于典型的结构化文本数据很多公开的美食网站都提供浏览页面可以按菜系、按食材分类去翻页抓取。数据采集有两条路线可以选。一是自己写 Python 爬虫从网页上抓用 requests 请求页面、BeautifulSoup 解析 HTML把菜名、食材、步骤这些字段提取出来。二是找现成的数据集或者公开 API 直接下载。两条路各有优劣我的建议是这个项目一定要自己写一遍爬虫哪怕数据量不大。原因很简单写爬虫的过程能锻炼到 requests、BeautifulSoup、数据解析、异常处理、限速策略这些技能这些都是数据分析工程师必备的能力。实际操作中爬虫的字段解析是最耗时间的部分。很多网站的前端代码并不规范同一个字段在不同页面上的标签结构可能不一致需要写多重兼容逻辑。我的经验是先用浏览器开发者工具观察几个页面的 HTML 结构找到规律之后再批量抓取不要上来就写完整爬虫否则很容易在解析阶段卡住。抓下来的数据先不急着入库以 JSON 或者 CSV 的格式暂存等数据量差不多之后统一做清洗。这样做的好处是如果后面清洗逻辑写错了可以重新清洗而不需要重新抓取。2.2 表结构设计的三个核心表MySQL 里菜谱数据的表结构设计我建议拆成三张表而不是把所有字段塞进一张表。为什么因为食材这个字段比较特殊一道菜有多个食材一个食材又会在很多道菜里出现这是典型的多对多关系需要一张关联表来表达。先看菜谱主表CREATE TABLE recipe ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL COMMENT 菜名, category VARCHAR(50) COMMENT 菜系如川菜、粤菜, flavor VARCHAR(100) COMMENT 口味标签多个用逗号分隔, cooking_time INT COMMENT 烹饪时长分钟, difficulty VARCHAR(20) COMMENT 难度简单/中等/困难, calories INT COMMENT 热量千卡/100g, steps TEXT COMMENT 烹饪步骤, source_url VARCHAR(255) COMMENT 来源链接, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;再建食材表和关联表CREATE TABLE ingredient ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE COMMENT 食材名称 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE recipe_ingredient ( recipe_id INT NOT NULL, ingredient_id INT NOT NULL, amount VARCHAR(100) COMMENT 用量描述, PRIMARY KEY (recipe_id, ingredient_id), FOREIGN KEY (recipe_id) REFERENCES recipe(id), FOREIGN KEY (ingredient_id) REFERENCES ingredient(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;三张表的设计有几个关键考虑。第一菜系、口味、食材都单独拆出来是为了方便后续做聚合统计。比如你想统计“川菜里最常用的食材 Top10”如果没有独立的菜系列字段这条分析语句写起来会非常痛苦。第二食材表用 UNIQUE 约束保证同一个食材只存一条记录避免“土豆”和“土豆 ”因为空格问题存成两条。第三关联表用复合主键 (recipe_id, ingredient_id) 防止重复关联。实际开发中如果你不想把数据拆得那么细也有一个偷懒方案食材字段直接以逗号分隔的字符串存在 recipe 表里分析的时候用 FIND_IN_SET 或者 LIKE 来匹配。但这种方案只适合小数据量一旦遇到“统计所有菜品中使用频率最高的食材”这类分析性能会差很多而且代码很难维护。正经做项目还是老老实实建关联表。2.3 数据清洗的关键细节数据入库之前一定要做清洗这一步直接影响后面所有分析结果的准确度。我从这个项目里总结出来几个必做的清洗动作都是血泪教训。字符编码问题最优先。菜谱数据从网页上抓下来经常带着各种各样的特殊字符比如全角空格、零宽字符、HTML 实体 之类。入库时统一用 utf8mb4 字符集读取时也指定 utf8mb4能避免大部分乱码问题。连接 MySQL 时 PyMySQL 要加上 charsetutf8mb4 参数这个细节经常被忽略。数值字段的异常值过滤。烹饪时间可能出现“30-60分钟”这种字符串需要统一转成分钟数可以取区间平均值转成 45。热量字段可能为空对于缺失值可以选择填充平均水平或者直接在分析时过滤掉根据分析场景灵活处理。文本内容的长度校验。食材字段如果出现超长的垃圾文本比如混杂了一大段菜谱介绍文字需要设置字段长度上限超出部分截断或者整体丢弃。这类脏数据不清理干净后面做统计的时候会拖慢查询速度还可能造成图表异常。做完清洗之后建议做一个简单的数据质量报告统计每个字段的缺失率、唯一值数量、数值范围确认没有明显异常再入库。这一步看起来很琐碎但实际上节省了你后面排查问题的无数时间。3. 数据分析从“能查”到“能看”3.1 基础统计菜系、口味、时长的全局画像数据入库之后分析就正式开始了。分析的第一步永远是先从宏观维度摸清数据的整体面貌对数据分析师来说这一步相当于“先看全貌再下结论”。菜系分布统计是最直观的切入点。一条简单的 SQL 就能统计出不同菜系的菜品数量SELECT category, COUNT(*) AS cnt FROM recipe GROUP BY category ORDER BY cnt DESC LIMIT 10;你会发现不同菜系的数据量差距会很明显这是由数据源本身的内容分布决定的。在做分析的时候要注意一个坑如果统计出来的川菜是 500 道、粤菜是 80 道这不代表川菜一定“更受欢迎”很可能只是数据源里的收录数量有偏差。分析结论要结合数据采集范围来解读不能过度解读。用 pandas 来跑这类聚合分析也很方便连接 MySQL 之后直接读表import pymysql import pandas as pd conn pymysql.connect( hostlocalhost, userroot, password123456, databaserecipe_db, charsetutf8mb4 ) df pd.read_sql(SELECT * FROM recipe, conn) category_stats df.groupby(category).size().sort_values(ascendingFalse) print(category_stats.head(10))口味标签可以基于 flavor 字段做拆分统计。flavor 字段里可能是一条“麻辣,鲜香,微辣”这样的字符串用 pandas 的 str.split 拆开再 explode就能得到每个口味的频次flavor_series df[flavor].dropna().str.split(、).explode() flavor_stats flavor_series.value_counts().head(15)这个分析的结论很直观麻辣、鲜香、清淡这类口味标签的频次会明显领先这背后其实反映了大众口味的基本盘。用这种方式分析比单纯看菜系分布要更有洞察力。3.2 食材关系分析高频食材与搭配规律食材分析是菜谱数据里最有分析价值的部分因为它跳出了菜系、口味的表面分类直接深入到菜品的内容构成。统计高频食材需要用到三张表的联查。SQL 可以这么写SELECT i.name, COUNT(ri.recipe_id) AS cnt FROM ingredient i JOIN recipe_ingredient ri ON i.id ri.ingredient_id GROUP BY i.id, i.name ORDER BY cnt DESC LIMIT 15;用 pandas 做同样的分析则更直观先合并三张表的数据再做分组统计recipe_ingredient_df pd.read_sql(SELECT * FROM recipe_ingredient, conn) ingredient_df pd.read_sql(SELECT * FROM ingredient, conn) merged recipe_ingredient_df.merge(ingredient_df, left_oningredient_id, right_onid) top_ingredients merged.groupby(name)[recipe_id].count().sort_values(ascendingFalse).head(15)做完高频食材统计之后你会发现一个有趣的现象葱、姜、蒜这种基础调味食材会以碾压性的优势霸榜猪肉、鸡肉、土豆紧随其后。这个结论本身不惊艳但它是后续所有食材分析的地基。更有意思的分析是食材搭配规律。你可以把每一道菜涉及的食材两两组合统计哪些食材搭配出现的次数最多。这个分析用 SQL 自连接来做很经典SELECT a.ingredient_id AS ing_a, b.ingredient_id AS ing_b, COUNT(*) AS cnt FROM recipe_ingredient a JOIN recipe_ingredient b ON a.recipe_id b.recipe_id AND a.ingredient_id b.ingredient_id GROUP BY a.ingredient_id, b.ingredient_id ORDER BY cnt DESC LIMIT 10;两个食材一起出现的次数越多说明这个搭配越经典比如“猪肉蒜苗”“番茄鸡蛋”这种家常组合必然排在最前面。这种关联分析虽然简单但思路可以迁移到电商购物篮分析、推荐系统等更复杂的场景拿这个项目练手理解关联规则的基本思想非常合适。3.3 热量与难度健康视角的切入点热量字段让这个项目多了一个贴近生活的分析维度。很多人关注菜谱的热量数据本质上是在关心吃得健不健康这个分析维度在美食类数据项目里是很有传播力的。按热量区间做分布统计可以用 pandas 的 pd.cut 函数把热量字段分成几个区间bins [0, 50, 100, 150, 200, 500] labels [低热量, 较低, 中等, 较高, 高热量] df[heat_level] pd.cut(df[calories], binsbins, labelslabels) heat_stats df[heat_level].value_counts().sort_index()统计结果可以进一步跟菜系叠加分析哪个菜系的平均热量最高哪个菜系的菜品普遍偏清淡低热量这种交叉分析是数据分析报告的精华所在单维度的统计只是描述现象交叉分析才能挖掘出更深层的信息。难度字段同样可以交叉分析。把难度分成“简单、中等、困难”三档然后分析不同菜系的难度分布。你会发现家常菜系里简单难度的占比很高而一些传统功夫菜集中的菜系困难难度的比例明显偏高。这种结论有现实意义想学做菜的人可以参考这个分析结果从简单菜系入手逐步挑战高难度菜系。4. 可视化实现把结论画出来4.1 可视化工具怎么选数据可视化工具的选择决定了图表能不能快速出效果。我在这个项目里最终选了 pyecharts 而不是 Matplotlib原因主要有三点。pyecharts 生成的是 HTML 图表天然支持鼠标悬停查看数值、点击图例筛选数据这些交互功能展示效果比静态图片好一个档次。Matplotlib 生成的 PNG 图片只能看不能用放到报告里没问题但做成可视化系统就差了点意思。pyecharts 的 API 设计非常人性化。柱状图、饼图、折线图这些常见图表几十行代码就能出效果配色、图例、提示框都是默认配置好的不需要像 Matplotlib 那样逐个去调样式。对于数据分析项目来说能把结果快速画出来比花半天调字体大小更有价值。pyecharts 生成的 HTML 文件可以很好地嵌入到 Flask 页面中。每个图表生成一个 HTML 片段用 Flask 的模板引擎拼装起来就是一个完整的可视化大屏。Matplotlib 嵌入网页要转 base64 图片交互性和加载速度都不理想。4.2 核心图表的设计与代码先看一个最基础的柱状图用来展示菜系分布 Top10。pyecharts 的写法非常简洁from pyecharts.charts import Bar from pyecharts import options as opts bar ( Bar() .add_xaxis(category_list) # 菜系名称列表 .add_yaxis(菜品数量, count_list) .set_global_opts( title_optsopts.TitleOpts(title菜系菜品数量 Top10), xaxis_optsopts.AxisOpts(axislabel_opts{rotate: 30}), ) ) bar.render(templates/chart_category.html)这里有个细节菜系名称如果比较长x 轴的文字会重叠需要设置 rotate 旋转角度。我第一次画的时候没注意出来的图一坨字叠在一起完全没法看。这个问题在设计师眼里是小细节但直接影响图表的可读性。食材分析用词云图来展示效果最好。pyecharts 的 WordCloud 类型可以传 (词, 权重) 的元组列表直接生成词云from pyecharts.charts import WordCloud wc ( WordCloud() .add(, data_pair, word_size_range[20, 80]) .set_global_opts(title_optsopts.TitleOpts(title高频食材词云)) ) wc.render(templates/chart_ingredients.html)词云图的视觉冲击力很强高频食材的字号大小一眼就能看出差距很适合放在可视化大屏的中央位置。口味分布用饼图或者环形图。环形图比饼图更现代在视觉上不会显得太笨重推荐直接使用环形图。from pyecharts.charts import Pie pie ( Pie() .add(, list(zip(flavor_labels, flavor_values)), radius[40%, 70%]) .set_global_opts(title_optsopts.TitleOpts(title口味偏好分布)) ) pie.render(templates/chart_flavor.html)这里 radius 参数中的 [40%, 70%] 控制的就是环形图的内径和外径设置成两个值之后就变成了环形图。如果只传一个值画出来的就是实心饼图。这个小参数调整能明显提升图表的精致度。4.3 拼大屏把图表组装成系统图表单独看没问题但要形成“系统”就需要一个页面把它们组装起来。我用 Flask 来搭建这个展示层。Flask 的优势是轻量一个 app.py 文件就能跑起来配合模板继承的机制页面结构可以组织得很清晰。Flask 的主程序只需要做一件事把之前生成的各种图表 HTML 内容读出来渲染到主模板里。pyecharts 提供了一个便捷方法可以直接嵌入图表内容不需要每个图表单独 render 成文件from flask import Flask, render_template from pyecharts import options as opts from pyecharts.charts import Bar app Flask(__name__) app.route(/) def index(): bar ( Bar() .add_xaxis([川菜, 粤菜, 湘菜]) .add_yaxis(数量, [500, 200, 300]) .set_global_opts(title_optsopts.TitleOpts(title菜系分布)) ) return render_template(dashboard.html, chartbar.render_embed()) if __name__ __main__: app.run(debugTrue)在 templates/dashboard.html 里设置一个容器把图表内容渲染进去即可。页面布局上用 CSS Grid 或者 Bootstrap 的栅格系统把柱状图、饼图、词云图、折线图排布成 2 列或者 3 列的网格就是一个完整的数据看板了。大屏布局有几个实用经验可以分享。第一页面宽度最好设置成 1440px 以上模拟大屏的视觉效果。第二每个图表卡片设置固定高度避免图表内容过高撑破布局。第三页面背景用深色或者浅灰色和图表默认的白色背景区分开视觉层次更清晰。我第一次拼出来的时候图表和页面背景全白整体观感特别廉价换了个浅灰背景之后质感立刻提升了不少。5. 部署运行与项目目录结构5.1 环境准备与依赖安装整个项目跑起来需要准备的 Python 环境其实很简单核心依赖就几个Flask、PyMySQL、pandas、pyecharts。用 pip 一次性安装pip install flask pymysql pandas pyechartsPython 版本建议使用 3.8 以上。pyecharts 在部分旧版本 Python 下会出现兼容问题如果遇到安装包解析出错优先检查 Python 版本是否过旧。本地如果没有 MySQL需要先安装 MySQL 服务。安装完成后创建一个数据库把项目自带的 recipe_db.sql 文件导入进来mysql -u root -p -e CREATE DATABASE recipe_db CHARACTER SET utf8mb4; mysql -u root -p recipe_db recipe_db.sql导入完成后检查一下三张表里的数据量是否正常。如果导入过程遇到报错一般是 MySQL 版本和 SQL 文件的语法兼容问题把错误信息贴出来搜索基本都能解决。数据库连接配置单独放在一个配置文件中不要在代码里写死。项目目录结构建议这样组织recipe_analysis/ ├── app.py # Flask 主程序 ├── config.py # 数据库连接配置 ├── spider/ │ └── recipe_spider.py # 爬虫脚本 ├── analysis/ │ ├── data_clean.py # 数据清洗脚本 │ └── analysis.py # 数据分析脚本 ├── templates/ │ └── dashboard.html # 可视化大屏模板 ├── data/ │ └── raw_recipes.json # 原始抓取数据 ├── requirements.txt └── recipe_db.sql # 数据库备份文件这个结构把一个数据分析项目的各个模块分得清清楚楚代码维护起来方便也符合课程设计、毕业设计对项目结构规范的要求。5.2 项目运行流程与关键配置整套系统按顺序跑起来需要三步第一步运行爬虫脚本抓取数据第二步运行数据清洗脚本把数据写入 MySQL第三步启动 Flask 应用访问可视化页面。config.py 里数据库连接参数是这个项目里最容易出错的地方DB_CONFIG { host: localhost, port: 3306, user: root, password: 你的数据库密码, database: recipe_db, charset: utf8mb4 }这里的 password 一定要改成你自己的 MySQL 密码charset 一定要写成 utf8mb4少写一个横杠、漏了字符集配置后面都会出现让人抓狂的乱码问题。启动 Flask 之后浏览器访问 http://127.0.0.1:5000 就能看到可视化大屏页面。如果页面打不开先确认 Flask 启动时终端有没有报错再看数据库连接配置是否正确。这类问题八成出在数据库连不上排查效率最高的方式是直接用 Python 交互式环境测试一下数据库连接而不是反复刷新页面。6. 实操中遇到的坑与解决办法6.1 中文乱码字符集问题全复盘中文乱码几乎是所有 Python MySQL 项目的第一个拦路虎我在这个项目里也踩了好几次。乱码的根源很简单就是数据在写入数据库和从数据库读取这两个环节中编码方式不一致。解决办法要三层同时到位。第一层是 MySQL 建库和建表时指定 utf8mb4 字符集第二层是连接数据库时 PyMySQL 的 charset 参数要写 utf8mb4第三层是 Python 代码文件本身保存为 UTF-8 编码。任何一层漏了都会出现不可预知的问题。最隐蔽的一个坑是CREATE TABLE 的时候已经指定了 utf8mb4但 show create table 一看某几个字段还是 latin1。这种情况通常是 MySQL 默认字符集配置有问题在建库语句里再显式指定一次 DEFAULT CHARACTER SET utf8mb4 就能解决。如果数据已经用错误的字符集写入直接改字符集是改不回来的需要清空表重新导入数据。6.2 数据库连接失败认证与驱动的坑PyMySQL 连接 MySQL 失败是最让人沮丧的问题因为报错信息往往看不懂。最常见的错误是 Access denied for user这个看起来像密码错误实际上有时候是 MySQL 8.0 以上版本默认的认证方式问题。MySQL 8.0 之后默认使用 caching_sha2_password 认证插件而早期版本的 PyMySQL 不兼容这种认证方式连接时会直接报错。解决办法有两种一是升级 PyMySQL 到 1.0 以上版本新版已经支持 caching_sha2_password二是把 MySQL 用户的认证方式改成 mysql_native_password。第一个办法更简单优先尝试。另一个容易被忽略的问题是端口冲突。如果你本地之前装过其他数据库服务或者 MySQL 没有使用默认端口 3306连接时就要在 config.py 里显式指定正确的端口号。用命令行先测一下 mysql -u root -p -P 3306 能不能连上能连上再回去改 Python 代码能省不少排查时间。6.3 pyecharts 图表显示与交互问题pyecharts 生成图表过程中也有过几个小问题。第一个是中文标签在浏览器中显示为方框这个问题在新版本中已经比较少见了如果遇到检查一下操作系统的中文字体是否正常安装。第二个是图表在 Flask 页面上显示不出来而单独打开 render 出来的 HTML 文件却能正常显示这个问题的原因通常是模板中引用图表的方式不对检查一下是否使用了正确的 auto-render 脚本。还有一个体验层面的问题用 render_embed 方式嵌入图表时每个图表默认加载一次完整的 JavaScript 库页面图表多了之后首次加载会比较慢。解决办法是使用 pyecharts 提供的 Page 对象把多个图表组织在一个页面中只加载一次公共资源加载速度会有明显提升。这些坑都不是什么高深的技术难题但每一个都能卡住新手一两个小时。我在做这个项目的过程中养成了一个习惯遇到问题先把完整的报错信息复制下来用搜索引擎搜索大部分问题在五分钟内就能找到答案。真正困难的问题反而少见耗时最多的永远是数据清洗和字段解析这种不显眼的基础工作。这个项目做完之后我从头到尾把整套代码重新看了一遍又把文档补全整个过程最大的体会是一个数据分析项目最核心的价值不是图表多好看、用了多高深的算法而是能把数据从采集到展示的整条链路打通每个环节都经得起推敲。如果你也想拿这个项目练手建议不要直接抄代码先自己把表结构设计出来再去想分析逻辑最后才看参考实现。踩过那几个经典的坑之后你收获的东西会比代码本身多得多。