图片生成成功却下载失败:保存 Base64、续查任务与避免重复提交
区分图片生成、异步查询和文件下载,按 task_id 恢复任务,保存 url 或 b64_json,并在发布前校验图片,避免下载失败后重复发起生成。

生图接口已经返回成功,图片却没保存下来;或者程序等到超时,不知道那张图到底有没有生成。这时先保留响应和任务记录,确认停在哪一步,再决定下一次请求。
生成、查询任务、下载图片是三个不同动作。 已有结果就继续保存;已有 task_id 就查询原任务。提交结果不明时,先查记录,不要把同一条提示词再发一遍当作恢复。
本文依据 2026 年 11 月 9 日查阅的官方资料和本站图片接口说明,给出恢复顺序与一个离线验证过的 Base64 保存示例。这里的异步任务路径属于 Nbility;OpenAI 官方接口的返回规则会单独标明。
先确定:生成没完成,还是文件没存好
| 当前拿到什么 | 说明什么 | 接下来做什么 |
|---|---|---|
| 只有提交超时,没有响应 | 不知道服务端是否已接受 | 保留时间与请求标识,查服务记录 |
task_id,状态仍在等待或运行 | 已创建任务,尚无最终结果 | GET 查询同一个任务 |
| 成功状态及图片 URL 或 Base64 | 已取得图片结果 | 下载或解码,校验后落盘 |
| 文件存在但无法打开 | 文件可能不完整、格式不对或根本不是图片 | 检查响应、文件类型和完整解码 |
“显示已过期”也要看发生在哪一层。Cherry Studio #18460有一条中文原始报告:Windows、2.0.3,通过微信 clawbot 发送的图片在安卓端无法查看。它没有提供可用于核对的日志,后续只有闲置机器人回复。这个报告说明需要检查消息传递和接收端,不能据此认定模型没生成图片,或某个图片 URL 一定过期了。
如果本来就没有生成结果,例如参数不支持或安全检查拒绝,先按图片生成报错排查处理。本文关注已有任务或结果以后怎样继续。
URL、Base64 与异步响应不要混着解析
先看实际响应的结构,再选择保存方式:
| 路径 | 需要读取的字段 | 保存方式 |
|---|---|---|
| 同步图片结果,返回链接 | data[i].url | 下载对应文件 |
| 同步图片结果,返回 Base64 | data[i].b64_json | 严格解码,再检查文件 |
| Nbility 异步提交 | data.task_id | 保存任务标识,继续查询 |
| Nbility 异步任务完成 | data.status、data.image_urls 或 data.result_url | 从已有结果继续下载 |
这里的 i 是每个结果的下标;多张图片应逐项处理,不能只保存第一项后就认定整批完成。
OpenAI 官方 Images API 参考说明,GPT Image 模型返回 Base64,不支持旧的 response_format 参数。直接调用 OpenAI 时按官方模型接口传参。 本站图片 API另有结果归一化:可按其说明选择 url 或 b64_json,不能把网关提供的返回方式套到官方入口。
近期 Open WebUI #33874就记录了一个开发分支问题:模型字段留空时,实际选中 GPT Image,构造请求的条件却仍使用原配置,带入了不适用的参数。PR #33875已于 11 月 8 日合并;当天核对的 v0.13.3 标签中,两处判断已改用实际 model。这是请求构造的修正,不能用来证明所有图片下载故障都已修复。
有 task_id,就续查原任务
通过本站 X-New-Api-Async-Task: true 创建的任务,用原来的认证配置查询。下面是 GET 查询,不会重新发起生成;把占位值替换为实际任务 ID,Key 从现有私密配置注入环境变量:
umask 077
export IMAGE_TASK_ID="task_YOUR_EXISTING_TASK_ID"
curl --fail-with-body --silent --show-error --dump-header task-headers.txt --output task-result.json -H "Authorization: Bearer $NBILITY_API_KEY" "https://api.nbility.ai/v1/images/tasks/$IMAGE_TASK_ID"
读取 task-result.json 的 data.status:
submitted、queued、in_progress:仍需等待。当前本站实现对未结束任务返回Retry-After: 3,按提示安排下一次查询,并给自己的等待循环设结束条件。succeeded:读取图片结果,进入保存步骤。这个状态不代表你的本地磁盘或对象存储已经有文件。failed:保存fail_reason并排查原因。再次发起生成是一个新操作,应明确决定。
状态文件、响应和结果链接保存在私密目录。记录至少包括任务 ID、提交时间、模型、请求参数摘要、当前状态,以及各张图片是否已保存。进程重启后先读这些记录,再恢复下一步。task_id 也不能无限期代替本地文件;本站任务与结果保留政策没有在本文核实,不承诺永久可查询或永久可下载。
如果提交超时而没有拿到 ID,保留请求时间、模型及已有请求标识,到用量日志查看可取得的记录,必要时联系支持核查。没有找到记录也不足以证明请求未被上游接受;本文没有核实可用于安全重放生成 POST 的幂等键,不建议只凭提示词相同就自动重提。
拿到 URL 后,只恢复下载
将自己已收到的完整结果 URL 存入私密环境变量 IMAGE_URL,在新目录下载到临时文件:
umask 077
mkdir -p image-recovery
curl --fail --silent --show-error --location --connect-timeout 15 --max-time 180 --output image-recovery/image.part "$IMAGE_URL"
这里没有附加模型 API Key。结果地址可能属于独立 CDN 或对象存储,不要把 API 的 Authorization 头复制过去;下载链接若带签名参数,完整链接本身也应私密保存。
出现问题时,按下载这一步处理:
- 超时、临时网络中断:保留原任务和结果,只对下载 GET 做有上限的重试;未完成的
.part不进入发布目录。 - 403/404:核对完整链接、有效期或资源状态,查看能否从原任务重新取得可用结果;不要假定每个 403 都是过期,也不要假定查询一定会刷新链接。
- 浏览器能打开,程序取不到:比较运行机器、重定向与网络路径。运行在服务器的任务不会自动获得你浏览器的登录状态。
- HTTP 200 但打不开:查看 Content-Type 和实际内容,可能下载到错误页。仅改成
.png后缀不能让它成为图片。
本文没有查到可统一套用的本站结果 URL 有效时长,因此不给“一小时内下载”这类固定承诺。需要长期使用的成品,及时复制到自己控制的存储,并单独确认其访问权限和保留时间。
Base64 保存示例:先验证,再写成品
下面的脚本只处理已保存的同步 Images 响应中的第一张 PNG、JPEG 或 WebP,不提交生成请求,也不解析异步任务响应。处理多张时应逐项扩展,并分别记录完成状态。GIF、SVG 等输出要用适合它们的校验方式,不在此例范围内。
在本地 Python 3 环境安装 Pillow:
python3 -m pip install Pillow
保存为 save-b64.py:
import base64
import io
import json
import os
import sys
import tempfile
from pathlib import Path
from PIL import Image
response = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
raw = base64.b64decode(response["data"][0]["b64_json"], validate=True)
with Image.open(io.BytesIO(raw)) as image:
suffix = {"PNG": ".png", "JPEG": ".jpg", "WEBP": ".webp"}[image.format]
image.verify()
with Image.open(io.BytesIO(raw)) as image:
image.load()
folder = Path(sys.argv[2])
folder.mkdir(parents=True, exist_ok=True)
destination = folder / ("image" + suffix)
with tempfile.NamedTemporaryFile(dir=folder, delete=False) as temporary:
temporary.write(raw)
temporary.flush()
os.fsync(temporary.fileno())
temporary_path = Path(temporary.name)
try:
os.link(temporary_path, destination)
finally:
temporary_path.unlink()
print(destination)
将已有响应存为私密的 result.json,执行:
python3 save-b64.py result.json image-recovery
脚本严格检查 Base64,再用 Pillow 验证并完整解码,按实际格式选择后缀。验证通过后写入同目录的私有临时文件,最后用硬链接发布成品名;目标已存在会失败,不覆盖旧文件。这要求该目录所在文件系统支持硬链接,适合普通本地磁盘,不是对象存储上传示例。
本文用临时生成的 PNG、JPEG、WebP,以及非法 Base64、损坏图片和已存在目标文件检查过这段脚本。没有为这些检查调用付费模型,也没有模拟出一笔“避免重复收费”的金额。
什么情况下才需要重新生成
先明确旧结果无法恢复,或新需求确实要一张新图,再决定是否重新提交。提示词相同不代表同一个任务,服务也不一定把重复请求当成一次。
发布前至少确认:文件能完整打开、格式与后缀一致、尺寸适合用途、上传后的文件可读,最后再更新文章或消息中的地址。需要保持原始输出时,校验后保存原字节,不要在“下载”过程中顺手转码、压缩或裁切。
用本站服务时,费用疑问按账户与计费说明核对实际请求和结算;客户端显示下载失败,不能单独决定是否应有费用。要回看一次任务,还可以按 T3 Code 用量与账单核对方法分清客户端记录和服务记录。开始接入的读者则先看图片 API 入门。

