批量生图跑到一半挂了怎么办?给 nano-banana2 的生图API 任务加断点续跑
批量出图最怕的场景:一千张跑到第七百张,进程挂了。重跑一遍不仅浪费两个小时, 前面七百张的积分也白花了。
这篇讲在该服务上给批量生图任务加断点续跑,挂了能接着跑。
一、核心:任务状态要落盘,不能只在内存
内存里的results数组进程一死就没了。每完成一条就写一次:
const fs = require('fs'); // 用 JSONL:一行一条,追加写,进程崩了也不会毁掉整个文件 // (JSON 数组写到一半崩了,文件就是坏的,解析不出来) function appendDone(file, rec) { fs.appendFileSync(file, JSON.stringify(rec) + '\n'); } function loadDone(file) { if (!fs.existsSync(file)) return new Set(); return new Set( fs.readFileSync(file, 'utf8').split('\n').filter(Boolean) .map((l) => { try { return JSON.parse(l).key; } catch { return null; } }) .filter(Boolean) ); }用 JSONL 不用 JSON 数组——追加写是原子的,崩在半路最多损失最后一行; 而 JSON 数组写到一半崩了,整个文件都解析不出来。
二、每条任务要有稳定的 key
续跑靠 key 判断"这条做过没有"。key 必须由输入决定,不能用随机数或时间戳:
const crypto = require('crypto'); const keyOf = (item) => crypto.createHash('md5') .update(item.sku + '|' + item.prompt + '|' + item.model).digest('hex').slice(0, 12);把model也放进 key:换了模型就该重跑,不然续跑时会把旧模型的结果当成已完成。
三、完整的续跑实现
async function runBatch(items, { workers = 16, doneFile = 'done.jsonl' } = {}) { const done = loadDone(doneFile); const todo = items.filter((it) => !done.has(keyOf(it))); console.log(`总共 ${items.length} 条,已完成 ${done.size},本次要跑 ${todo.length}`); if (!todo.length) return; let next = 0, ok = 0, fail = 0; await Promise.all(Array.from({ length: workers }, async () => { while (next < todo.length) { const it = todo[next++]; const key = keyOf(it); try { const r = await fetch('https://API_HOST/v1/chat/completions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TTQ_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: it.model, messages: [{ role: 'user', content: it.prompt }] }), signal: AbortSignal.timeout(120_000), }); if (!r.ok) throw new Error('HTTP ' + r.status); const data = await r.json(); appendDone(doneFile, { key, sku: it.sku, model: it.model, data, at: Date.now() }); ok++; } catch (e) { // 失败的不写 done 文件 —— 下次续跑会自动重试它 fail++; console.warn('失败', it.sku, e.message); } if ((ok + fail) % 50 === 0) console.log(`进度 ${ok + fail}/${todo.length} 成功 ${ok} 失败 ${fail}`); } })); console.log(`本轮完成:成功 ${ok},失败 ${fail}`); }失败的不写 done 文件,这样下次直接跑就是在重试失败的那些,不用另写重试逻辑。 反复跑几次直到本次要跑 0为止。
四、几个容易忽略的点
进度要打印。跑两个小时一行日志没有,没人知道是在跑还是卡死了。每 50 条打一次刚好。
别把图片二进制写进 done 文件。只存 URL 或落地后的路径, 否则 JSONL 文件几个 G,加载done集合就要等半天。
中断要能优雅退出:
let stopping = false; process.on('SIGINT', () => { stopping = true; console.log('收到中断,跑完手上的就停...'); }); // 循环里:if (stopping) break;直接 Ctrl+C 强杀,正在飞的请求积分已经扣了但结果没落盘,白花钱。
五、模型与消耗
| 模型代码 | 定位 | 消耗 |
|---|---|---|
nano-banana2 | 跑量首选,最快最便宜 | 8 积分/次 |
nano-banana-pro | 终稿,支持 1K/2K/4K | 10 积分/次 |
gpt-image-2 | 细节质感 | 按次计费 |
Veo 3.1 视频生成/Sora 2 | 生视频 | 120 / 130 积分/次 |
跑量务必用nano-banana2——同样一千张,比 Pro 级省两千积分,挑中的再用 Pro 重出。
六、小结
断点续跑四条:状态落盘用 JSONL 不用 JSON 数组、key 由输入内容决定、失败的不记账下次自动重试、中断要优雅退出别浪费在途请求。
该服务并发高,跑量任务才有意义;模型全(nano banana pro、gpt-image-2、Veo 3.1、Sora 2)、价格便宜、出图快、稳定。
接口服务:甜甜圈API(dashengfenshen.cn)
