Over one week in July I scraped 697,299 products from 397 European Shopify stores, enriched ~110,000 of them with LLM-judged attributes, and mined another ~72,000 labels for free — for a total LLM bill of about $31. Solo, on a laptop, mostly unattended. The pipeline is open source, and the full case study is on this site. This essay is about what generalizes: the 18 lessons in the repo's LESSONS.md, distilled to the seven that will save you real money and real days.
1. A transport failure is not a business verdict
My scraper's first full run "completed" with 434 of 504 stores marked failed. Almost all of those failures were fake. Shopify throttles by IP, and my code was recording HTTP 429s as "not a Shopify store" — a network-layer event written down as a fact about the business. The tell was temporal: detection worked beautifully for the first 50 stores, then collapsed to roughly zero. Nothing about the stores changed at store 51. My reputation with the server did.
The fix was not retry logic. It was a type system: RateLimited and RetryableError structurally cannot reach the "failed" state. Once "throttled" became unrepresentable as "dead," and a redundant probe request was deleted (it was half of all traffic), the re-run had zero throttle events. This generalizes far beyond scraping: any time your system records a verdict, ask which layer of the stack the evidence came from. I've watched teams kill features over "failed" API integrations that were expired credentials.
2. Coverage is not accuracy
Before the LLM, I tried keyword rules for colour and material — the obvious cheap approach. The rules produced impressive coverage numbers and were ~21% wrong. They grabbed upsells ("add a glass screen protector" → Glass), trims, promo tags ("black friday" → Black), and the subjects of products rather than the products themselves. A dashboard showing "82% of products now have a colour" looks like progress. It's only progress if the labels are true, and no aggregate metric will tell you that.
The only thing that told me was reading. I audited 50 samples against their source text, by hand, with a written protocol. That one sitting found every systematic error in both the rules and, later, the model. Fifty rows sounds unserious next to 697K; it caught what no metric did.
3. Models beat rules because they read context
The same audit measured the LLM at roughly 94% precision on the same task. The difference is not intelligence, it's context: the model read "suede protector spray" and correctly refused to call it Suede, because it understood the suede belongs to your shoes, not the bottle. A keyword can't know that.
But models fail in their own ways, and the audit surfaced those too: brand priors ("it's an Amina Muaddi bag, so Leather" — with zero material stated anywhere) and undocumented inference chains (denim → Cotton, plausible but never sanctioned). Both became explicit written rules in the prompt. The lesson isn't "models good, rules bad" — it's that both need their error modes measured, and the model's error modes are subtler.
4. The prompt is a file, not a memory
The most expensive small mistake of the project: a prompt that was retyped instead of copied lost one sentence, and 97% of an eyewear batch came back UNKNOWN. If the prompt that produced your data isn't in version control, you don't actually know what produced your data — you know what you remember asking for. Prompt-as-file costs nothing and turns "why is this batch weird?" from archaeology into a diff.
"If the prompt that produced your data isn't in version control, you don't know what produced your data — you know what you remember asking for."— TheGlocalPM
5. The Batch API is half price — if you respect its quirks
Anthropic's Batch API runs at 50% of list price, which is how ~250 products cost about $0.07 to enrich. Two quirks nearly ate the savings:
-
Sonnet thinks by default.
Omit the
thinkingparameter and the model runs adaptive reasoning. For chat, wonderful. For bulk classification, it will spend your entire token budget deliberating and emit zero answers. Disable it explicitly, in code, where it can't be forgotten. -
Big requests silently drop rows.
At 250 products per request, 1:1 row correspondence broke ~40% of the time — not truncation, just missing rows with a clean
end_turn. At 50 rows per request the problem vanished. Roughly a third of my $31 was paying twice for this lesson. Validate every response by key set, never by line count.
For calibration, the alternatives: a manual labelling team for the same 110K products runs ~$5–15K over weeks; naive per-request LLM calls with thinking on and no batching land around $300–500 plus days of babysitting. The batch-plus-validation pattern is, as far as I know, the cheapest reliable bulk-LLM setup there is.
6. Free labels exist — but measure before you apply
The ~110K LLM answers doubled as training data for propagation rules: brand-and-product-type groups that are ≥90% one material, name tokens that are ≥95% one material. Scored on a held-out split before touching a single row: 97.1% precision. Applied only where independent mechanisms agreed, and tagged inferred so every propagated value is forever separable and revertible. That free step added +72K labels — more than the paid run itself.
The negative result mattered just as much: colour does not propagate. It's a per-product fact — neighbouring products predict about 3% of it. Twenty minutes of measurement killed what could have been days of wishful engineering. "Measure before apply" cuts both ways, and the cheap 'no' is one of its best outputs.
7. The boring machine will betray you before the clever one does
One night, throughput collapsed from 38 stores an hour to 5 — a curve that looks exactly like cumulative rate-limiting. I spent hours investigating the API. It was my Mac going to sleep. One caffeinate -i restored full speed. When a pipeline degrades, check the dumbest layer first — power, sleep, disk, network — before theorizing about the smart one. That check is now a startup warning in the CLI, because a lesson that lives in a person's memory isn't a lesson; it's a future incident.
What this buys you
The point of all this discipline is not the pipeline — it's the answer quality on the other end. Before enrichment, my benchmark for "what does a leather bag cost?" was a single number: median €640, pooling couture with high-street, useful to nobody. After: mid-tier €82 (n=202), premium €197 (n=116), luxury €440 (n=896) — numbers a merchant can actually price against, each with its sample size attached.
Everything above — the typed failures, the 50-row requests, the audit protocol, all 18 lessons — is in the public repo, MIT licensed, cited line-by-line in the code. The case study tells the full story with the economics side by side. Steal the patterns; skip the tuition.
Run it yourself
The engine — scraper, chunker, Batch API enrichment, merge, precision-gated propagation, and the audit protocol — is free and open source. Point it at a stores list, version your prompt, and measure before you trust.