1. 电商出图这件事为什么值得用工程化思路重做一遍做电商视觉的朋友大概率都经历过这样的场景大促前两周运营甩过来一张Excel表里面躺着三百多个SKU每个SKU要五张场景图、三张白底图、两张细节图截止时间是后天早上。你打开设计软件调好一个模板换产品、换背景、换文案重复几百次。做到第一百张的时候手已经不是自己的了眼睛也花了还容易出错——漏改价格、用错素材、导出命名混乱最后运营找图找半天。这两年AI绘图能力突飞猛进很多人第一反应是那我用AI生成不就行了。但真正上手之后会发现单张生成和批量生产之间隔着一道巨大的鸿沟。单张生成你只需要写一段提示词点一下按钮挑一张能看的就行。批量生产要考虑的问题完全不一样提示词怎么保证风格统一、几百个任务怎么排队不堵死、生成出来的文件怎么命名才能让运营一眼找到、中途失败了怎么重试而不会重复生成、成本怎么控制。我最近完整落地了一个商品图批量生成的流程核心思路就是三个词提示词模板化、队列调度、幂等命名。这套东西不是什么高深技术但组合起来之后原本需要两三天的人工出图工作压缩到了两个小时以内而且质量稳定、可追溯、可复现。下面我把整个实践过程拆开讲包括设计思路、关键细节、实操步骤和踩过的坑希望能给同样在做电商视觉自动化的朋友一些参考。这套方案适合谁如果你是个体电商卖家SKU不多手动生成也能应付那可能不需要这么重。但如果你是运营着一个中等规模店铺、或者在公司里负责视觉中台、又或者你是一个想接批量出图需求的设计工作室那这套工程化思路能帮你省下大量重复劳动。技术门槛不算高会写一点Python、能看懂JSON配置就行。2. 整体设计思路把生成图片当成一条流水线来设计2.1 为什么不能直接写个循环调用接口很多人第一版方案是这样的读一个商品列表for循环里拼提示词调一次生成接口下载图片改个名字下一个。这个方案跑十个商品没问题跑一百个就开始出各种幺蛾子。我最早也是这么干的结果遇到了几个典型问题。第一个问题是接口限流。大部分图像生成服务都有QPS限制你循环调得太快后面的请求直接被拒程序报错中断前面生成的图也不知道跑到哪里去了。第二个问题是风格漂移。每个商品手动拼提示词稍微改几个字生成出来的光影、色调、构图就可能不一致放在店铺里一看就不像一套东西。第三个问题是文件混乱。生成出来的文件默认是一串哈希值你根本分不清哪张图对应哪个商品运营拿到之后还得一张张打开看。第四个问题是重复生成。程序跑到一半崩了重启之后从头开始前面已经生成过的又跑了一遍浪费时间和额度。这些问题的根源在于我把批量生成当成了一个简单的循环而没有把它当成一条流水线来设计。流水线的核心特征是每个环节职责单一、环节之间有缓冲、整体可中断可恢复、产出物有唯一标识。想清楚这一点之后整个方案的结构就清晰了。2.2 三层架构模板层、调度层、存储层我把整个流程拆成了三层每一层解决一个核心问题。模板层解决的是生成什么的问题。核心思路是把提示词拆成固定部分和可变部分。固定部分是风格描述、画质要求、光影设定这些所有商品都一样的词可变部分是商品名称、材质、颜色、场景这些每个商品不同的信息。固定部分写死在模板里可变部分从商品数据里读取填充。这样一来所有图片的风格基调就是统一的不会因为手抖改了几个字就漂移。调度层解决的是怎么排队的问题。所有生成任务先进入一个队列然后由有限数量的工作线程从队列里取任务执行。这样做的好处是你可以控制并发数量不会因为一次性发太多请求被限流同时队列本身可以持久化程序崩了重启之后没做完的任务还在队列里接着做就行。存储层解决的是产出物怎么管的问题。每张生成的图片都有一个基于商品ID和图片类型计算出来的唯一文件名这个文件名是幂等的——同样的商品、同样的类型算出来的名字永远一样。这样无论你跑多少次同一个商品同一类型的图只会有一张不会重复。同时文件名本身就携带了信息运营一看文件名就知道是哪张图。这三层配合起来整个流程就变成了读商品数据 → 填充模板生成提示词 → 任务入队 → 工作线程取任务 → 调接口生成 → 按幂等规则命名保存 → 标记任务完成。任何一个环节出问题都不会影响整体可以单独排查、单独重试。2.3 为什么选择模板化而不是让AI自由发挥这里可能有人会问AI绘图不是越自由越好吗为什么要用模板限制它这个问题我一开始也纠结过。实测下来对于商品图这种需要风格统一的场景模板化带来的收益远大于自由发挥。原因很简单商品图的核心诉求是突出商品而不是展示AI的创意。你店铺里三十个商品如果每个都让AI自由发挥生成出来的背景、光影、色调五花八门消费者浏览的时候会觉得这个店铺很不专业。而模板化之后所有商品图共享同一套视觉语言看起来就像同一个摄影师在同一间影棚里拍的专业感立刻就上来了。当然模板化不等于死板。我的做法是在模板里留出几个可变槽位比如场景槽位可以填木质桌面大理石台面纯色背景光影槽位可以填柔和自然光硬朗侧光暖色氛围光。这样既保证了整体风格统一又能在一定范围内做出变化避免所有图看起来一模一样。3. 提示词模板化的核心细节与实操要点3.1 模板结构怎么设计才合理一个实用的提示词模板我建议拆成四个区块主体描述区、场景描述区、风格描述区、技术参数区。这四个区块各司其职拼起来就是一条完整的提示词。主体描述区放商品本身的信息比如一款白色陶瓷马克杯杯身有简约的竖条纹纹理杯口圆润。这部分的数据来源是商品数据库每个商品不一样。场景描述区放商品所处的环境比如放置在浅灰色水泥台面上旁边有一小枝干花作为点缀。这部分可以按商品类目预设几套方案也可以从商品标签里读取。风格描述区放整体的视觉调性比如极简风格柔和自然光从左侧打来背景虚化整体色调偏冷。这部分基本是固定的所有商品共用。技术参数区放画质相关的要求比如高清细节丰富商业摄影质感比例1:1。我实际用的模板大概长这样用占位符标记可变部分{product_description}{scene_description}{style_description}{tech_params}然后在代码里用商品数据去替换这些占位符。这里有个细节要注意占位符的替换顺序会影响最终效果。我建议把主体描述放在最前面因为大部分图像生成模型对提示词开头的权重更高把最重要的商品信息放前面能保证商品本身被准确呈现。3.2 可变槽位的取值策略模板里的可变槽位怎么取值直接决定了生成结果的多样性。我的做法是给每个槽位准备一个候选池然后根据商品属性去匹配。举个例子场景槽位的候选池里可能有木质桌面大理石台面纯色背景亚麻布面玻璃台面这几个选项。怎么决定某个商品用哪个场景我的策略是按商品类目和价格带来分。比如家居类目、中高价格带的商品优先用木质桌面或大理石台面显得有质感快消品类目、低价格带的商品优先用纯色背景突出商品本身。这个匹配逻辑可以写成一个简单的映射表放在配置文件里改起来不用动代码商品类目价格带推荐场景推荐光影家居高大理石台面柔和自然光家居中木质桌面暖色氛围光快消任意纯色背景硬朗侧光服饰高亚麻布面柔和自然光数码任意玻璃台面冷色侧光这张表是我根据自己店铺的实际情况调的不一定适合所有人但思路可以参考。核心原则就是让场景和光影服务于商品调性而不是随机分配。3.3 模板版本管理这件事别偷懒模板是要迭代的。你今天觉得这个风格好明天可能就觉得该换换了。如果不做版本管理改完之后想回滚都回不去之前生成的图和新图风格对不上整个店铺看起来就乱了。我的做法是给每个模板一个版本号存在文件名或者数据库字段里。每次修改模板版本号加一旧版本保留不删。生成图片的时候文件名里带上模板版本号。这样你随时能知道某张图是用哪个版本的模板生成的想复现或者对比都很方便。具体操作上我把模板存成独立的JSON文件文件名就是版本号比如template_v1.json、template_v2.json。程序启动的时候读取最新版本但生成记录里会写明用的是哪个版本。这个习惯看起来麻烦但真到了需要排查问题的时候能省下大量时间。提示模板文件建议纳入版本控制工具管理每次修改写清楚改了什么、为什么改。我吃过亏有一次改了个光影描述结果发现新图整体偏暗想找回原来的描述已经记不清了只能凭感觉重写浪费了不少时间。4. 队列调度的实现细节与避坑经验4.1 为什么用队列而不是线程池直接跑线程池和队列看起来都能控制并发但实际用起来差别很大。线程池的问题是任务提交进去之后你很难知道整体进度也不好做失败重试。而且线程池的任务是存在内存里的程序一崩全没了。队列的好处是任务和消费解耦。任务先写进队列队列可以是内存里的也可以是持久化的。工作线程从队列里取任务执行执行完标记完成。程序崩了队列里的任务还在重启接着跑。进度也一目了然队列里还剩多少任务、已完成多少、失败多少随时能查。我用的是最简单的内存队列加持久化记录的方式。任务本身存在一个JSON文件里每条任务有状态字段pending、running、done、failed。程序启动的时候把所有pending和failed的任务重新加载进内存队列。工作线程从队列取任务取到之后把状态改成running执行完改成done出错了改成failed并记录错误信息。这样即使程序中途挂了重启之后也能从断点继续。4.2 并发数量怎么定才不触发限流并发数量这个参数设大了会被限流设小了效率上不去。我的经验是先看服务商文档里写的QPS限制是多少然后并发数设成QPS的60%到70%。比如文档说每秒最多10次请求那并发数设6到7比较稳妥。留出余量是因为实际网络延迟、接口响应时间都会有波动卡得太死容易触发限流。另外我建议加一个自适应降速机制。具体做法是记录最近N次请求的响应状态如果连续出现限流错误就自动把并发数降下来等一段时间再慢慢升回去。这个逻辑不复杂但能有效避免因为突发限流导致大批任务失败。还有一个细节是请求间隔。即使并发数设得不高如果所有线程同时发请求瞬间的请求量还是可能超限。我的做法是在每个请求之间加一个随机的小延迟比如0.1到0.3秒之间随机。这样请求就被打散了不会出现瞬间峰值。4.3 失败重试的策略与边界失败重试是必须的但不能无脑重试。我的策略是分错误类型处理网络超时、限流这类临时性错误重试三次每次间隔翻倍参数错误、内容审核不通过这类永久性错误不重试直接标记失败并记录原因。重试次数和间隔我写在配置里方便调整。重试的时候要注意任务状态要先改回pending再重新入队不能直接在线程里循环重试否则一个卡住的任务会占着线程不放影响其他任务。还有一个容易忽略的点是重试的幂等性。如果一个任务第一次执行其实已经成功了只是返回结果的时候网络断了程序以为失败了又重试一次就会生成两张一样的图。这就是为什么幂等命名很重要——即使重试了第二次生成的文件名和第一次一样直接覆盖就行不会产生重复文件。注意重试间隔不要设得太短否则容易在服务商那边形成重试风暴反而加重限流。我一般设成第一次5秒、第二次15秒、第三次45秒给服务端足够的恢复时间。5. 幂等命名的设计逻辑与落地方法5.1 幂等命名到底解决什么问题幂等这个词听起来有点技术味其实道理很简单同样的输入永远得到同样的输出标识。放到商品图生成这个场景里就是同一个商品、同一种图片类型无论生成多少次文件名永远一样。这样做的好处有三个。第一避免重复文件。程序重试或者重跑的时候不会产生一堆内容相同但名字不同的文件存储空间不浪费运营找图也不会困惑。第二方便断点续传。程序启动的时候扫一遍输出目录已经存在的文件名对应的任务就可以跳过不用重新生成。第三可追溯。文件名本身就携带了商品ID、图片类型、模板版本等信息出了问题一看文件名就知道是哪来的。5.2 文件名怎么拼才既唯一又可读我的命名规则是这样的{商品ID}_{图片类型}_{模板版本}_{序号}.png。举个例子SKU1024_main_v2_01.png表示商品SKU1024的主图用第二版模板生成的第一张。这里有几个设计考量。商品ID放最前面方便按商品排序和查找。图片类型用简短的英文标识比如main表示主图、scene表示场景图、detail表示细节图。模板版本用v加数字方便区分不同版本生成的图。序号是因为同一个商品同一类型可能要生成多张备选用01、02来区分。这个命名规则的关键在于确定性。商品ID来自数据库图片类型来自任务定义模板版本来自配置序号来自任务里的计数。这四个要素都是确定的所以拼出来的文件名也是确定的。同样的任务跑一百遍文件名都一样。5.3 用文件存在性做去重的实操有了幂等命名之后去重就变得非常简单生成之前先检查目标文件是否存在存在就跳过。这个检查放在任务执行的最前面开销极小但能省下大量重复生成的时间和额度。具体代码逻辑大概是这样import os def generate_image(task): filename build_filename(task) filepath os.path.join(OUTPUT_DIR, filename) if os.path.exists(filepath): mark_task_done(task, reasonalready_exists) return prompt build_prompt(task) image_data call_generation_api(prompt) save_image(image_data, filepath) mark_task_done(task)这段逻辑里build_filename就是幂等命名的核心它根据任务信息算出唯一文件名。os.path.exists检查文件是否已存在存在就直接标记完成不调接口。这样就实现了跑多少次结果都一样的效果。有一个细节要注意如果生成过程中程序崩了文件可能只写了一半。这种情况下文件存在但内容不完整下次跑的时候会被误判为已完成。解决办法是先写临时文件写完再重命名。重命名是原子操作要么成功要么失败不会出现半成品。这个技巧在处理任何文件生成任务时都适用。6. 完整实操流程从零跑通一条批量生成流水线6.1 环境准备与依赖安装先把基础环境搭起来。我用的是Python 3.10主要依赖就几个请求库、图像处理库、以及一个用来管理配置的库。具体装什么取决于你用哪家图像生成服务但通用的这几个是跑不掉的。pip install requests pillow pyyaml tqdmrequests用来调接口pillow用来做图片的简单处理比如裁剪和格式转换pyyaml用来读配置文件tqdm用来显示进度条。这几个库都很成熟装起来不会有坑。目录结构我建议这样组织project/ ├── config/ │ ├── template_v1.json │ └── settings.yaml ├── data/ │ └── products.csv ├── output/ │ └── images/ ├── tasks/ │ └── task_queue.json └── main.pyconfig放模板和配置data放商品数据output放生成的图tasks放任务队列的持久化文件main.py是入口。这个结构清晰各个部分职责分明后面维护起来方便。6.2 商品数据准备与清洗商品数据一般从后台导出CSV或者Excel格式。导出之后不能直接用得先清洗。常见的问题有商品名称里有特殊字符、类目字段不统一、价格带缺失、有些商品没有主图所以不需要生成。我的清洗步骤是这样的先把商品名称里的特殊字符去掉只保留中文、英文和数字然后把类目字段做一次映射把后台的各种叫法统一成几个标准类目价格带根据实际售价算出来补上最后过滤掉不需要生成图的商品。清洗完的数据存成CSV字段包括商品ID、商品名称、类目、价格带、材质、颜色、卖点。这些字段后面填充模板的时候都会用到。6.3 任务生成与入队数据准备好之后就可以生成任务了。每个商品根据需要的图片类型生成对应的任务。比如一个商品需要一张主图、两张场景图、一张细节图那就生成四个任务。任务的数据结构大概是这样{ task_id: SKU1024_main_01, product_id: SKU1024, image_type: main, template_version: v2, sequence: 1, status: pending, retry_count: 0, created_at: 2024-01-15T10:30:00 }所有任务生成完之后写入task_queue.json同时加载进内存队列。这里要注意如果之前已经跑过一轮有些任务已经完成了重新生成任务的时候要检查一下已经完成的不要重复入队。检查的依据就是幂等文件名对应的文件是否存在。6.4 工作线程的启动与监控工作线程的数量根据前面说的并发策略来定。启动之后每个线程循环从队列取任务、执行、标记状态。主线程负责监控定期打印进度比如已完成120/300失败3剩余177。监控这块我建议加一个简单的日志每个任务开始和结束都记一条包含任务ID、耗时、结果。出问题的时候翻日志就能定位比瞎猜强得多。import threading import time from queue import Queue def worker(task_queue, results): while not task_queue.empty(): task task_queue.get() try: process_task(task) results[done] 1 except Exception as e: results[failed] 1 log_error(task, e) finally: task_queue.task_done() time.sleep(random.uniform(0.1, 0.3)) threads [] for i in range(CONCURRENCY): t threading.Thread(targetworker, args(task_queue, results)) t.start() threads.append(t) for t in threads: t.join()这段代码是核心骨架实际用的时候还要加上状态持久化、错误分类处理、进度打印等逻辑。6.5 结果校验与人工抽检程序跑完之后不能直接交付得做一轮校验。校验分两步自动校验和人工抽检。自动校验检查文件是否存在、文件大小是否正常、图片尺寸是否符合要求。这些用脚本批量跑一遍几秒钟的事。人工抽检就是随机抽十几张图肉眼看看质量。重点看几个方面商品主体是否清晰、有没有明显的变形或错误、风格是否统一、文案有没有乱码。抽检发现问题的话根据问题类型决定是调模板还是调参数。如果是风格不对改模板如果是商品主体不准改主体描述如果是画质问题改技术参数。改完之后把相关任务的状态改回pending重新跑一遍就行。因为幂等命名的存在已经生成的图不会被重复生成只有改过的任务会重新执行。7. 常见问题与排查技巧实录7.1 生成结果不稳定同一提示词两次结果差异大这是最常见的问题。图像生成本身有随机性同样的提示词两次生成结果不一样是正常的。但如果差异大到风格都变了那就要排查了。首先检查提示词里有没有模糊的描述。比如好看的背景这种词AI理解起来每次都不一样。把模糊描述换成具体的比如浅灰色水泥墙面背景稳定性会好很多。其次检查有没有设置随机种子。大部分生成服务支持指定种子固定种子之后同样的提示词生成结果基本一致。如果服务不支持种子那就只能通过细化提示词来提升稳定性。还有一个技巧是增加风格锚点。在提示词里加入具体的风格参考比如参考北欧极简家居摄影风格比单纯说极简风格要稳定得多。7.2 任务跑到一半卡住不动这种情况一般是某个任务卡住了占着线程不放。排查方法是看日志找到最后一个开始但没有结束的任务看它卡在哪一步。常见原因有接口请求没有设超时、图片下载卡住、文件写入被阻塞。解决办法是给所有网络请求加上超时设置比如连接超时10秒、读取超时60秒。超时之后抛异常任务标记失败线程释放出来继续处理下一个。另外文件写入操作也要注意如果输出目录在网络上写入可能会很慢建议先写到本地临时目录再移动到最终位置。7.3 生成到后面越来越慢这个问题的原因通常是内存泄漏或者连接池耗尽。如果每次请求都新建连接而不复用连接数会越来越多最后耗尽。解决办法是用会话对象复用连接requests库里的Session就是干这个的。另一个可能的原因是任务队列里积压了太多失败任务每次重试都在消耗资源。检查一下失败任务的数量如果失败率很高先停下来排查原因不要让它一直重试。7.4 常见问题速查表问题现象可能原因排查方法解决办法风格不统一提示词模糊、无种子对比不同任务的提示词细化描述、固定种子任务卡住请求无超时、文件阻塞看日志找卡住的任务加超时、改本地写入越来越慢连接未复用、失败积压看连接数、失败任务数用Session、暂停重试重复生成幂等命名未生效检查文件名规则修正命名逻辑图片质量差技术参数不足看提示词技术区补充画质描述限流频繁并发过高、无间隔看错误类型降并发、加随机延迟7.5 几个我踩过的坑第一个坑是模板里的标点符号。中文逗号和英文逗号在提示词里的效果可能不一样我一开始混用导致有些图风格怪异。后来统一用中文逗号问题就消失了。这个细节很小但影响不小。第二个坑是文件名的长度限制。有些系统对文件名长度有限制如果商品ID很长、类型名很长拼出来的文件名可能超限。解决办法是给商品ID做个短哈希或者限制各部分的长度。第三个坑是并发数设太高导致账号被临时限制。我有一次急着出图把并发数调到了文档限制的满值结果跑了不到一百个任务就被限了等了半小时才恢复。后来老老实实按70%设再没出过问题。这个教训就是别贪快稳定比速度重要。第四个坑是没有做任务去重。有一次我误操作把同一批任务入队了两次结果每个任务都跑了两遍。虽然幂等命名保证了文件不重复但白白浪费了一倍的生成额度。后来我在入队之前加了一个检查已经存在的任务ID不再入队。8. 成本控制与效率优化的几个实战技巧8.1 先小批量试跑再全量铺开模板改完之后不要直接全量跑。先挑十个不同类型的商品试跑看看效果。确认没问题了再全量。这一步能帮你省下大量额度因为模板问题在小批量阶段就能发现不用等到跑了几百张才发现风格不对。试跑的时候要覆盖各种类目和价格带不能只挑一种商品。因为不同类目的商品在同一个模板下表现可能不一样只试一种的话全量跑的时候其他类目可能出问题。8.2 用低分辨率预览再生成高清图如果生成服务支持指定分辨率可以先按低分辨率生成一批预览图人工筛选出满意的再对选中的任务生成高清图。这样能大幅降低无效生成的成本。预览图虽然画质低但构图、风格、主体是否准确这些关键信息都能看出来。这个策略特别适合SKU多、预算有限的场景。我实测下来预览阶段能筛掉大概三成的图这三成如果直接生成高清就是纯浪费。8.3 缓存复用与增量生成如果商品数据变化不大很多图其实不需要重新生成。我的做法是给每个任务算一个内容哈希哈希值基于提示词和模板版本。如果哈希值没变说明提示词没变直接复用已有的图不用重新生成。只有哈希值变了才重新跑。这个机制配合幂等命名效果很好。日常运营中大部分商品的信息是稳定的只有少数商品会改描述或者换模板。增量生成只跑变化的部分效率提升非常明显。8.4 生成额度的分配策略如果预算有限我建议把额度优先分配给主图和高价格带商品。主图是消费者第一眼看到的质量最重要高价格带商品的转化对图片质量更敏感值得多花额度。低价格带的商品或者细节图可以用低分辨率或者少生成几张备选。这个策略不是绝对的要根据自己店铺的实际情况调整。核心思路是把有限的资源花在影响转化的关键位置上。9. 后续可以继续扩展的方向这套流程跑通之后能扩展的地方还有很多。比如可以接一个简单的审核界面让运营在网页上直接看生成的图点通过或者打回打回的任务自动重新入队。这样整个流程就更闭环了不用在命令行和文件夹之间来回切换。再比如可以加一个A/B测试机制同一个商品用两套模板各生成一张投放到不同渠道看转化数据用数据来决定哪套模板更好。这个需要和投放系统打通但思路是通的。还有一个方向是自动质量检测。用图像识别模型自动检查生成的图有没有明显问题比如商品主体缺失、文字乱码、画面模糊。有问题的自动标记出来人工只需要看被标记的效率又能提升一截。我个人在实际操作中的体会是这套东西的价值不在于技术多先进而在于把零散的手工操作变成了可重复、可追溯、可优化的流程。一开始搭的时候可能要多花一两天但后面每次出图都能省下大量时间而且质量稳定不用每次都提心吊胆。如果你也在做类似的批量出图工作建议先把模板化和幂等命名这两件事做好这两块是基础做好了后面扩展什么都方便。
阅读完成 · 觉得有帮助?