去年春天,一位卖家把 Gumroad 后台和存着 40 个成品的文件夹并排打开,然后感到一阵尴尬。产品本身没问题,问题出在目录上——它看起来像四十个陌生人各自做出来的东西。一个封面是深蓝配衬线字体,一个是荧光绿配剪贴画,还有一个干脆就是压缩包的截图。

有买家发邮件来问:这些东西真的都出自同一家店吗?就是那一刻,他不再把包装当成事后补丁,而是把整个目录当成一个产品来做。整套流程跑在一台 1 vCPU、2 GB 内存、没有 GPU 的服务器上。

目录是一个产品,只是有很多 SKU

让他想通的那次认知转变是:买家评估的不是一个个压缩包,而是这家店。如果封面、文件名和文件夹结构前后不一致,每上一个新品,店铺的可信度不是增加而是减少。

于是他选定一套封面模板,之后再没动过。他的 pack_product.py 用同一段 Pillow 代码路径、同一款字体、同一套版式,把每张封面渲染成 1280x720、每张缩略图渲染成 600x600,只改标题和副标题。它接收产品名、副标题和文件数量,一条命令就写出压缩包、封面和缩略图:

python pack_product.py my-product "My Product" "A short subtitle" 9

做到第九个产品时,封面已经可以互换。这就是目标——喜欢过某个产品的买家,一眼就能认出下一个。

命名和文件夹要先于一切

没有固定结构,目录迟早变成杂货抽屉。整套流水线默认的布局是这样的:

  • assets/ 原始输出,每个素材一个文件
  • output/ 待提交文件加 metadata.csv
  • packs/ 成品压缩包加封面
  • research/ 笔记和来源
  • scripts/ 工具

有两条听起来无聊但很重要的规则。第一,文件名会变成标题。没有提供标题文件时,元数据脚本会把文件名转成标题格式,这意味着 botanical-border-04.jpg 第一遍就能变成可用标题。如果你的文件名是 final_final_v3.jpg,你的 CSV 就会继承这个毛病。第二,research/ 要永久保留。他曾为了"清理"删掉抓取日志,结果丢了三个后续产品的原始素材,再也不敢了。

元数据是图库平台先审判你的地方

如果同时在图库渠道卖货,目录问题会翻倍,因为每个平台要求的 CSV 形状不同,表头错一个就立刻被拒。Adobe Stock 要 5 列表头,Vecteezy 要 4 列,Dreamstime 要 15 列。错一列,整批上传在人工看到你的作品之前就被弹回来。

他写了 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

一致性收益和封面一样:每个平台上的每个素材,标题和关键词都来自同一个真相源,所以买家无论在 Adobe 还是在你自己的收银台遇到你,看到的品牌是同一个。

唯一会让账号被标记的错误

这一条和好看无关,和生存有关。让图库账号被标记最快的方式,是把上传批次跑两遍。他早期干过一次,重跑了一个部分失败的脚本,结果发出去 30 个重复项。清理花了一个晚上,还算走运。

现在 upload_tracker.py 是一本普通账本,记住什么已经发过:

python upload_tracker.py pending items.txt # 显示还没做的
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status

重跑永远不会重复发布,因为入队之前会先查账本。它只用 Python 标准库,完全不用安装。

把文档变成看起来像产品的产品

他目录里有一半不是美术文件,而是文档:指南、清单、提示词手册。一个裸 Markdown 文件不算产品。md2pdf.py 接收一个 Markdown 文件,渲染出带样式标题、表格、列表和链接的干净 PDF:

python md2pdf.py content.md product.pdf "My Product Title"

它用 reportlab,这就是全部依赖。每次排版都一样,所以目录里的 PDF 和旁边的封面是配套的。

让小服务器扛住的小习惯

这一切都不需要更大的机器,但需要纪律。一次只跑一个进程,绝不在单核上并行展开图片任务。每个素材处理完调用 gc.collect() 释放内存。用 resource.getrusage(RUSAGE_SELF).ru_maxrss 测峰值内存而不是平均占用,因为杀死 2 GB 机器的是峰值。重渲染步骤一个文件一个子进程,让内存归还给操作系统。只需要检查版式时,用 60 dpi 看预览,而不是渲染 300 dpi。

最终结果:39 个成品、856 MB 打包输出,压在大约 19000 行流水线代码之下,全部在同一台 1 vCPU、2 GB 内存的服务器上验证过。

上面这些不是事后写下的建议,而是四个独立的 Python 脚本:make_metadata.py、upload_tracker.py、md2pdf.py 和 pack_product.py。没有框架,没有云账号,第三方包总共两个——Pillow 和 reportlab,一半脚本只需要 Python 本身。它们是更大流水线里的打包层,也是把杂货抽屉变成"看起来像一个品牌"的目录的东西。