ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

DeepSeek评论分析实战:用千万条数据驱动菜单优化

DeepSeek评论分析实战:用千万条数据驱动菜单优化 简介面向餐饮品牌管理者、菜单研发人员及数据分析初学者这份PDF以DeepSeek为核心工具展示如何从千万级评论中挖掘用户偏好并优化菜单。全文26页按项目实战顺序展开先梳理餐饮业数据驱动背景再讲解评论数据的外卖平台/点评网站采集、爬虫与API方法以及去重、缺失值处理、分词等清洗环节随后进入DeepSeek建模涵盖模型选型、情感分析、主题挖掘和关联规则将分析结果转化为保留高满意度菜品、设计套餐组合、平衡成本利润等决策。末尾还提供数据加载、模型训练与可视化的代码实现以及优化前后销售额、客流量、满意度等指标对比方法。资源为单个PDF文件大小1.86MB已有100人学习适合需要利用AI提升餐饮运营效率的从业者和数据实践者。1. 千万条评论不是噪音是菜单改版的底稿餐饮业做菜单优化最贵的不是食材是猜。你猜顾客喜欢什么、讨厌什么猜错了就换菜、打折、再换菜一轮下来成本全花在试错上。而真正答案其实已经摆在平台上那些动辄几万条、几十万条的评论里顾客早就把“太咸”“上菜慢”“孩子爱吃”“拍照好看”这类信号写得明明白白。问题只是数据量太大人工看不完普通分词工具又只能数词频看不出“为什么”。DeepSeek 分析千万评论数据优化菜单这个标题拆开就是一套完整方案用 DeepSeek 这类大模型把非结构化的中文评论转成结构化指标再落到菜品去留、口味调整、定价和出餐顺序上。适合谁适合有门店、有平台评论导出权限但缺一个数据分析岗的餐饮运营者、连锁品牌产品经理以及想接餐饮数据分析项目的自由职业者。这篇文章会把环境搭建、数据清洗、批量推断、成本控制、结果验证整条链路讲完照着做就不需要再拍脑袋换菜单了。2. DeepSeek 接入与基础环境先把“能跑评论分析”的地基打好2.1 选 API 还是本地部署三种接入方式和取舍逻辑做千万级评论分析第一个决策不是选模型而是选接入方式。DeepSeek 目前的常见做法有三种官方 API、自建推理服务、用第三方兼容接口。官方 API 适合绝大多数餐饮数据分析项目零运维按 token 计费批量跑完就关不需要养 GPU 机器。本地部署适合数据敏感、评论数据不能出内网的门店系统代价是要准备显卡和推理框架比如用 vLLM 拉模型起来吞吐量上去了但运维复杂度也上去了。第三方兼容接口常见于已经有云厂商账号的团队通常走 OpenAI 格式的接口代码写起来和官方 API 几乎一样。我的建议是单店或者区域连锁直接走官方 API。原因很现实——千万条评论听上去吓人但真正送到模型里的不是整条评论而是清洗、截断后的结构化片段token 消耗可控。本地部署不是不能做而是如果你们团队没有人能处理显存溢出和推理队列堆积分析没跑完先开始修环境项目就烂尾了。先把业务跑通再考虑私有化。2.2 最小可跑的评论分析环境Python 侧配置与 DeepSeek API 请求代码不管选哪种接入方式代码侧的环境都很轻。常见组合是 Python 3.10、pandas 处理表格、openai 库或 requests 直接调 HTTP 接口。DeepSeek 的 API 兼容 OpenAI 协议所以现有 Python 代码改动很小。下面是完整的最小示例先把一条评论送到 DeepSeek拿回结构化结果验证 Key 和网络通不通。from openai import OpenAI client OpenAI( api_keysk-xxxxxxxxxxxxxxxx, # 从 DeepSeek 开放平台获取 base_urlhttps://api.deepseek.com # DeepSeek 兼容接口的地址 ) resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: 你是餐饮数据分析师只能输出 JSON。}, {role: user, content: 分析这条评论毛血旺量太少了配菜全是豆芽下次不想点了。} ], temperature0.0, response_format{type: json_object}, # 要求结构化输出 ) print(resp.choices[0].message.content)这段代码做了三件关键事。第一通过base_url指向 DeepSeek 的接口而不是默认的 OpenAI 地址这是很多人第一步就翻车的地方。第二temperature0.0强制模型少一点“创作”多一点“服从”评论分析不需要文采需要稳定输出。第三response_format{type: json_object}让模型尽量吐 JSON后续用json.loads直接解析避免从一大段文字里抠字段。运行时如果报AuthenticationError先检查 Key 是否复制了空格报ModelNotFound确认模型名写的是deepseek-chat而不是别名。2.3 千万级评论的处理策略分批、重试与缓存数据量上了千万条逐条请求 API 是灾难。不是 DeepSeek 扛不住是你的网络、Key 限流和钱包扛不住。常见策略是三段式先分批再重试最后缓存。批量大小按单条评论 token 数估算单批 20 到 50 条比较稳重试用指数退避比如第一次失败等 1 秒第二次等 2 秒最多重试 5 次缓存就是把每批请求的入参和出参落盘jsonl 格式最简单一行一个 JSON 对象即使跑到一半断网重启后直接跳过已处理批次。我一般会在批处理脚本里加一个cache_path参数每处理完一批就json.dumps追加到文件里。这样有几个好处调试 Prompt 时不用重新花钱跑全量数据中途 API Key 欠费了充完钱续跑即可给老板汇报时也能拿出中间产物证明进度。下面这段代码展示了带缓存的分批处理骨架import json, time import pandas as pd from openai import OpenAI client OpenAI(api_keysk-xxx, base_urlhttps://api.deepseek.com) df pd.read_csv(reviews_sample.csv) batch_size 30 cache_file deepseek_cache.jsonl with open(cache_file, a, encodingutf-8) as f: for start in range(0, len(df), batch_size): batch df.iloc[start:start batch_size] texts batch[comment_text].tolist() try: resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: 你是餐饮评论分析助手。}, {role: user, content: json.dumps({comments: texts}, ensure_asciiFalse)} ], temperature0.0, max_tokens2000, ) result {batch_index: start, response: resp.choices[0].message.content} f.write(json.dumps(result, ensure_asciiFalse) \n) except Exception as e: print(fbatch {start} failed: {e}, retrying...) time.sleep(2) continue这段代码的逻辑很简单但值得注意三个细节。max_tokens2000是给输出留的余量批处理 30 条评论每条输出若上百 token这个值才够太小会直接截断导致 JSON 解析失败。异常处理里只做了continue真实项目要在这里写重试计数器失败超过 3 次的批次单独存到failed_batches.csv。最后千万条数据不需要一次性读进内存pandas 分块读pd.read_csv(..., chunksize5000)才是跑批的正确姿势单机内存不够时这就是后悔药。3. 评论数据清洗与预处理千万条变千条有效语料3.1 评论数据的真实形态导出文件里藏着哪些坑餐饮平台的评论导出常见格式有两种一种是从商家后台导出的 Excel 或 CSV另一种是通过开放接口拿到的 JSON。前者字段相对规整有rating、comment_time、comment_text后者则经常嵌套着user_info、shop_reply、extras等无关字段。这带来两个实际痛点无效评论比想象中多字段名不统一。无效评论的典型构成系统自动回复“此用户未填写评价内容”、纯表情或标点的评论、只包含“好吃”两个字的超短评论、刷单机器人复制粘贴的长篇大论。如果这些不清理后续传给 DeepSeek 的语料里全是噪音模型再强也分析不出真实顾客偏好。字段不统一则会影响脚本复用今天解析的是content明天导出的文件里叫description你的清洗代码就要跟着改。所以第一步不是急着写 DeepSeek 的 Prompt而是先做字段映射和过滤。3.2 清洗流水线去重、过滤、字段标准化我一般会在项目里建一个clean_reviews.py脚本把清洗拆成四个步骤字段标准化、去重、长度过滤、敏感信息剔除。字段标准化用字典映射比如{content: comment_text, score: rating}把不同来源的字段归到统一 schema。去重要小心同一个用户在不同平台都发了同一句话千万别只按文本去重否则会把无意义样本误伤稳妥做法是(user_id, comment_text)组合去重。敏感信息剔除是很多分析师容易忽略的一步。评论里可能带着“店员小张态度差”“经理姓李”这类实体把原始评论文本直接喂给 DeepSeek虽然只是内部调用但下游一旦生成报告对外发布就可能暴露出员工个人信息。清洗阶段先用正则把手机号、微信号、员工姓名替换成占位符比如把“13800138000”替换成[PHONE]。这一步做了后面不管是 API 调用还是本地部署数据泄露风险都会小很多。下面这段代码覆盖了上面说的核心逻辑import pandas as pd import re df pd.read_csv(raw_reviews.csv, dtypestr) field_map {content: comment_text, score: rating, ctime: comment_time} df.rename(columnsfield_map, inplaceTrue) df[comment_text] df[comment_text].fillna().astype(str) df df.drop_duplicates(subset[user_id, comment_text]) def clean_text(t: str) - str: t t.replace(\n, ).replace(\r, ) t re.sub(r1\d{10}, [PHONE], t) # 手机号脱敏 t re.sub(r\\w{2,8}(?:经理|店长|店员|服务员), [STAFF], t) # 模糊员工称呼 return t.strip() df[comment_text] df[comment_text].map(clean_text) # 过滤掉太短或太长的评论 df df[(df[comment_text].str.len() 4) (df[comment_text].str.len() 500)] # 去掉“此用户未填写评价内容”这类占位评论文案 placeholder_patterns [此用户未填写评价内容, 默认好评, 此用户没有填写评论] df df[~df[comment_text].isin(placeholder_patterns)] df.to_csv(reviews_cleaned.csv, indexFalse) print(fclean done: {len(df)} rows left, original {len(raw_df)} rows)这段清洗脚本有四个参数值得根据实际数据调整str.len() 4是为了去掉“好吃”“不错”这类无效短评 500是为了防止有人把评论当记事本写长篇作文超长文本既浪费 token 又会模糊分析焦点占位评论文案的列表按平台导出的实际文案补充正则里1\d{10}匹配中国大陆手机号如果你处理的平台评论里还有座机号再加一个0\d{2,3}-\d{7,8}的规则。注意脱敏只覆盖了常见格式真实的私人信息还可能藏在“微信同号”这类话术里保守做法是把整条含微信号字样评论标记出来人工审核。清洗完的reviews_cleaned.csv才是后面送进 DeepSeek 的合法语料而不是原始导出文件。3.3 文本预处理把口语评论修剪成模型友好的输入不少做过传统 NLP 的人拿到文本第一反应是去停用词、做分词。但在 DeepSeek 这类大模型场景下我劝你克制住“切词冲动”。中文分词对直接调用大模型的场景意义不大模型本身能理解整句上下文强行去掉“的”“了”“吧”反而会破坏语气——“太咸了”和“太咸”在模型眼里的情绪强度是不同的。真正需要做的预处理只有三件统一数字单位、修正乱码、加上上下文标记。统一数字单位解决的是“等了一个小时”“等了 60 分钟”这类等价表达Prompt 里让模型统一输出为分钟数字即可但文本输入侧我建议也做一层转换把常见中文数字“一、二、三”替换成阿拉伯数字减少模型不必要的推断。修正乱码针对的是平台导出时的编码问题常见的是 UTF-8 转 GBK 后出现的éÂ这类字符用iconv或 Python 的bytes(errorsignore)处理。加上下文标记则是在每条评论前拼上菜品名称和门店信息比如[门店:望京店][菜品:酸菜鱼] 评论内容……。因为很多评论压根不会提菜名只说“这个太辣了”你需要在预处理阶段就把“这个”指向哪道菜模型才能给出有效分析。4. 用 DeepSeek 做评论分析从原始语料到菜单改版建议4.1 设计高命中率的分析 Prompt抽取情绪、问题、菜品关键词Prompt 设计是整个项目里最像“玄学”但实际最有章法的环节。我建议把它当作数据清洗的一部分来写你希望模型输出什么字段就必须在系统提示里限定清楚。下面的 Prompt 模板我用在好几个餐饮分析项目里核心是让 DeepSeek 输出统一的 JSON 结构每个字段都有明确的可选值范围避免模型自由发挥。你是连锁餐饮品牌的数据分析师。我会上传顾客评论你需要解析每条评论并输出 JSON 数组。 每条评论输出五个字段 1. dish评论中明确提到的菜品名未提到则为空字符串 2. dimension评论涉及的角度可选值为 口味/分量/价格/服务/环境/出餐速度/其他 3. sentiment整体情绪可选值为 正/负/中只选一个 4. complaint顾客抱怨的具体问题用一句话概括没有则写无 5. suggestion建议改进方向10 字以内没有则写无 要求 - 菜品名使用评论里的原词不要自行替换成菜单标准名 - 一个句子里既夸又骂时complaint 和 sentiment 分别记录负向内容 - 输出必须是 JSON 数组不要输出其他解释文字这个 Prompt 的几个细节值得琢磨。dish字段要求原词是为了后续聚合时能发现“番茄鱼”和“番茄鱼片”实际是同一道菜这个归并在聚类阶段做模型阶段不去重保留原始颗粒度。dimension限制六个枚举值是为了让后续透视表能直接按维度分组否则模型能生成五百种维度名你就没法做统计了。suggestion限制 10 字以内是为了控制输出 token 成本要知道千万条评论每条多输出 20 个字总成本就多出几十万 token这些钱可以省下来做更多测试。以我的经验一次给模型 20 条评论效果最好太少则每条输出浪费在固定的 prompt 开销上太多则模型容易漏掉后面的评论这种漏检在语义上是隐蔽的比显式报错更难排查。4.2 批量推断与结果校验JSON 解析、字段统计、失败回捞模型输出 JSON 之后真正的数据处理工作才开始。先看一个最容易翻车的点DeepSeek 用response_format指定 JSON 时一般能稳定输出但偶尔会在 JSON 前后加 json 标记或额外注释。所以解析时不要直接json.loads(整段输出)先用正则把最外层 JSON 片段抠出来再解析。代码如下import json, re import pandas as pd def extract_json(text: str): # 去掉可能的代码块标记 text re.sub(rjson|, , text).strip() # 找到第一个 “{” 和最后一个 “}” 之间的内容 start, end text.find({), text.rfind(}) if start -1 or end -1: return None return json.loads(text[start:end 1]) results [] for line in open(deepseek_cache.jsonl, encodingutf-8): rec json.loads(line) parsed extract_json(rec[response]) if parsed is None: # 记录失败批次后面的步骤单独捞回来 print(fparse failed at batch {rec[batch_index]}) continue results.append(parsed) # 合并成 DataFrame rows [] for batch in results: # 兼容输出为对象 / 输出为数组两种情况 items batch if isinstance(batch, list) else [batch] for item in items: if item.get(dish) or item.get(complaint, 无) ! 无: rows.append(item) df_result pd.DataFrame(rows) df_result[sentiment] df_result[sentiment].map({正: 1, 中: 0, 负: -1})这段代码做完之后你要验证结果质量不能直接拿去给老板看。最常用的验证方式是抽检随机拿 100 条人工标注和模型输出对比一致率。如果 sentiment 一致率低于 90%说明你的 Prompt 里对“正/中/负”的定义不够清晰常见问题是“还可以”“一般般”被模型判成“中”人眼里其实是“负”解决方法是往 Prompt 里加示例。另一个验证点是dish字段的空值率如果超过 30% 的评论没有提到任何菜名说明你的预处理器拼接“菜品上下文”那一步没做对需要回到清洗阶段补全门店和菜品信息。这一层校验在千万条数据的项目里必须有否则分析结果越精确地出错改菜单的决策就越危险这是花钱买不回来的血泪经验。4.3 从结构化结果到菜单决策维度透视、菜品排名与问题聚类拿到带dish、dimension、sentiment、complaint字段的表格后菜单优化的分析就能像做常规商业数据分析一样操作了。最先做的是三维透视菜品 X 维度 X 情绪。看哪些菜品的负面评论集中在“分量”哪些集中在“口味”这直接决定了改菜单时是调整规格还是调整配方。pivot pd.crosstab( index[df_result[dish], df_result[dimension]], columnsdf_result[sentiment], valuesdf_result[sentiment], aggfunccount, fill_value0 ) pivot[total] pivot[1] pivot[0] pivot[-1] pivot[neg_ratio] pivot[-1] / pivot[total] top_risks pivot[pivot[total] 20].sort_values(neg_ratio, ascendingFalse) print(top_risks.head(20))这里的筛选条件total 20是防噪的关键阈值。一道菜只有 3 条评论、2 条差评neg_ratio高达 66%但它只是偶然性数据不能作为改菜单的依据。几十万条评论里能筛出几百个有效菜品组合每个组合背后都有足够的样本量这种统计才有决策价值。跑完这段代码你会得到一张风险菜品表格但这只是第一步。真正的菜单优化还要看评论里反复出现的suggestion词组用简单的方法把 complaint 和 suggestion 做词频聚合就能发现“太辣”“量少”“等太久”这类共性问题再把这些共性问题对应到具体菜品上菜单改版的优先级就排出来了。比如“酸菜鱼-分量-负”出现 300 次建议字段里高频出现“加量”“配菜”那这道菜的止损方案就不是下架而是改规格或者加配菜这就是评论数据带来的确定性。5. DeepSeek 评论分析避坑指南五个高频踩坑点与修复方案5.1 评论导出乱码和缺失字段清洗前先做数据体检现象CSV 用 pandas 读出来全是星星符号和NaN或者comment_text字段大片缺失。原因餐饮平台导出文件的编码不一定是你认为的 UTF-8常见的是 GB18030还有带 BOM 的 UTF-8。缺失字段则是因为导出时没选全字段或者接口分页漏了。解决读取时明确指定编码pd.read_csv(file, encodinggb18030)还是不行就用encodingutf-8, errorsignore试但这样会丢数据最好先摸清文件真实编码。字段缺失没有代码层面的后悔药只能回到导出端重新拉取这正是为什么我建议清洗第一步先跑一个df.info()体检不要急着做任何处理先看每个字段的non-null count。如果 comment_text 缺失超过 5%这个数据源就要打问号用了也是白用。5.2 API 输出偶尔不听话JSON 解析失败与字段漂移现象json.loads报错或者模型返回里sentiment写成“负面”而不是“负”导致后续映射失效。原因大模型受 prompt 里枚举值写法的影响差一个字它就可能换个说法。response_format能约束必须输出 JSON但约束不了 JSON 内部字段的取值用词。解决解析时先做一个取值归一化函数把“负面”“负向”-1 都映射成 -1把“正向”“正面”映射成 1。同时不要把全部希望押在模型不犯错上跑完批量后做一遍df_result[sentiment].value_counts()如果出现了非 {-1, 0, 1} 之外的取值说明 Prompt 枚举定义不够强回去加一条“只能从可选值中选一个不要改写”的约束。这个步骤看着琐碎但在百万级数据上0.1% 的漂移率就是 1000 条脏数据落在具体菜品上可能就是一道菜的误判。5.3 token 成本失控千万条评论全部喂给模型是最大的坑现象全量 1000 万条评论跑完账单出来发现花了 3 万块老板问你分析出了什么你说“还在跑”项目直接终止。原因天真地把每条评论都送进 DeepSeek。实际上餐饮评论的分布极度不均衡60% 的评论集中在 20% 的菜品上剩下 40% 的评论分布在长尾菜品。对长尾菜品你不需要逐条分析抽样就行。解决先对清洗后的数据做菜品关键词粗匹配把评论映射到菜品维度统计每道菜的评论量。对于评论量超过 100 条的菜品每条都分析对于 20 到 100 条的按 50% 采样少于 20 条的直接合并成“其他菜品”类别不单独分析。这个分层采样策略能把成本压缩到原来的三分之一以下而且统计精度几乎不受影响。精准的预算控制是先拿 5000 条做小批量测试看单条平均消耗多少 token再乘上总采样量得出总成本超预算就调采样率。这比跑完再后悔靠谱得多。5.4 评论分析不是一次性项目缓存和结果库必须做好现象分析报告做了三个月突然老板说“重新跑一遍上个月的”你发现还要重新花钱调 API。原因没有把模型输出结果持久化成业务数据资产。DeepSeek 跑出来的dish、dimension、sentiment字段其实已经是结构化数据应该入库存起来。解决把解析后的 DataFrame 追加写入 SQLite 或 ClickHouse表结构包含review_id、batch_id、dish、dimension、sentiment、complaint、suggestion、created_at。以后再做月度对比分析只需要从库里取历史结果不用再碰原始评论。还要留意 DeepSeek 模型的版本更新新版本可能改变输出风格体现在结果库里的就是同一批评论在不同时间的分析结果不一致所以结果库里记得存model_version字段对比差异时可回溯。这不是技术洁癖而是分析数据可复现性的基本要求。5.5 把分析结果转换成菜单决策时的期望值落差现象分析报告显示“宫保鸡丁”负面情绪高但下架后营业额反而下降了。原因评论数据分析的是“已经来吃饭的人”的反馈不是“为什么其他人不来”的原因。一道菜可能是引流菜差评多但带客多只看负面率就下架属于把分析结果过度外推。解决菜单决策一定要叠加交易数据。评论分析给出方向和优先级具体决策必须结合菜品销量、毛利率、出餐时间。正确姿势是把neg_ratio高的菜品拉一个交叉表这道菜的销售占比、退菜率、原材料成本占比。只有销量低、毛利好、差评多三者同时满足才进入下架候选如果销量高但差评集中在分量优先改规格而不是下架。这个坑的本质是数据分析告诉你“发生了什么”不会直接告诉你“该怎么办”中间的判断还是要结合业务。能意识到这一点你就不会把一份评论分析报告当成菜单改版的万能答案。6. 菜单健康度周报让 DeepSeek 分析从一次性项目变成日常机制分析项目跑完报告交上去事情通常就结束了。但餐饮菜单是动态的时令菜换季、厨师流动、供应链波动都会让评论结构跟着变。所以最后一公里不是出报告而是把整套流程固化成每周能自动跑一遍的例行机制。我建议做一张“菜单健康度周报”每周三上午从平台导出上周评论跑清洗、跑 DeepSeek、出透视表然后自动生成一条摘要发给运营群。周报的核心变量是neg_ratio的变化率。上周“剁椒鱼头”负面率 25%这周变成 35%不需要等月报当周就必须处理。实操上可以写一个定时脚本先读结果库里的历史基线再对比本周新增评论的分析结果只输出变化超过阈值的菜品# weekly_report.py 核心逻辑 weekly df_result[df_result[created_at] 2025-xx-xx] pivot_week pd.crosstab(weekly[dish], weekly[sentiment], aggfunccount) pivot_week[neg_ratio] pivot_week[-1] / pivot_week.sum(axis1) baseline result_db.query(SELECT * FROM analysis WHERE created_at 2025-xx-xx) pivot_base pd.crosstab(baseline[dish], baseline[sentiment], aggfunccount) pivot_base[neg_ratio] pivot_base[-1] / pivot_base.sum(axis1) merged pivot_week.join(pivot_base[neg_ratio], rsuffix_base, howinner) merged[delta] merged[neg_ratio] - merged[neg_ratio_base] alerts merged[merged[delta] 0.1].sort_values(delta, ascendingFalse)这段脚本的核心是delta 0.1意味着负面率一周内上升 10 个百分点才报警防止正常的随机波动触发无效提醒。阈值可以在不同门店间差异化设置新店因为评论量少阈值调高到 0.15。周报机制最大的价值是让菜单决策有了节奏感不是等顾客投诉到店长那里才改而是每周站在数据上看变化这样换菜、调价、改份量都有依据不再靠感觉。我也吃过亏早期把阈值定成 0.05结果每周都有三四个菜被标记为“需关注”运营群天天吵最后大家都不看了。后来调成 0.1 并附上样本评论原文讨论就回到了具体问题上。现在我做菜单决策习惯是先看周报里的负面率变化再翻对应菜品的评论原文最后和厨师对一遍操作流程。数据分析给的是靶子人做的才是决策这两者结合才是这套方案能长期跑下去的关键。希望这套方法能帮你也少走这些弯路把菜单优化从猜测变成一件有条理、可迭代、经得起回溯的日常动作。本文还有配套的精品资源点击获取
返回列表