[控场AI]
· 6 分钟阅读· 3,221 字

用一个文件终结重复上传:Adobe Stock批量发布追踪器

用一个文件终结重复上传:Adobe Stock批量发布追踪器

用零依赖追踪器和确定性元数据脚本,根除批量提交中的重复上传。

文章针对批量向 Adobe Stock 等素材平台提交作品时,因脚本重跑导致的重复上传问题,提出了一套轻量级修复方案。作者指出,重复上传的根源在于“生成元数据”和“提交”两个步骤非原子:CSV 表头错误引发渠道拒绝,修正后重跑整批,而脚本没有本地记忆,导致已提交文件被重复处理。解决方案是一个仅依赖 Python 标准库的单文件追踪器 upload_tracker.py,通过 pending/done/status 三个命令维护纯文本账本,决定每次实际提交哪些文件;配套的 make_metadata.py 按各平台精确表头生成确定性 CSV,从源头消除重跑冲动。作者还总结了单核服务器上的运维习惯:串行执行、用 gc.collect() 释放内存、测量峰值 RSS、逐文件子进程处理。这套思路可迁移到任何多平台批量分发场景。

批量向Adobe Stock等素材平台提交作品时,脚本重跑导致的重复上传是 Contributor 账户被标记的高危操作。一位开发者分享了他的轻量级修复方案:一个仅依赖 Python 标准库的单文件上传追踪器,以及配套的确定性元数据生成脚本,从根本上杜绝“无记忆重跑”。

Adobe Stock 重复上传追踪器方案示意

重复上传是怎么发生的

问题的根源在于提交流程中“生成元数据”和“提交”两个步骤并非原子操作。Adobe Stock 的 CSV 模板要求严格的五列表头,Vecteezy 需要四列,Dreamstime 则需要十五列。任何表头错误都会导致渠道立即拒绝文件,而人的本能反应是修正 CSV 后重跑整个批次。

脚本没有记忆,它会愉快地为已经提交过的文件重新生成条目,因为“已提交”这一事实只存在于 Contributor 后台,而不在本地磁盘上。部分失败会让情况更糟:如果一批 40 个文件在第 30 个中断,你既不想重传前 30 个,也没有账本告诉你停在哪里,只能全量重传或手动审计后台——而后者正是流水线本该消除的繁琐工作。这位开发者正是在某个周二深夜发现同一批矢量素材在审核队列里出现了两次,一周内 40 个文件被提交了两遍。

非原子操作是这里的关键概念。在数据库和分布式系统中,原子操作指一个不可分割、要么全部成功要么全部失败的操作序列。文章中的提交过程由“生成元数据”和“提交文件”两个步骤组成,两者之间没有事务性保证:CSV 生成可能成功,但提交可能部分失败或中断,而系统不会回滚已提交的部分。这种“部分完成”状态在重跑时无法被感知,于是脚本会为所有文件(包括已提交的)重新生成条目,导致重复上传。解决非原子问题的常见模式包括:引入幂等键(如文件哈希)让重复提交被平台忽略,或使用本地状态机记录每个文件的阶段(生成、提交中、已完成),确保重跑时跳过已完成阶段。文章中的追踪器本质上就是一个轻量级状态机。

追踪器只有一个文件,零依赖

解决方案小得令人尴尬。upload_tracker.py 是一个纯标准库的 Python 脚本,无需 pip install,用一份纯文本账本记录完成状态,回答三个问题:

python upload_tracker.py pending items.txt   # 还有哪些没做
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status

替换掉“周二灾难”的工作流是:任何提交运行之前,先从文件夹生成 items.txt,先运行 pending,只提交它显示的文件,每发出一个就标记 done。如果运行在第 30 个文件中断,重跑 pending 只会显示最后 10 个,且仅限这 10 个。重跑不再可能重复上传,因为决定发出什么的是账本,而不是记忆。

零依赖这一点值得强调。作者的整条流水线跑在一台 1 vCPU、2 GB 内存、无 GPU 的服务器上,工具集中只有两个脚本需要第三方包(Pillow 处理封面、reportlab 生成 PDF)。元数据构建器和追踪器只需要 Python 3.9+,这意味着它们在任何环境都能跑,包括这台小机器,不需要为虚拟环境争论。

