為何錯誤的 CSV 標題列會令素材平台的上載批次在人工查看前遭拒
Why Wrong CSV Headers Get Your Stock Uploads Rejected Before Anyone Looks At Them
Adobe Stock、Vecteezy 和 Dreamstime 會先以機器檢查 CSV 欄數及欄位次序,標題列錯誤便會令整批素材在人工審核前遭拒。三個平台分別要求 5、4、15 欄;作者用 make_metadata.py 按平台生成標題列,並以 upload_tracker.py 記錄已上載檔案,避免重複提交。兩個工具只用 Python 標準程式庫,無需安裝依賴。
Last March I lost a whole weekend to a header row. I had 60 JPGs ready for Adobe Stock, a CSV I'd built by hand in a spreadsheet, and I hit submit. The rejection came back in under a minute. Not a review rejection, not quality, the file never got that far. The parser choked on my header row and the whole batch bounced. I fixed the header, resubmitted, and got a second rejection for a different column. That's when I stopped guessing and wrote the headers down once, in code.
Here's what I learned about why this happens, because it's the least glamorous and most expensive part of the whole pipeline.
The parser reads before any human does
Every stock platform runs your CSV through a machine check first. Adobe Stock, Vecteezy, and Dreamstime all do this. The check verifies the column count and the column order before it validates a single asset. Get it wrong and you get a metadata error, not a content review. You wait nothing, you learn almost nothing, and your files sit unsubmitted while you debug a spreadsheet.
The counts are fixed and they are not the same across platforms:
- Adobe Stock: 5 columns. Filename, title, keywords, category, releases.
- Vecteezy: 4 columns. Filename, title, keywords, category.
- Dreamstime: 15 columns. The widest of the three, and the one where hand-built CSVs break most often, because a missing empty column shifts everything after it.
The trap is that these look similar enough that your brain fills in the gaps. Adobe's 5-column header does not work on Vecteezy. Dreamstime's 15 columns will not tolerate a 5-column row. I know because I tried all three combinations.
Why I stopped hand-editing CSVs
A spreadsheet feels safe until you do it forty times. The failure modes are all boring: a trailing comma that creates a phantom 16th column, a keyword field where you pasted commas instead of the separator the platform wants, a filename with the wrong extension so the row points at a file that doesn't exist. Each one bounces the batch, and some platforms count repeated failed submissions against you before a human ever sees your work.
So I wrote make_metadata.py. It takes a folder of files and a platform flag and writes the exact header that platform demands, and nothing else:
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
It uses only the Python standard library, so there is nothing to install. The title defaults to your filename, title-cased, which is fine for a first pass but you should replace it with real titles and keywords before you submit. There's a --titles titles.json flag for that.
The second way automation bites you: double-posting
Once your CSV is right, the next machine-rejection risk is you, running the same batch twice. I did this early on. The upload script re-ran, the same filenames went up again, and duplicate submissions are one of the fastest ways to get an account flagged. No platform wants the same asset twice from the same contributor.
The fix is dumb and it works: a ledger. upload_tracker.py keeps a plain record of what you've already posted.
python upload_tracker.py pending items.txt # shows what is NOT done yet
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Before any batch run, check pending. If a file isn't in the ledger, it goes up. If it is, it doesn't. This also uses only the standard library. Two scripts, zero dependencies, and they removed the two failure modes that cost me the most time.
The boring math of why this matters
The art is maybe a third of the work. In the parent project that produced 39 finished products, about 856 MB of output, the pipeline code that handles metadata, tracking, and packaging is roughly 19,000 lines. That ratio is not an accident. The creative part is one prompt away. The part that gets you rejected is a header row, and it rejects you silently, before any reviewer opens your file.
The other thing worth saying: none of this needs a big machine. The whole pipeline, including image generation and packaging, ran on 1 vCPU and 2 GB RAM with no GPU. The habits that made that work are unglamorous: one process at a time, gc.collect() after each asset, measure peak RSS with resource.getrusage, not average. If your metadata step needs more than 2 GB, you've over-engineered it. Building a 5-column CSV should use almost nothing.
What I'd tell past me
Write the header once, in code, per platform. Never hand-edit a submission CSV. Track every upload in a ledger so a re-run can't double-post. Keep your research notes forever; I deleted a research folder once to "clean up" and regretted it within a month when I needed the sources for a new product line.
All of this is now packaged as scripts because I got tired of rewriting it. The metadata builder, the upload tracker, a Markdown-to-PDF converter, and a packaging script that zips a product and renders a 1280×720 cover plus a 600×600 thumbnail are in one place. They're the same ones that packaged those 39 products. I've folded the workflow, the platform rules, and the small-server habits into the ai sticker factory playbook, which is the honest version of this post: what broke, what the rules actually are, and the tools I use to stop breaking them.
If you're about to submit your first batch, fix the headers first. Everything else can wait a day.
Get ai sticker factory playbook
Building digital products? Free guides & kits for digital product sellers.
來源:DEV Community · dev.to