- name
- meta_ads_extractor
- description
- Extract and save ad creative assets (images + videos) from the Meta Ads Library via browser automation, with dimensions, aspect ratios, and CTA destination URLs. Use when user says "/meta_ads_extractor", "extract Meta ads", "download competitor ads", "swipe file", "save Facebook ad creatives", "scrape ad library", or in French "extrais les pubs Meta", "telecharge les pubs concurrentes", "recupere les creatives Facebook", "fais un swipe file". For analyzing extracted ads into a strategy report, see /meta_ads_analyser. For the full extraction + analysis pipeline in one command, see /ads_analyst.
# Meta Ads Library Asset Extraction
Extract actual ad creative images and videos from Meta's Ad Library via browser automation.
> **Portability note:** delivery via the `message` tool (`action=send`) and the Telegram size
> limits below assume the OmegaOS VPS bridge. Off-VPS, drop assets into the run's output
> directory instead and skip the `message` send step — extraction logic is unchanged.
## Dynamic Workflow orchestration
Extraction fans out cleanly over independent **units** = each advertiser (and, within one
advertiser, each lazy-loaded batch / each asset). Use this when extracting from multiple
advertisers or a large swipe file; for a single small page, run linearly.
1. **Plan** — resolve the unit list: list advertisers/pages requested + the per-advertiser
target (top-N, media type, country). One unit = one `view_all_page_id` extraction.
2. **Parallel fan-out** — per advertiser: resolve delegate_page ID -> open filtered Ad Library
-> extract all media + CTA URLs from DOM (steps 1-4) -> download only the requested assets
(step 5). File-disjoint by advertiser slug, so units never collide (R-SCOPE: one writer
per output file).
3. **Adversarial verify (2-of-3)** — accept each unit only when at least 2 of 3 independent
checks pass: (a) every requested file exists and is non-zero on disk; (b) dimensions/aspect
ratio captured via `sips`/`identify`/`ffprobe` and written to metadata JSON; (c) DOM order
matches the requested rank (e.g. `images[0]` = top ad). A failed unit re-extracts (URLs may
have expired — see Known Limitations) rather than being reported as done.
4. **Synthesize** — merge per-advertiser metadata into one swipe-file manifest, dedup across
advertisers, then deliver (grouped images, then videos).
5. **Loop-until-dry** (unbounded asks like "get ALL their ads") — scroll + re-extract + dedup
until no new URLs appear across two consecutive scrolls, then stop.
## Prerequisites
- Browser tool (openclaw browser or Playwright)
- `curl` for downloading assets
## Workflow
### 1. Get the correct Ad Library Page ID
The Ad Library uses a **delegate_page ID**, NOT the profile ID from meta tags. This is the #1 source of errors.
```
Navigate to: https://www.facebook.com/<page-username>/
Extract via JS: document.documentElement.innerHTML.match(/"delegate_page":\{[^}]*"id":"(\d+)"/)
Fallback (if no delegate_page): use fb://profile/<id> from al:android:url meta tag
```
The delegate_page ID is what `view_all_page_id` parameter expects.
### 2. Open the Ad Library with filters
Build URL with these parameters:
```
https://www.facebook.com/ads/library/?active_status=all&ad_type=all&country=ALL&is_targeted_country=false&media_type=image_and_meme&search_type=page&view_all_page_id={PAGE_ID}
```
Key filter values:
- `active_status`: `active` | `inactive` | `all`
- `media_type`: `image_and_meme` | `video` | `all` (omit for all)
- `country`: `ALL` | `US` | `DE` etc.
### 3. Extract media URLs from DOM
Run the extraction JS from `references/dom-selectors.md` via browser evaluate. Returns JSON with image and video URLs.
**Important: extraction ≠ downloading.** The JS extracts ALL URLs from the page (instant, no network cost). Then download only the specific assets requested. When the user asks for "top N ads," the page sort order matches DOM order — so `images[0]` / `videos[0]` = highest impressions.
**Image selectors:** `img` elements with `fbcdn.net` src containing `s600x600` (ad thumbnails).
**Video selectors:** `video` elements with `fbcdn.net` src (direct .mp4 URLs).
**Video posters:** `video[poster]` attributes (full-res thumbnail frames).
### 4. Extract CTA destination URLs
Each ad card has a "Learn More" (or similar) button linking to the advertiser's landing page. Extract these for landing page analysis:
```javascript
// Run via browser evaluate — extracts CTA hrefs from ad cards
(() => {
const ctas = [...document.querySelectorAll('a')]
.filter(a => a.textContent.trim().match(/^(Learn More|Shop Now|Sign Up|Download|Get Offer|Book Now|Apply Now|Contact Us|Subscribe|Watch More|See More)$/i))
.map(a => ({ text: a.textContent.trim(), url: a.href }))
.filter(c => c.url && !c.url.includes('facebook.com/ads/library'));
return JSON.stringify(ctas);
})()
```
These URLs can be passed to the **landing-page-analysis** skill for offer/funnel analysis.
### 5. Download assets & capture dimensions
All fbcdn.net URLs work with direct `curl` — no authentication needed. Only download what was requested.
```bash
curl -sL -o "output.jpg" "https://scontent-xxx.xx.fbcdn.net/v/..."
curl -sL -o "output.mp4" "https://video-xxx.xx.fbcdn.net/v/..."
```
### 6. Extract aspect ratios
After downloading, determine the aspect ratio of each asset:
**For images (using `sips` on macOS or `identify` from ImageMagick):**
```bash
# macOS
sips -g pixelWidth -g pixelHeight image.jpg
# Calculate ratio
width=1080; height=1350
# 1080:1350 = 4:5 (vertical feed)
```
**For videos (using `ffprobe`):**
```bash
ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 video.mp4
# Output: 1080,1920 → 9:16 (Stories/Reels)
```
**Common Meta ad aspect ratios:**
| Ratio | Dimensions | Use Case |
|-------|------------|----------|
| 1:1 | 1080×1080 | Feed (universal) |
| 4:5 | 1080×1350 | Feed (recommended, more space) |
| 9:16 | 1080×1920 | Stories, Reels |
| 16:9 | 1920×1080 | Video, landscape |
| 1.91:1 | 1200×628 | Link ads |
**Save metadata to JSON:**
```json
{
"filename": "brand-video-01.mp4",
"type": "video",
"width": 1080,
"height": 1920,
"aspect_ratio": "9:16",
"duration_seconds": 31
}
```
Create `{advertiser-slug}-metadata.json` alongside the assets with dimensions for each file.
### 6. Scroll for more results
The Ad Library lazy-loads ads. To get more than the initial batch (~20), scroll down and wait, then re-extract. The JS deduplicates automatically.
## Naming Convention
```
{advertiser-slug}-{type}-{nn}.{ext}
```
Examples: `hbs-online-image-01.jpg`, `iman-gadzhi-video-03.mp4`
## Delivering Assets
Send downloaded files via `message` tool with `action=send` and `filePath`. Include advertiser name and asset type in caption.
For batches: summary message first, then files one by one. Group images together, then videos.
**Telegram limits:** 50MB images / 2GB video. Most ad creatives are well under this.
## Integration with Other Skills
After extraction, use:
- **ad-creative-analysis** — Analyze each creative (video via Gemini, images via vision)
- **landing-page-analysis** — Visit + analyze the CTA destination page
## Known Limitations
- **Image resolution capped at 600×600** — server-enforced. Sufficient for reference/analysis.
- **Videos are full quality** — no resolution restriction.
- **Rate limiting** — add 1-2s delay between downloads for large batches.
- **Dynamic loading** — must scroll to load all ads; initial load shows ~20 results.
- **URL expiry** — fbcdn.net URLs contain auth tokens that expire. Download promptly.
## Output contract
Every run produces, per advertiser:
- The requested asset files named `{advertiser-slug}-{type}-{nn}.{ext}` (images grouped, then videos).
- A `{advertiser-slug}-metadata.json` listing every file with `type`, `width`, `height`,
`aspect_ratio`, `duration_seconds` (videos), and the source CTA URL when available.
- (On-VPS) delivery: one summary message, then files sent one by one.
## Verify (before claiming done)
1. **Count match** — number of delivered files == number requested (per advertiser, per media type).
2. **On-disk truth** — each file exists and is non-zero: `ls -la` / file size > 0 (L1: runtime
is the only truth — a green extract with a 0-byte file is NOT done).
3. **Metadata complete** — every delivered file has a row in the metadata JSON with real
dimensions (not placeholders).
4. **Rank fidelity** — for top-N asks, confirm `images[0]`/`videos[0]` map to the highest-impression
ad (DOM order).
## Guardrails (evidence, no hallucination, scope)
- **Never invent assets or dimensions.** Every reported width/height/aspect ratio comes from
`sips`/`identify`/`ffprobe` on the actual downloaded file — cite the command output, never guess.
- **Extraction != fabrication.** If a URL is expired (fbcdn tokens expire) or a download fails,
re-extract; never substitute a similar/cached asset or report a file that isn't on disk.
- **Scope discipline.** Download only the assets requested (top-N / media type / country) — the
JS extracts ALL URLs for free, but bandwidth and Telegram limits are not. Do not expand beyond
the advertisers and filters the user named.
- **No regression.** This skill extracts and delivers assets; it does NOT analyze creatives or
landing pages — hand off to /meta_ads_analyser and the landing-page skill for that.
## Reference
See `references/dom-selectors.md` for detailed CSS selectors, URL patterns, and JS extraction code.
View on GitHub