
简介包含4927条菜式信息的《食谱菜谱大全》数据库资源包面向烹饪爱好者、数据分析师及餐饮应用开发者提供结构化菜品数据解决菜谱来源分散、难以批量检索与统计的问题。压缩包共4个文件约10.35MB涵盖SQL、JSON、CSV、XLS四种常用格式内容包含菜名、做法、功效、烹饪时间和烹饪方式等字段SQL文件便于在关系型数据库中筛选排序JSON适合Web端交互与集成CSV可导入Excel做清洗分析XLS则便于图表透视与公式计算四种格式可根据使用习惯灵活选择。资源包结构紧凑无需额外处理即可直接导入主流数据库或分析工具。目前已有4690人学习下载。利用这份数据可开展菜系分布与地域饮食文化研究对比不同烹饪方式与时长找出高效做法依据功效字段挖掘健康膳食规律也可快速筛选出烹饪时间短的快手菜或按功效推荐菜品进而构建个性化食谱推荐系统、营养管理工具或在线教学平台为烹饪学习、数据分析和应用开发提供扎实的素材基础。1. 食谱菜谱大全数据库一份能直接跑查询的菜谱数据集做数据库课程设计、练习 SQL 查询、或者给餐饮小程序攒初始数据时最缺的不是技术方案而是一份字段完整、张数够多、能直接导入的干净数据。这份食谱菜谱大全数据库本质是一套多格式的菜谱数据集覆盖 SQL、JSON、CSV 三种常见载体把菜名、分类、食材、调料、步骤、营养信息等维度都做进了结构化字段里。对我来说它最有用的地方在于拿到手不用洗数据导入 MySQL 或者 SQLite 就能开始写联表查询练窗口函数、做菜谱推荐、做用料统计都能直接落在真实数据上。适合正在做课设的学生、准备面试前想刷查询语法的开发者以及需要内容数据做原型验证的产品和技术人员。往下读之前先给你提个醒这份数据真正的坑不在下载而在导入时的编码、类型推断和关联关系处理上本文后面会把每条都拆开讲。2. 数据结构与设计思路先搞清楚表里装了什么再动手导库2.1 实体关系与字段设计这套数据是按“菜”为核心组织的拿到数据库压缩包后第一件事不是急着导入而是先打开目录结构看清楚里面到底有哪几张表。常见做法是菜品主表配若干张从表而不是把所有信息塞进一张大宽表。菜品主表存的是菜品的全局属性比如菜名、分类、菜系、难度、准备时间、烹饪时间、总耗时、热量预估、口味标签。从表则负责存储一对多的内容像食材清单、调料清单、步骤描述、标签映射。这样设计的好处是修改某种食材的价格或名称时只需要在一张食材表里更新不用去反查每道菜的大宽表做联表查询时也能更清晰地把逻辑写出来。我拆完这套数据后建议你重点看三张表菜品主表、食材明细表、步骤表。菜品主表是核心入口食材明细表通过菜品 ID 和主表关联每行记录一道菜所需的一种食材及其用量步骤表同样通过菜品 ID 关联每一行是一个步骤序号加一段操作描述。如果你在数据库里看到多对多的表那通常是菜谱和标签的关系——道菜可以属于多个标签比如“低脂”“快手”“下饭菜”一个标签也能对应多道菜。2.2 三种格式的差异SQL、JSON、CSV 各自的适用场景这份资源贴心地给出了三种格式但别以为它们是等价的。SQL 文件最完整里面不仅有建表语句还有插入语句适合直接灌进 MySQL、PostgreSQL、SQLite 这类关系型数据库JSON 格式保留了完整的嵌套结构适合写程序解析、导入 MongoDB 或者用 Python 的 json 模块做数据加工CSV 格式则是给电子表格软件和 ETL 工具准备的下文会提到它有个经典的大坑空值处理。我的建议是如果你是做课程设计主要用 SQL 文件如果你要基于这份数据写后端接口优先用 JSON如果你只是想快速浏览内容或者做数据清洗练习再打开 CSV。三者的字段顺序理论上一致但我实际对比后发现 JSON 里有几个字段是嵌套对象CSV 则拍平成了带下划线的列名导入时要注意一一对应。下面用一张表概括三者的差异格式典型应用场景数据完整性导入难度SQLMySQL、SQLite 直接导入完整含表结构和数据低JSON程序解析、文档型数据库完整保留嵌套关系中CSVExcel 打开、ETL、pandas 读取可能丢失类型信息中空值需处理2.3 字段裁剪建议不是所有列都要用这套数据集的字段设计偏通用实际做项目时我用不上全部字段。比如做小程序端展示只需要菜名、分类、图片、步骤、主要食材做数据统计分析才需要耗时、热量、难度这些数值型字段。你可以按自己的场景裁剪不要一股脑全部导入字段越多后面维护时越容易乱。我的习惯是先建一个最小视图把核心字段抽出来等业务需要时再补列。这里给一套兼顾查询练习和真实业务的推荐字段组合菜品 ID、菜名、分类、菜系、难度、总耗时、热量、主料列表、步骤列表。这套组合既能支撑大多数查询题目又能直接对接点餐系统的菜单展示。如果你做的是更垂直的减肥餐推荐可以额外保留热量和营养成分字段做筛选。3. 把数据导入数据库两套方案覆盖 MySQL 和 SQLite3.1 方案一直接导入 SQL 文件进 MySQL拿到 SQL 文件后第一步是在 MySQL 里创建一个独立的数据库避免和已有的业务库混在一起。命令行中执行CREATE DATABASE recipe_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE recipe_db; SOURCE /path/to/recipes.sql;逻辑说明这里特意指定了 utf8mb4 字符集而不是默认的 utf8因为菜谱数据里很可能出现生僻字、特殊符号或者 emoji 风格的标签字符utf8mb4 是 utf8 的超集可以兼容四字节字符导入时不会报 1366 错误。COLLATE 选 utf8mb4_unicode_ci是为了后续查询时中文排序和比较行为更可预期。SOURCE 是 MySQL 客户端的导入命令路径建议不要包含中文和空格出问题概率更低。导入完成后验证一下数据是否完整。我一般会查三个数菜品总行数、食材明细行数、步骤总行数。如果 SQL 文件里的 INSERT 语句很长终端显示导入成功但实际只插了一部分这种半截导入最常见的原因是客户端默认的 max_allowed_packet 太小。遇到这种情况可以临时调大这个参数SET GLOBAL max_allowed_packet 128 * 1024 * 1024;这是在导入前设置的不是导入后。参数含义是把允许的最大数据包临时扩大到 128MB适合导入包含长文本的菜谱数据。注意这个设置重启 MySQL 后会失效生产环境需要写进配置文件才持久。3.2 方案二SQLite 零配置方案适合快速验证如果你不想装 MySQL或者只想拿到数据后立刻用 Python 做分析SQLite 是更快的路。SQL 文件如果本身是 MySQL 语法不能直接喂给 SQLite但这份数据集比较贴心也可以用 Python 直接解析 JSON 文件写入 SQLiteimport sqlite3 import json conn sqlite3.connect(recipes.db) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS recipes ( id INTEGER PRIMARY KEY, name TEXT, category TEXT, cuisine TEXT, difficulty TEXT, total_time INTEGER, calories REAL ) ) with open(recipes.json, r, encodingutf-8) as f: data json.load(f) for item in data: cursor.execute( INSERT INTO recipes (id, name, category, cuisine, difficulty, total_time, calories) VALUES (?, ?, ?, ?, ?, ?, ?), (item[id], item[name], item[category], item[cuisine], item[difficulty], item[total_time], item[calories]) ) conn.commit() conn.close() print(导入完成)逻辑说明这段代码做了两件事先建表再逐条插入。建表时显式声明了字段类型避免 JSON 里的数字被全被当成文本存进去。比如 total_time 声明为 INTEGER插入的 45 会被转成整数 45后续做大于等于比较时不会出现类型错位。calories 声明为 REAL因为热量很可能是小数。这里没有使用 executemany数据量大时逐条插入会慢一点但胜在逻辑直观适合入门理解。3.3 验证导入结果写三条常用检查 SQL导完之后先别急着写复杂查询跑三条基础语句确认数据没有问题。SELECT COUNT(*) FROM recipes; SELECT COUNT(*) FROM recipe_ingredients; SELECT category, COUNT(*) FROM recipes GROUP BY category ORDER BY COUNT(*) DESC LIMIT 10;第一条统计菜品总数确认主表行数和压缩包说明一致。第二条统计食材明细行数用来确认关联表的数据没有丢。第三条按分类统计菜品数量并取前十这能直观看出数据的分布是否合理。如果分类结果里出现大量 未分类 或者 其他 的标签说明数据质量一般后续查询时要留意这部分脏数据。4. 实战查询从增删改查到菜谱推荐把 SQL 练到熟练4.1 用聚合查询做分类统计理解 GROUP BY 的边界一道很经典的问题是每个分类下菜品数量的分布以及各分类的平均烹饪时间。这正好拿来练 GROUP BY 与聚合函数配合写法。先看一个统计分类菜品数量和平均总耗时的查询SELECT category, COUNT(*) AS dish_count, ROUND(AVG(total_time), 1) AS avg_time FROM recipes WHERE total_time IS NOT NULL GROUP BY category HAVING COUNT(*) 5 ORDER BY avg_time DESC;逻辑说明WHERE 在前面先把空值过滤掉避免 AVG 计算结果被 NULL 干扰ROUND 是保留一位小数让结果更简洁GROUP BY category 对分类做分组HAVING 在分组后对组做过滤只保留菜品数不少于 5 个的分类排除只有一两条数据的冷门分类。执行这个查询后你能直接看出哪些分类的菜适合做“快手菜”哪些分类需要花时间炖煮。GROUP BY 最常见的认知误区是SELECT 后面出现的列除了聚合函数包裹的列必须都在 GROUP BY 里。MySQL 的 ONLY_FULL_GROUP_BY 模式默认是开启的如果 SELECT 中混入一个既没有聚合、也不在 GROUP BY 里出现的列就会直接报错。菜谱数据里尤其要注意“步骤表”这种一对多表——统计步骤数量时千万不能把步骤 ID 查询到 SELECT 列表里否则会出现语义错误。4.2 多表联查找到同时含两种食材的菜菜谱数据最有意思的查询是“我冰箱里只有土豆和鸡胸肉能做什么菜”。这个需求要拆成两个条件菜品必须同时拥有两种食材。这时需要两次 JOIN 食材表SELECT DISTINCT r.name, r.category FROM recipes r INNER JOIN recipe_ingredients i1 ON r.id i1.recipe_id AND i1.ingredient_name LIKE %土豆% INNER JOIN recipe_ingredients i2 ON r.id i2.recipe_id AND i2.ingredient_name LIKE %鸡胸肉% LIMIT 20;逻辑说明两个 JOIN 分别过滤出含有土豆的菜和含有鸡胸肉的菜再把两个结果集交到同一道菜上。关键点在于两次 JOIN 使用同一个 recipe_id 关联所以只有同时满足两个条件的菜才出现在结果里。DISTINCT 防的是同一道菜在数据里出现多条重复记录比如步骤表被误关联进来的情况。这个查询非常接近真实业务场景用户勾选食材系统反查可做菜品。4.3 根据现有食材凑合做菜优先匹配食材数量多的菜更进一步如果想知道“用我手上这几种食材最多能做出什么菜”这就需要把“食材覆盖率”算出来。思路是先找出所有包含目标食材的候选菜然后按命中食材数量倒序排序。这个查询用窗口函数更清晰WITH target AS ( SELECT ingredient_name FROM ( VALUES (土豆), (鸡胸肉), (青椒), (洋葱) ) AS t(ingredient_name) ) SELECT r.name, COUNT(DISTINCT i.ingredient_name) AS matched_count FROM recipes r INNER JOIN recipe_ingredients i ON r.id i.recipe_id INNER JOIN target t ON i.ingredient_name LIKE % || t.ingredient_name || % GROUP BY r.name ORDER BY matched_count DESC LIMIT 15;逻辑说明WITH 子句先定义目标食材集合主查询里通过 JOIN 把候选菜里命中的食材数统计出来。COUNT(DISTINCT ...) 防止一道菜里同一个食材出现多行记录比如“土豆 300 克”“土豆 50 克”这种拆行数据会被正确去重。这里用 LIKE 是因为食材列里可能存的是“土豆丁”“土豆块”精确等值匹配会漏掉这些变体。实际执行后发现覆盖率最高的菜品会优先展示这比纯粹的关键词搜索靠谱得多。4.4 修改与删除操作注意先查关联关系再动主表课设里经常有删除菜品的操作但菜谱数据是典型的父子表结构主表删除从表的关联数据如果不同步删就是孤儿数据。SQL 文件如果建表时写了外键约束删除子表未处理时会直接报错如果没写约束就会静默留下垃圾数据。处理时我一般分两步走DELETE FROM recipe_ingredients WHERE recipe_id 42; DELETE FROM recipe_steps WHERE recipe_id 42; DELETE FROM recipes WHERE id 42;顺序很重要先删子表再删主表。如果反了主表记录没了子表数据还在后续查询 JOIN 会产出大量 NULL。另一条经验是这类数据集的 ID 是自增主键但 JSON 文件里可能不是从 1 开始连续编号的删除时别用小于某数字这种批量删法不要凭对连续编号的假设去批量操作老老实实按具体 ID 和条件来。5. 避坑指南导入期和查询期的五个坑踩一个都得返工5.1 坑一导入 CSV 时中文菜名全变成乱码现象用 Navicat 或 Python pandas 导入 CSV 时中文菜名显示为“鏉¤彍”或“”。原因CSV 文件保存时的编码与导入工具默认编码不一致。常见的是文件本身是 GBK 或 GB18030而导入工具默认按 UTF-8 解析。菜谱数据里中文内容占比极大这个坑几乎必然遇到。解决打开 CSV 文件前先确认编码。可以用文本编辑器比如 VS Code右下角查看文件编码如果是 GBK就转成 UTF-8 再导入。Python 读取时显式指定编码import pandas as pd df pd.read_csv(recipes.csv, encodinggbk)如果转码后仍然乱码说明原始文件虽有中文但文件头里没有声明编码格式。这时候只能用 notepad 批量转换编码再配合encodingutf-8-sig读取后者会自动去掉 UTF-8 BOM 头避免第一列列名多出一个看不见的字符。从那以后我每次导入 CSV 前都强制自己先看一眼编码这个习惯帮我躲过了无数次乱码返工。5.2 坑二CSV 中的空值导入后变成空字符串而不是 NULL现象导入后发现 WHERE total_time IS NULL 查不到任何数据但很多菜明明没有记录烹饪时间。原因CSV 里的空单元格被导入工具读成了空字符串 而不是数据库语义上的 NULL。空字符串参与数值计算时MySQL 会发生隐式类型转换比较时经常得到不可预期的结果。解决导入后先做一轮数据清洗把空字符串改成 NULLUPDATE recipes SET total_time NULL WHERE total_time ;这条命令很简单但必须在任何统计查询之前执行。因为 AVG、SUM 会忽略 NULL但不会忽略空字符串空字符串参与聚合会让统计结果偏离常识。另一个从源头避免这个问题的办法是导入 CSV 前在导入工具中把“空字符串转为 NULL”的选项打开。这个选项藏得比较深MySQL Workbench 里叫“Treat empty strings as NULL”Navicat 里则在高级选项里眼尖才能找到。5.3 坑三忽略外键约束导入子表时先塞数据导致关联失败现象先导入食材明细表再导入菜品主表结果食材表里有 100 条记录的 recipe_id 在主表里找不到对应菜品。原因SQL 文件中的导入顺序是约定好的先主表后从表。如果手动分开导入且数据库外键约束是关闭状态这类孤儿数据不会被拦截只在联查时悄悄暴露。解决全量导入时直接用 SOURCE 执行完整 SQL 文件不要拆开执行。如果必须分表导入先把外键检查临时关闭SET FOREIGN_KEY_CHECKS 0; -- 导入食材明细表 SET FOREIGN_KEY_CHECKS 1;这个开关在 MySQL 里默认是 1也就是开启检查置为 0 只是临时跳过外键校验导入完成后一定记得改回来。我见过有人关了外键检查后忘了恢复后续开发时写入非法数据不报错整个数据链路变成黑匣子问题排查成本极高。宁可导入慢一点也别关检查。5.4 坑四对中文字段排序结果不按拼音也不按笔画现象ORDER BY name 排序结果既不是拼音顺序也不是笔画顺序看起来像是随机排列。原因MySQL 中对中文排序依赖排序规则默认的 latin1 或 utf8_general_ci 对中文的排序能力很弱最终按二进制编码排序结果就是所谓的“乱序”。这在实际展示菜谱列表时非常刺眼。解决建库时声明 utf8mb4_unicode_ci 或 utf8mb4_zh_0900_as_cs。如果库已经建好了可以单独指定排序规则SELECT name FROM recipes ORDER BY name COLLATE utf8mb4_zh_0900_as_cs;注意utf8mb4_zh_0900_as_cs 只在 MySQL 8.0 及以上版本可用8.0 以下要用 utf8mb4_unicode_ci中文排序效果差一点但至少有稳定的顺序。如果对排序的准确性要求极高最好的办法是不依赖数据库排序在应用层用拼音库排序。5.5 坑五JSON 格式里的嵌套字段直接平铺导入会丢数据现象JSON 里 ingredients 是一个数组里面每个元素包含名称、用量、单位三个属性导入关系型数据库时只取第一个元素其他食材全部丢失。原因把 JSON 当 CSV 用试图用一行数据表示一道菜的全部信息嵌套结构被强行拍平后只能保留一个值。解决写导入脚本时先展开嵌套数组再逐行插入子表# 展开方式示例 for item in data: for ing in item[ingredients]: cursor.execute( INSERT INTO recipe_ingredients (recipe_id, ingredient_name, amount, unit) VALUES (?, ?, ?, ?), (item[id], ing[name], ing[amount], ing[unit]) )这个处理逻辑最容易被忽略因为导入主表时一切正常直到查食材明细时数据行数对不上才发现问题。经验总结是拿到嵌套结构的数据先画一遍 JSON 层级图再决定建几张表千万不要偷懒只建一张大表存 JSON 字符串——那等于把数据库用成了文本文件后续任何联查和统计都会非常痛苦。6. 进阶玩法从菜谱库到推荐功能做一个最简单的“食材匹配”模块当你能熟练对这份菜谱库做增删改查之后可以再往前走一步把静态的数据库变成能响应需求的小功能。这里分享一个我常用的做法不写复杂算法就靠 SQL 加 Python 搭一个“冰箱里有什么”的食材匹配模块它非常适合课程设计的创新点也是面试时能讲清楚业务场景的功能。实现思路分两层数据库层负责按食材模糊匹配候选菜应用层负责计算覆盖率并排序输出。第一步在 SQLite 里创建一个视图把每道菜和它的全部食材拼接成一个字段CREATE VIEW dish_ingredients_view AS SELECT r.id, r.name, GROUP_CONCAT(i.ingredient_name, ;) AS all_ingredients FROM recipes r LEFT JOIN recipe_ingredients i ON r.id i.recipe_id GROUP BY r.id, r.name;第二步在 Python 里计算用户手上的食材能命中几道菜def recommend_dishes(have_ingredients, cursor): results [] for row in cursor.execute(SELECT id, name, all_ingredients FROM dish_ingredients_view): matched sum(1 for ing in have_ingredients if ing in row[all_ingredients]) if matched 0: results.append((row[name], matched)) return sorted(results, keylambda x: -x[1])[:10]这个模块虽然简单但已经能满足“食材推荐”的核心逻辑。实际运行中需要注意GROUP_CONCAT 默认长度限制是 1024 字节如果一道菜的食材特别多拼接结果会被截断。需要先执行SET SESSION group_concat_max_len 4096;再创建视图不然推荐结果会偶尔缺食材。这门课设级别的小功能做完之后我自己也把这份数据用在了另一个小项目里把菜谱库导入到内存型 SQLite 中供后端接口实时查询整个响应时间控制在毫秒级。最后说一个我的习惯。因为数据导入的坑踩多了现在每拿到一份数据库资源我都强制自己先写一个验证脚本导入完成后立刻跑三条基础检查总行数、日期或数值字段的非空率、外键关联的完整性。这三项过了我才敢放心往下写业务逻辑。从那以后凡是过不了这三关的数据我宁可不用也不会带病上线。希望这份食谱菜谱数据库的拆解能帮你少走几步弯路如果你在导入或者查询中卡住了对照本文第五部分的坑去逐个排查大概率能直接找到病根希望帮到你。本文还有配套的精品资源点击获取