元数据是同一个问题的另一半

追踪器只有在 CSV 一次就正确的前提下才有用。make_metadata.py 按各渠道要求的精确表头写出文件,不多不少:

python make_metadata.py ./my-assets --platform adobe      --out metadata.csv   --ext .jpg
python make_metadata.py ./my-assets --platform vecteezy   --out vecteezy.csv   --ext .jpg
python make_metadata.py ./my-assets --platform dreamstime --out dreamstime.csv --ext .jpg

如果提供 titles.json,脚本可以从中读取真实的标题和关键词;没有提供时,文件名会被转为标题格式作为标题,这在第一遍处理时够用,但提交前应当替换。CSV 阶段的拒绝正是触发重跑冲动的源头,因此修复表头问题和修复重复问题本质上是同一个修复:让流水线变得确定,你就永远不会产生“再跑一遍试试”的冲动。

确定性元数据生成指的是:给定相同的输入(文件列表、标题、关键词),脚本每次输出的 CSV 内容(包括行序、列序、转义格式)完全一致。这消除了“重跑导致 CSV 变动”的可能,从而避免因表头或格式差异触发平台拒绝。实现确定性输出的要点包括:固定文件遍历顺序(如按文件名排序)、固定日期时间格式(避免本地时区或当前时间影响),以及固定列顺序和分隔符。文章中的 make_metadata.py 通过 --platform 参数硬编码各平台表头,正是确定性的体现。确定性是幂等性的前提:只有输出可复现,才能安全地重跑而不产生副作用。

保持安全的三个习惯

作者每个批次都坚持三条规则:

  • 一次只跑一个进程。在单核机器上绝不并行展开图像任务。串行按单项看更慢,但整体更快,因为没有任何东西会死掉。
  • 在项目之间用 gc.collect() 释放内存,并用 resource.getrusage(RUSAGE_SELF).ru_maxrss 测量峰值 RSS 而非平均值。平均内存对上限毫无参考意义。
  • 重渲染步骤每个子进程只处理一个文件,让内存在素材之间真正归还给操作系统。

这些经验来自运行约 19,000 行完整流水线、打包 39 个产品、产出约 856 MB 成品的过程。作者说,如果其他一切都丢了,追踪器是他第一个要重建的部件。

峰值 RSS(Resident Set Size)是衡量进程实际占用物理内存的指标,ru_maxrss 记录的是进程生命周期内达到的最大常驻内存集大小。与平均内存相比,峰值 RSS 更能反映内存瓶颈:在单核 2GB 内存的服务器上,一旦峰值超过可用内存,进程会被 OOM killer 终止,导致批次中断。作者强调测量峰值而非平均值,是因为平均值会掩盖瞬时内存尖峰。gc.collect() 手动触发垃圾回收,可以及时释放循环引用或大对象占用的内存;在子进程中逐文件处理,则让每个子进程退出时由操作系统回收全部内存,避免内存泄漏累积。这些习惯共同确保长时间批量任务在资源受限环境下稳定运行。

对自动化发布工作流的启示

这个案例的价值超出 Adobe Stock 本身。任何面向多平台的内容分发——素材库、电商上架、播客分发——都存在同样的非原子提交问题。核心思路可以归纳为三点:把“已完成”状态从平台后台拉回本地磁盘;用确定性输出消除重跑的动机;在资源受限的环境里优先选择零依赖、串行执行的方案。对于使用 AI 工具批量生成内容并分发的创作者,这套思路尤其值得借鉴:生成可以快,但提交必须有账本。

作者将追踪器、元数据构建器以及另外两个脚本(Markdown 转 PDF 转换器、负责打包文件并渲染 1280x720 封面和 600x600 缩略图的产品打包器)打包进了 Pipeline Starter Kit,与其矢量提示词包同源,运行在每月几美元成本的硬件上。

#Adobe Stock#上传追踪器#批量上传#Python脚本#元数据生成#内容发布流水线#重复提交
分享:

相关推荐