为什么一个上传脚本最怕的不是上传失败,而是被重跑?去年三月,这个问题差点让一批40个Adobe Stock素材在同一个下午被提交两次。
当时一批上传已经排进队列,终端历史记录乱成一团,超时之前究竟哪一半已经走完,完全想不起来。Adobe对重复提交并不宽容:审核队列会把它们标出来,一个账号上标记攒得够多,就得和投稿支持团队进行一次相当不愉快的对话。
坐在一堆文件前面,却没有可靠的办法确认哪些已经提交过,问题不在素材本身,而在于缺一本账。
那天下午写出的第一版,后来成了upload_tracker.py。大约150行纯标准库Python,没有任何依赖,此后一直让那批39个产品保持干净。
要防的是重跑,不是上传
每个上传脚本迟早都会被重跑。批量传到一半网络断了、程序崩了、文件名打错一个字母,然后就得再跑一遍。如果脚本分不清“已经发布”和“还没发布”,重跑就意味着重复。
在素材平台上这不是小失误。Adobe的审核会抓出完全相同的文件,反复重复提交是让投稿账号被标记的最快途径之一。
所以这个追踪器要能立刻回答一个问题:对这一个确切的文件,它被标记为完成了吗?不靠文件日期去猜,不靠比对文件夹内容,要的是一条写下来的记录。
要一本账,不要一个数据库
SQLite同样属于标准库,也考虑过,但对这件事来说,一个纯文本文件更合适。每个素材一行,在标记完成时写入:
- done:把指定文件标记为已完成
- pending:读取批次清单和账本,只打印尚未标记完成的部分
- status:给出计数
整个接口就这些。文件是人可读的,任何编辑器都能打开,可以grep,备份只需要复制一个文本文件。在1 vCPU、2 GB内存的服务器上,打开并扫描几千行的文本文件几乎不花代价。没有守护进程,没有锁文件,没有数据库结构迁移。机器这么小的时候,少跑一个进程就是省下一份内存。
有一个细节很关键:账本按提交时的原样记录文件名,而用make_metadata.py构建的元数据CSV使用同一套文件名。Adobe的上传CSV是严格的5列表头,Vecteezy要4列,Dreamstime要15列,三家都会在表头不对时立刻拒绝。因为CSV构建器和追踪器共享同一个文件名来源,被标记完成的东西,就是真正上传上去的东西。
只用标准库是硬要求,不是偏好
四个流水线脚本里,make_metadata.py和upload_tracker.py只用标准库,另外两个需要Pillow做封面、reportlab做PDF。这个划分是有意的。
每次上传前后最常跑的那两个脚本,不该因为虚拟环境缺失或某个包被升级弄坏而失败。在那台小服务器上,这两个脚本干脆不建虚拟环境,等于少一个故障点,也省下大约100 MB磁盘空间。
这也意味着只要机器上有Python 3.9或更新版本,追踪器就能跑。服务器上跑过,笔记本上跑过,有一次赶截止日期还在朋友的机器上跑过。没有安装步骤,就没有理由跳过“标记完成”这一步。
真正防住重复发布的习惯
脚本只有在“标记完成”成为上传循环本身的一部分时才有效,它不能是一件单独去补的杂事。规则很简单:上传命令和done命令逐个文件紧挨着执行,绝不在最后一次性批量补。
如果事后才把40个文件标记完成,那又是在信任记忆,而三月出问题的正是记忆。
服务器上的内存管理遵循同样的纪律:一次只跑一个进程,每个素材处理完调用gc.collect(),用resource.getrusage(RUSAGE_SELF).ru_maxrss测量峰值常驻内存,而不是靠猜。上限只有2 GB的时候,平均内存说明不了任何问题。
六个月后的样子
产出39个产品、856 MB输出的完整流水线就架在这本账之上:面向三个平台的元数据构建器、Markdown转PDF的渲染器、负责打包并渲染1280×720封面和600×600缩略图的打包器。父项目里大约19000行流水线代码,在没有GPU的硬件上验证过。
追踪器是其中最小的一块,也是如果全部丢失会第一个重建的一块,因为其他每一块都假设它存在。
热门跟贴