
简介这份MySQL菜谱数据库适合美食类App开发者、数据分析爱好者以及需要搭建菜品展示或推荐系统的站点运营者提供13万条真实菜谱数据。数据结构清晰分为catalog目录表、dishes菜谱表以及link目录-菜谱关联表目录表可用于菜系、分类等维度的导航菜谱表记录菜品名称、用料、做法等核心信息关联表则建立多对多关系便于灵活查询。搭配描述中提供的36G菜谱图片资源可快速完成图文并茂的菜品展示页或用于食材分析、热门菜谱统计等数据应用。整个资源包共4个文件含3个SQL脚本和1个说明文本压缩后大小约52.48MB导入MySQL即可使用适合有一定SQL基础、希望用真实数据搭建项目原型或练习复杂查询的开发者和学生。当前已有941人学习下载物超所值。1. 菜谱食谱数据落到 MySQL13万条数据与36G图片值不值先看两件事拿到一份“菜谱食谱数据-mysql【13万条数据36G图片超值】”的数据包第一反应通常不是研究表结构而是赶紧建库导数据结果一个晚上耗在乱码、磁盘爆满和图片路径失效三件事上。这类菜谱数据的核心价值其实不在那13万条文本而在配套的36G图片资产它能支撑菜品识别模型、食材搜索和前端展示但前提是 MySQL 的表结构先搭对。如果你正打算做菜谱检索、食材反向查询、美食推荐或菜品图像分类这篇会把从建表、导入到图片落库、踩坑的完整链路讲清楚帮你判断值不值得投入。2. 拆解菜谱数据表结构与图片资产导入前先跑一遍数据体检声明是“mysql”的菜谱数据包经常只是 mysqldump 导出的数据甚至给的是 JSON、CSV 和图片压缩包直接导入会因为字符集、路径、字段缺失各种问题中断。所以我的顺序是先把表结构和图片文件“看到底”再决定怎么建库。别急着解压和 LOAD DATA体检这一步省掉的每一分钟后面都要用返工来还。2.1 菜谱主表与食材关联表建表 SQL 与字段取舍一份可用的菜谱数据核心字段通常是菜名、分类、菜系、难度、烹饪时间、简介、封面路径、步骤文本外加一组食材列表。13万条的体量不算大主键用 INT UNSIGNED 即可不需要上 BIGINT但菜名和步骤里会有中英文混合、emoji、空格VARCHAR 长度和字符集必须一次定对否则导入后改字段类型非常痛苦。我一般会把字符集固定为 utf8mb4。食材是典型的多值字段最忌讳的做法是在菜谱主表里加一个 ingredients JSON 字段。虽然存起来方便但“我想查所有用到五花肉的菜谱”只能靠 LIKE 扫全表13万行也会被拖到几百毫秒以上。正确做法是拆一张 recipe_ingredient 关联表每一行记录一个食材is_main 标记主料还是辅料amount 存用量描述。这样反向查询走索引后面做推荐也能直接复用。CREATE TABLE recipe ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 菜谱ID, name VARCHAR(200) NOT NULL COMMENT 菜名, category VARCHAR(50) NULL COMMENT 分类热菜/凉菜/汤羹等, cuisine VARCHAR(50) NULL COMMENT 菜系川菜/粤菜等, difficulty TINYINT NULL COMMENT 难度1简单 2中等 3困难, cook_time SMALLINT NULL COMMENT 烹饪时间单位分钟, description TEXT NULL COMMENT 简介, cover_path VARCHAR(255) NULL COMMENT 封面相对路径, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_cuisine (cuisine), KEY idx_category (category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; CREATE TABLE recipe_ingredient ( recipe_id INT UNSIGNED NOT NULL COMMENT 菜谱ID, ingredient VARCHAR(50) NOT NULL COMMENT 食材名, is_main TINYINT NOT NULL DEFAULT 0 COMMENT 1主料 0辅料, amount VARCHAR(100) NULL COMMENT 用量描述, KEY idx_ingredient (ingredient, recipe_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;id 用自增主键name 不需要单独建索引后面会用全文索引覆盖cuisine、category 是查询高频条件先建单列索引。difficulty 用 TINYINT 而不是 VARCHAR是为了避免“简单/中等/困难”这类脏数据混进来清洗阶段统一映射成分级数字。cover_path 统一存相对路径不存绝对路径这是为了换服务器、挂对象存储都方便。recipe_ingredient 不需要自增主键recipe_id ingredient 组合就足够定位加主键只会白白占用空间。2.2 36G 图片的目录与命名先看文件组织再定存储策略图片包不管压缩前是多大解压后到 36G 这个量级先别急着全部解压。先看压缩包里的顶层目录和命名规律是全部图片平铺在一个目录还是按 id 分桶、按菜系分目录。平铺意味着单目录文件数可能上万文件系统在大目录下遍历会变慢按 id 分桶则要注意桶的切分方式常见做法是取 id 后两位或三位做二级目录。命名规则看起来是小细节但它决定了 cover_path 怎么写进 MySQL。unzip -l recipe_images.zip | head -20 unzip -l recipe_images.zip | wc -l du -sh images find images -type f -name *.jpg | wc -lunzip -l 只列目录不实际解压先看顶层结构du -sh 看解压后的总大小确认磁盘余量是否够find 统计实际 jpg 数量跟数据里的 id 数对比。如果发现图片文件数比数据行数少很多说明数据包里的 cover 字段本来就缺了不少图导入后再来补就晚了。存储策略上我一般把图片放在独立的 images 根目录数据库里存相对路径。比如 cover_path 存“12/3/12345.jpg”业务层拼出完整 URL。绝对路径在你自己机器上能用换环境、上容器、做负载均衡时全会变成无效路径这是菜谱数据包里最常见也最坑的一处。若数据包里已经是绝对路径导入前批量替换成相对路径再移动文件到统一根目录。2.3 数据体检缺失、重复、图片对不上的三个脚本判断建表之前拿一个样本文件先跑体检。检查三件事必填字段id、name是否缺失id 是否有重复cover 路径对应的图片文件是否真实存在。这三类问题决定了你是“导入前清洗”还是“导入后返工”后者代价明显更高。示例脚本用 pandas 读一个 json 切片实际包是 CSV 也可以把 read_json 换成 read_csv。import pandas as pd from pathlib import Path img_root Path(images) df pd.read_json(sample.json) print(总行数:, len(df)) # 1. 必填字段缺失检查 for col in [id, name]: print(col, missing:, df[col].isna().sum()) # 2. id 重复检查 print(dup id:, df[id].duplicated().sum()) # 3. 图片路径存在性检查 def img_exists(path): return bool(path) and (img_root / str(path)).exists() df[has_img] df[cover].map(img_exists) print(cover 缺失或路径不存在:, (~df[has_img]).sum())脚本输出三组指标缺 name 的行数说明文本质量dup id 说明数据包是否做过主键去重cover 缺失率说明图片资产有多少比例对不上。对 dup 的处理我建议保留第一条、删掉重复行而不是用 INSERT IGNORE因为 INSERT IGNORE 静默丢弃数据后面排查时察觉不到。体检结果需要落成两份产物一份是清洗后的 CSV另一份是 missing_img.txt列出所有图片缺失的菜谱 id。后续导入主表时把缺失封面统一置 NULL查询时再回退到默认图比导入后再全表 UPDATE 快得多。别嫌这一步麻烦在13万行上备份和返工的成本远高于这里跑一次脚本。3. 13万条数据导入 MySQL从清洗到批量写入的三步流程清洗脚本跑完接下来真正导入。13万行不算海量但如果你不管 MySQL 参数就直接 LOAD或者在 Python 里逐条 INSERT都能把一个晚上耗在这批数据上。先确认三件事磁盘临时目录空间够不够、binlog 要不要临时关、连接字符集是否与表一致。这三件事没落实后面每个报错都要花双倍时间排查。3.1 把 JSON 拍平成三张表归一化后再考虑入库数据包里的 JSON 和最终 MySQL 表结构通常不同源不能直接导。需要把菜谱、食材、步骤拍平。步骤文字合并成主表的一个 mediumtext 字段食材拆进关联表菜谱字段放主表。拍平后的 CSV 字段顺序要和后面的 INSERT 或 LOAD DATA 完全一致这是最容易翻车的地方。import csv import json with open(recipe.json, encodingutf-8) as f: rows json.load(f) # 难度字符串映射为数字避免脏数据直接入库 diff_map {简单: 1, 中等: 2, 困难: 3, 高难度: 3} with open(recipe_clean.csv, w, newline, encodingutf-8) as r_f, \ open(ingredient_clean.csv, w, newline, encodingutf-8) as i_f: r_w csv.writer(r_f) i_w csv.writer(i_f) r_w.writerow([id, name, category, cuisine, difficulty, cook_time, cover]) i_w.writerow([recipe_id, ingredient, is_main, amount]) for row in rows: # 字段缺失时给默认值不要直接跳过 r_w.writerow([row.get(id), row.get(name), row.get(category), row.get(cuisine), diff_map.get(row.get(difficulty), 0), row.get(cook_time, 0), row.get(cover)]) for ing in row.get(ingredients, []): i_w.writerow([row[id], ing.get(name), 1 if ing.get(is_main) else 0, ing.get(amount, )])这里的关键是 row.get 带默认值而不是直接索引取值JSON 里缺字段时不会抛异常。ingredients 为空列表时关联表不产生行但主表照常写入这样不会丢任何菜谱。菜品分类、菜系这类字段我作为字符串原样保留不做字典映射等导入后再用 SQL 统一归类强行在清洗阶段映射反而会把脏数据变成新的脏数据。3.2 用 executemany 分批插入还是 LOAD DATA参数怎么给13万行这个量级两种方式都能接受executemany 分批插入适合需要实时控制的场景LOAD DATA 适合一次性全量导入。我一般先用 executemany好处是可以在同一进程里控制数据质量、出错定位到行号前提是每批 500 到 1000 条太大了事务回滚成本高太小了网络往返浪费。import csv import pymysql conn pymysql.connect(hostlocalhost, userroot, password***, databaserecipe_db, charsetutf8mb4, local_infileTrue) cur conn.cursor() # 分批读 CSVexecutemany 一次性提交 500 行 batch [] sql INSERT INTO recipe (id,name,category,cuisine,difficulty,cook_time,cover) VALUES (%s,%s,%s,%s,%s,%s,%s) with open(recipe_clean.csv, encodingutf-8) as f: reader csv.reader(f) next(reader) for line in reader: batch.append(line) if len(batch) 500: cur.executemany(sql, batch) conn.commit() batch.clear() cur.executemany(sql, batch) conn.commit()batch 每满 500 行提交一次最后一次残留的行也要提交。注意 MySQL 的 max_allowed_packet 默认值在 4M 到 64M 之间如果一行包含很长的步骤文本500 行的包可能超过该值并直接报错遇到这种错误就把批次降到 100 行再试。如果数据已经清洗成规整 CSV用 LOAD DATA 更省事SET GLOBAL local_infile 1; LOAD DATA LOCAL INFILE /data/recipe_clean.csv INTO TABLE recipe CHARACTER SET utf8mb4 FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY LINES TERMINATED BY \n IGNORE 1 LINES (id, name, category, cuisine, difficulty, cook_time, cover);LOAD DATA 比 INSERT 快一个数量级但字段顺序、引号转义、NULL 处理都要对。CSV 里的空字符串默认会变成 NULL 还是空串取决于字段是否可空和 strict 模式如果拿不准先导 1000 行再 SELECT 抽查别一上来全量灌进去。导入期间几个参数值得单独调一下参数建议值说明innodb_buffer_pool_size物理内存的 60%~70%减少导入时的磁盘 IOinnodb_flush_log_at_trx_commit2导入可容忍秒级丢失大幅提升写入速度unique_checks0导入完后必须改回 1local_infile1LOAD DATA 必需的开关导入完成后把这些参数恢复生产值并执行 ANALYZE TABLE 更新统计信息。注意生产库不要直接改这些参数建议在导入专用实例上操作innodb_flush_log_at_trx_commit 改回 1 之前先确认业务是否能接受短时间丢事务。3.3 图片路径回填与落位把 cover 统一成相对路径主表和关联表导完下一步处理图片路径。如果源数据里的 cover 是绝对路径必须先批量替换。我用一个简单的 Python 脚本扫描 CSV把路径里的前缀去掉只保留相对部分同时检查对应文件是否存在不存在的行把 cover 置空避免前端展示死链。from pathlib import Path base Path(/data/images) bad [] with open(cover_map.csv, encodingutf-8) as f: for line in f: rid, rel line.strip().split(,) # 去掉可能的绝对路径前缀统一为相对路径 rel rel.replace(\\, /).split(images/)[-1] if not (base / rel).exists(): bad.append(rid) print(missing:, len(bad))replace 先把 Windows 风格分隔符统一成斜杠再取 images/ 之后的部分。检查通过的行生成新的 CSV最终写成相对路径。文件本身不用移动只要根目录一致就行。这一步的作用是把数据库和文件系统彻底解耦后面想换对象存储、上图片加速SQL 都不用动。4. 菜谱数据查询起来高频 SQL 与索引设计13万条在 MySQL 里正好是一个尴尬区间全表扫也能出结果但做应用时接口响应会明显变慢。菜谱类业务的高频查询其实不多主要就是按食材找菜、按菜系和难度过滤、查详情带步骤和封面。下面几个场景基本覆盖了会用到的 SQL 类型索引怎么建也一并说明。4.1 食材反向找菜关联表 IN HAVING 的组合写法菜谱应用里常有一种反向查询我有五花肉和豆腐能做什么菜。这种查询要在 recipe_ingredient 上做交集而不是并集。只写 IN 会得到用五花肉或用豆腐的所有菜不满足“同时有”正确做法是先 IN 过滤出候选再用 GROUP BY HAVING 保证两个食材都出现。SELECT r.id, r.name FROM recipe r JOIN recipe_ingredient ri ON ri.recipe_id r.id WHERE ri.ingredient IN (五花肉, 豆腐) GROUP BY r.id, r.name HAVING COUNT(DISTINCT ri.ingredient) 2;COUNT(DISTINCT ri.ingredient) 2 这个数字必须和 WHERE IN 的数量一致写死容易出问题生产上我会在服务端拼一个参数 cnt跟传入食材列表的长度保持一致。少了它会变成 OR 语义多了则查不到结果。id 和 name 同时出现在 GROUP BY是因为 MySQL 的 ONLY_FULL_GROUP_BY 模式要求 select 列必须出现在 group by 或聚合函数里。很多原始数据把食材用逗号拼在一个字段里查询时用 FIND_IN_SET 确实也能出结果但它没法用索引每次都是全表扫描。13万行一次过滤可能只要 0.2 秒但放到接口上每次请求都扫一遍就不太能接受了数据从源头就要拆进关联表而不是依赖字符串函数兜底。4.2 菜系、难度、时间过滤组合索引与排序字段选择菜谱列表页最常见的筛选是菜系川菜、难度小于等于2、按烹饪时间升序。单独为每个字段建索引MySQL 通常只能挑一个用这种查询建组合索引更合适。索引列顺序按等值条件在前、范围条件在后放cuisine 是等值difficulty 是范围cook_time 是排序字段。场景建议索引说明菜系 难度筛选(cuisine, difficulty)等值在前范围在后分类 创建时间排序(category, created_at)列表页常用食材反查(ingredient, recipe_id)覆盖关联表查询菜名搜索name 建 FULLTEXT见第6章用 ngram 分词explain 是验证索引是否生效的唯一标准。如果 type 是 ALLrows 显示 13万说明索引没走上最常见的原因是排序字段不在索引里或者筛选字段上写了函数。加好组合索引后explain 里 type 至少应该是 ref 或 rangerows 缩到几百以内查询耗时到几毫秒。EXPLAIN SELECT id, name FROM recipe WHERE cuisine 川菜 AND difficulty 2 ORDER BY cook_time ASC LIMIT 20;排序方向上如果列表要倒序且和索引方向相反MySQL 8 之前的版本会引入 filesort。非要优化的话在排序字段上建 DESC 索引或者让列表页统一按更新时间倒序把 cook_time 这类字段留给详情页查询。排序字段别超过两个超过两个的复合排序在13万行上很容易触发临时表。4.3 封面路径与详情页查询缺图兜底和批量取食材封面字段常常有一批 NULL 或者指向缺失文件查询要统一兜底。以前端接口为例列表页直接返回 cover_path由业务层判断为空时拼接默认图不要在 SQL 里做 IFNULL 拼接完整 URL因为图片域名或前缀属于部署配置不该写死在数据库里。当然简单场景也可以在查询时用 CASE 把空值转成相对默认路径。SELECT id, name, CASE WHEN cover_path IS NULL OR cover_path THEN default.jpg ELSE cover_path END AS cover FROM recipe WHERE category 甜品 ORDER BY id LIMIT 20;WHERE 条件里别对 cover_path 做函数处理比如 WHERE cover_path ! 就好过 WHERE LENGTH(cover_path) 0。返回空串和 NULL 统一成一种格式前端只认一种。分页深了也会慢13万行用 LIMIT 100000, 20 会越走越慢列表页应用 cursor 分页替代页码分页。详情页需要一次返回菜名、步骤、食材列表。步骤存主表 mediumtext食材在关联表先查主表再批量查食材就够用。关键是用 recipe_id IN 一次取回避免在循环里查 N1 次13万行下 N1 会把本该 10ms 的接口拖到秒级。SELECT recipe_id, GROUP_CONCAT(CONCAT_WS(:, ingredient, amount) SEPARATOR ;) AS ingredients FROM recipe_ingredient WHERE recipe_id IN (1001, 1002, 1003) GROUP BY recipe_id;GROUP_CONCAT 默认长度限制是 1024 字节食材特别多的菜谱可能被截断如果发现结果不完整可以用 SET SESSION group_concat_max_len 4096 放宽限制。5. 菜谱数据落地避坑从36G图片损坏到中文乱码的5个现场下面这5个坑几乎每一个都会在批量导入时遇到。文字描述里看不出来只有数据量上来才会爆。每条按现象、原因、解决顺序写看完可以直接对照排查。5.1 图片解压中断与损坏文件训练脚本批量报错现象导入后拿图片做菜品分类训练前几百张正常跑到某一张报 DecodeError 或 Premature end of JPEG file然后进程直接挂掉。单独打开文件却只能看出文件大小异常人工检查不可能逐张看。原因数据经过传输或多次解压坏文件已经混在里面。解压时磁盘满了或中断会留下截断文件。图片这类二进制对截断没有自愈能力尾部丢失就永久损坏。解决先扫一遍把坏文件隔离出来比训练时逐张 try 更可控。扫描脚本用 PIL 打开并加载像素数据打不开的记到 broken.txt然后批量改名或移走不要让模型训练在异常上反复中断。from PIL import Image from pathlib import Path broken [] for p in Path(images).rglob(*.jpg): try: with Image.open(p) as im: im.load() # 完整解码只有 open 不会真正读取像素 except Exception: broken.append(str(p)) print(broken count:, len(broken))im.load() 是关键只 open 不 load 不会完整解码。jpg 以外的 png、webp 同样适用。扫描速度取决于图片大小36G 图片大约要跑几分钟建议先抽样 1000 张估算损坏率损坏率明显偏高就直接找数据方补文件不值得自己花时间修。5.2 utf8 与 utf8mb4emoji 和生僻字入库变成问号现象导入后 SELECT 菜名发现部分行变成“西红柿??蛋”或者直接报错 Incorrect string value。数据在 CSV 里看是正常的进库就没了。原因MySQL 的 utf8 字符集最多存 3 字节emoji 和不少生僻字要 4 字节。建库时用了 utf8写入时要么截断成问号要么直接报错。连接串里指定 charsetutf8 也绕不开这个限制。解决建库建表统一 utf8mb4连接串也用 utf8mb4。ALTER DATABASE recipe_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE recipe CONVERT TO CHARACTER SET utf8mb4;注意如果表里已经有乱码数据CONVERT 不会恢复已被截断的字符必须重新导入。连接参数 pymysql 里写 charsetutf8mb4不要只改表不改连接两边不一致照样会出问题。5.3 近似重复菜谱md5 去重后仍有大量同名同步骤现象按 id 主键导入很顺利但跑推荐时发现同一道“红烧肉”出现十几遍标题相同、图片不同、步骤文本几乎一致。这些不是主键冲突所以导入阶段完全拦不住。原因菜谱数据大多是从不同站点聚合的源站之间互相采集。标题和主料大致一致但图片路径、封面、描述字段有差异导致同一道菜被当成了多条记录。不做内容层去重后面做搜索和推荐质量都会很难看。解决用标题 主料集合拼成签名做近似去重保留评分最高或步骤最长的一条。md5 不需要存库导入前在 pandas 里生成一列即可。import hashlib def signature(row): # 主料排序后拼接消除食材顺序差异 ingredients |.join(sorted(row[ingredients])) s (row[name] or ).strip() | ingredients return hashlib.md5(s.encode(utf-8)).hexdigest()把主料排序后拼接是因为两个菜谱的食材顺序可能不同排序能消除顺序差异。这个方案只能去掉完全相同的签名标题有小改动就漏了要更严谨可用 SimHash 或编辑距离但对 13万行的菜谱数据靠签名已经能去掉相当一部分重复。数据里如果有渠道来源字段建议保留下来叫 source_id方便追溯哪一批数据质量差。5.4 索引失效在13万行上出现的隐式转换与函数查询现象明明给 created_at 建了索引列表页按年份筛选仍然全表扫描explain 里 type 是 ALL耗时从几十毫秒涨到几百毫秒。原因WHERE YEAR(created_at)2024 对索引列套了函数索引顺序被打乱优化器只能放弃索引或者 id 字段是 VARCHAR传入数字时发生隐式类型转换索引同样用不上。这类问题在小数据集上几乎无感13万行时就会变成明显的慢查询。解决改成范围条件让索引列保持裸列。-- 不好对索引列做函数处理 SELECT * FROM recipe WHERE YEAR(created_at) 2024; -- 好用范围查询 SELECT * FROM recipe WHERE created_at 2024-01-01 AND created_at 2025-01-01;隐式转换靠统一字段类型解决字符型主键就不要传数字进 SQL。explain 不只看 key 有没有值还要看 rows 估算行数有没有明显下降。如果 key 有值但 rows 还是几万行通常是联合索引里列的顺序不对调整索引列顺序后再 explain 一次。5.5 导入后磁盘空间锐减binlog 和临时文件占空间的排查现象数据本身占的空间不算大导入完却发现整块盘少了很大一块。删掉数据文件也没见空间回来像是机房磁盘玄学。原因三个部分最常见binlog 记录了导入期间每一条写操作13万行加关联表会产生接近数据量级的日志InnoDB 的 redo log 在导入压力下会维持高位分批导入间的临时表也会占用临时目录空间。三者叠加磁盘当然明显缩水。解决导入结束后先看 binlog确认不需要回滚就清理。临时目录如果爆了把 tmpdir 换到另一块盘。SHOW BINARY LOGS; PURGE BINARY LOGS BEFORE NOW(); ANALYZE TABLE recipe;PURGE 之前要确认不需要基于这些 binlog 做恢复导入专用机上临时关掉 sql_log_bin 是可以接受的操作生产环境不要这么干。ANALYZE TABLE 顺手做掉后面查询的执行计划才准。6. 把这批菜谱数据用起来全文索引与图像分类的最小方案数据落库并清洗干净之后两个最常用的落地方案是文本检索和图像模型。前者直接把 MySQL 变成可用的搜索后端后者把 36G 图片资产变成可迭代的模型训练集。6.1 用 MySQL 全文索引直接做菜名搜索菜谱数据落库之后最直接的应用就是搜索。13万条菜名用 LIKE %红烧肉% 也能出结果但无法按相关性排序MySQL 的 FULLTEXT 索引配合 ngram 解析器可以做中文分词在相同数据量下体验完全不一样。注意全文索引只适合菜名、简介这类短文本步骤那种长文本不建议纳入索引会显著膨胀索引体积。ALTER TABLE recipe ADD FULLTEXT INDEX ft_name (name) WITH PARSER ngram; SELECT id, name FROM recipe WHERE MATCH(name) AGAINST(红烧肉 IN NATURAL LANGUAGE MODE) LIMIT 20;ngram 默认分词长度是 2两个字的菜名也能命中如果发现召回不准可以用 BOOLEAN MODE 加引号做精确短语匹配。全文索引在更新频繁的线上表上要谨慎但对这种基本只读的菜谱表非常合适。6.2 拿36G图片做菜品分类先用小模型把流程跑通36G 图片最有价值的地方是训练图像模型。但别一上来就拿 13 万张原图训练先抽样几个类目用迁移学习把流程跑通。我一般会把训练输入固定为 224x224分类头换成本地菜品类目数只训练分类头不微调底层特征这在小样本下不容易过拟合。第一轮只跑几百张确认数据加载和评估指标正常再逐步放大数据量。import torch import torch.nn as nn from torchvision import models base models.resnet18(weightsDEFAULT) base.fc nn.Linear(512, 10) # 10个菜品类目按自己数据改这里的类目数必须是数据里实际存在的类别数量直接硬编码成 1000 个类会训练得极慢且不收敛。拿菜谱里的 category 字段做标签但先检查类别分布去掉样本数太少的数据。这批数据用到现在最深的体会是数据本身只是原材料真正花时间的是建表、清洗、去重和图片校验。我第一次拿同类菜谱数据时在字符集和绝对路径上各翻了一次车后来每次导入前都先跑一遍体检再动库。把这些前置工作做扎实13 万条文本和 36G 图片才能真正变成应用和模型的地基。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。