For growth teams doing creative research at scale
How to compare 200 ads and find the pattern
Ask every ad the same question, in the same shape, and count the answers. The method has three parts: a fixed set of fields rather than free prose, one reading per ad with the session kept, and an aggregation step that happens in your own code rather than in a model. Prose is the part that does not scale: 200 paragraphs cannot be counted, and a model asked to summarise 200 paragraphs will produce a confident sentence that nobody can check.
Published 5 min read
The short version
- Ask for fields, not for prose. 200 paragraphs cannot be counted; 200 rows of the same eight fields can.
- One reading per ad, and keep every session_id. A second question across the library then costs a fraction of a second pass.
- Aggregate in your own code. A model asked to summarise 200 answers produces one confident sentence and no way to check it.
- Read the unanswered list per ad. An empty field and an unreadable ad are different findings, and a count that mixes them is wrong in a direction nobody notices.
Ask for fields, not for prose
The single decision that makes the difference between a study and a pile of paragraphs.
A reading of one ad should be prose, because a person reads it. A reading of 200 ads is data, because a spreadsheet reads it.
So the question changes shape. Instead of asking what happens in the ad, ask for the same short list of facts every time, and phrase each one so there is exactly one right answer in the file.
| Field | What it asks for | Why it is countable |
|---|---|---|
| hook_seconds | The second the first shot changes | A cut is a measurement, not an opinion |
| opens_on | face, product, text, or scene | Four values, decided by what fills the first frame |
| cuts_per_minute | Pace across the whole ad | Counted from the cut list, not estimated |
| offer_seconds | When an offer first appears on screen | The frame it appears in, or absent |
| offer_held_seconds | How long it stays up | Two frames and the distance between them |
| price_shown | true or false | It is on screen or it is not |
| cta_seconds | When the call to action arrives | The frame it appears in, or absent |
| aspect | 9:16, 1:1 or 16:9, after letterbox bars are removed | A property of the picture, measured before the reading |
Running it across the library
One reading per ad. Everything after that is arithmetic in your own code.
import { Playhead } from "@playhead/sdk";
const playhead = new Playhead();
const SCHEMA = `Answer only with these fields, one per line:
hook_seconds, opens_on (face|product|text|scene), cuts_per_minute,
offer_seconds, offer_held_seconds, price_shown (true|false),
cta_seconds, aspect. Write "absent" when the ad does not show it.`;
const rows = [];
for (const url of library) { // 200 Meta Ad Library links
const ad = await playhead.ask({ url, question: SCHEMA });
rows.push({
url,
session: ad.session_id, // the next question is cheap
fields: parse(ad.answer),
unread: ad.unanswered, // never fold this into "absent"
});
}The session_id per ad is the part worth keeping. A second question across the whole library, asked a week later, reads what each reading already found instead of looking at 200 videos again.
What we found
Not measured yet. This section is the reason the article is not published.
What goes wrong at 200 that never goes wrong at one
The library is not one shape. A 6 second vertical bumper and a 90 second wide explainer answer cuts_per_minute on completely different scales. Group by aspect and by duration before you compare anything, or the pattern you find is a pattern about format.
An empty column is a finding about the question. When a field comes back absent for most of the library, the usual cause is that the field was not answerable from the picture. Check ten of them by hand before you report the number.
One rule tuned on one category is not a rule. A finding from 200 direct-response ads in one vertical says nothing about brand film, and the honest version of the sentence names the library it came from.
The verdict
The method is the easy half. Ask for fields, keep the sessions, count in your own code, and separate absent from unreadable. The hard half is the library: a finding is only as good as the set it came from, and 200 ads from one category on one date is a claim about that category on that date. Say so in the write-up, and the number stays true for longer than the campaign.
Common questions
How do you analyse hundreds of video ads at once?
Ask every ad the same fixed set of fields rather than for a description, run one reading per ad through an API, and aggregate the fields in your own code. The part that does not scale is prose: 200 paragraphs cannot be counted, and asking a model to summarise them produces one sentence nobody can check.
Can this run automatically on new ads?
Yes. The reading is an ordinary HTTP call, so a scheduled job can run it on whatever is new in the library and append to the same table. A long job returns an id and fires a webhook when it settles.
What does a run of 200 ads cost?
The cost follows the length of the ads and the detail level, because both decide how many frames are read. One first look per ad plus a handful of follow-ups is the shape of the bill. See the pricing page for the credit figures.