What the Meta Ad Library scraper collects
Scrapes Meta's Ad Library (facebook.com/ads/library) by keyword or by advertiser Page ID, in one country or all of them, and returns one row per ad: text, link, call to action, every image and video URL, the dates it ran, the platforms it ran on and the advertiser with its exact Page like count. With --details it adds EU and UK reach, targeting, payer and beneficiary; political and issue ads carry spend, impressions and reach ranges. Output is JSON or CSV with a .meta.json sidecar.
Keyword search
Input: A search term, optionally with --region and --exact-phraseOne row per ad matching the search in the chosen country (or ALL): creative text, link, CTA, media URLs, run dates, platforms, format and advertiser.
Advertiser ads
Input: An advertiser's numeric Page IDEvery ad of one advertiser, active and inactive, with the advertiser's exact like count and categories on every row.
Political & issue ads
Input: A search term with --ad-type political_and_issue_adsAdds the library's spend, impressions and reach ranges and the "Paid for by" disclosure to each row.
Ad details (EU/UK reach & targeting)
Input: Any search with --detailsReads each ad's "See ad details" dialog: EU and UK reach with a country, age and gender breakdown, targeted ages, gender and locations, payer, beneficiary and the advertiser's Instagram account.
Pasted search URLs
Input: An Ad Library search URL from your browser, or a file of themRuns searches exactly as copied from the address bar; a URL without a country gets --region filled in so results do not follow your own location.
From zero to Meta Ad Library data in 3 steps
Install
Clone the repository and install one browser engine. Python 3.9+.
$ git clone https://github.com/2scraper/facebook-ads-scraper.git $ cd facebook-ads-scraper $ python3 -m venv .venv && source .venv/bin/activate $ pip install -r requirements-playwright.txt $ playwright install chromium $ python3 playwright_scraper.py --query nike --region US --max-results 100
Run a scraper
Pick what you need and run it. Every flag is documented in the README.
$ python3 playwright_scraper.py --query "running shoes" --region US --format csv$ python3 playwright_scraper.py --page-id 15087023444 --active-status all --max-results 500$ python3 playwright_scraper.py --query election --region US --ad-type political_and_issue_ads$ python3 playwright_scraper.py --query nike --region DE --details$ python3 playwright_scraper.py --url "https://www.facebook.com/ads/library/?q=nike&country=DE"Get structured data
Rows land in JSON or CSV (
--format), with a.meta.jsonsidecar recording status, pages fetched and why the run stopped.
Meta Ad Library data fields
55 columns per row in a predictable schema. The most useful ones:
| Field | What it holds |
|---|---|
ad_id | The Library ID printed on every card; rows are deduplicated on it |
is_active | Whether the ad is still running |
advertiser_name | The advertiser's Page name |
advertiser_page_id | Numeric Page ID, usable with --page-id |
advertiser_likes | The Page's exact like count, not the rounded figure |
first_shown_at | First day the ad ran (Pacific time) |
last_shown_at | Last day shown; today for an active ad |
days_shown | Days the ad has run, both ends counted |
platforms_json | Where it ran: FACEBOOK, INSTAGRAM, MESSENGER, AUDIENCE_NETWORK, THREADS, WHATSAPP |
display_format | IMAGE, VIDEO, CAROUSEL, DPA or DCO |
body | The creative's text, rendered rather than a template |
link_url | The ad's destination link as the library publishes it |
cta_text | Call to action, e.g. Shop now |
image_urls_json | Every image of the creative and its cards |
spend | Political and issue ads: spend range as printed |
eu_reach | With --details: people reached in the EU |
[
{
"sku": "facebook-ad-1550534860016399",
"source": "facebook.com",
"category": "ad",
"title": "🔥🍭Themepack is a FREE and personalized APP😍 various styles of Theme💎 、Weather💖、Photo😊、C…",
"brand": null,
"price": null,
"currency": null,
"price_source": null,
"product_url": "https://www.facebook.com/ads/library/?id=1550534860016399",
"image_url": "https://scontent.fakx3-1.fna.fbcdn.net/v/t39.35426-6/700927950_933331229664450_64877633…",
"scraped_at": "2026-10-06T12:44:15Z",
"ad_id": "1550534860016399",
"collation_id": null,
"versions": 1,
"is_active": true,
"advertiser_name": "Perfect themes",
"advertiser_page_id": "115939511520844",
"advertiser_url": "https://www.facebook.com/100093512920259/",
"advertiser_likes": 18662,
"advertiser_categories_json": "[\"Topic\"]",
"first_shown_at": "2026-05-19",
"last_shown_at": "2026-10-06",
"days_shown": 141,
"platforms_json": "[\"FACEBOOK\", \"INSTAGRAM\", \"AUDIENCE_NETWORK\", \"MESSENGER\", \"THREADS\"]",
"display_format": "IMAGE",
"body": "🔥🍭Themepack is a FREE and personalized APP😍 various styles of Theme💎 、Weather💖、Photo😊、C…",
"link_url": "http://play.google.com/store/apps/details?id=com.iconchanger.widget.theme.shortcut",
"link_title": "𝕊𝕦𝕡𝕖𝕣 𝕙𝕠𝕥 𝕨𝕚𝕕𝕘𝕖𝕥𝕤 𝕒𝕡𝕡 !",
"link_description": null,
"link_caption": "play.google.com",
"cta_text": "Install now",
"cta_type": "INSTALL_MOBILE_APP",
"card_count": 0,
"image_urls_json": "[\"https://scontent.fakx3-1.fna.fbcdn.net/v/t39.35426-6/700927950_933331229664450_648776…",
"video_urls_json": null,
"ad_categories_json": "[\"UNKNOWN\"]",
"paid_for_by": null,
"spend": null,
"spend_currency": null,
"impressions": null
},
{
"sku": "facebook-ad-1870984290951787",
"source": "facebook.com",
"category": "ad",
"title": "Get the gear that's up for it all. Any time. Anywhere.",
"brand": null,
"price": null,
"currency": null,
"price_source": null,
"product_url": "https://www.facebook.com/ads/library/?id=1870984290951787",
"image_url": "https://scontent.fakx3-1.fna.fbcdn.net/v/t39.35426-6/793144872_3319523978256315_8997515…",
"scraped_at": "2026-10-06T12:44:15Z",
"ad_id": "1870984290951787",
"collation_id": null,
"versions": 1,
"is_active": true,
"advertiser_name": "Nike",
"advertiser_page_id": "15087023444",
"advertiser_url": "https://www.facebook.com/nike/",
"advertiser_likes": 39506245,
"advertiser_categories_json": "[\"Sportswear\"]",
"first_shown_at": "2026-09-02",
"last_shown_at": "2026-10-06",
"days_shown": 35,
"platforms_json": "[\"FACEBOOK\", \"INSTAGRAM\", \"AUDIENCE_NETWORK\", \"MESSENGER\", \"THREADS\"]",
"display_format": "DPA",
"body": "Get the gear that's up for it all. Any time. Anywhere.",
"link_url": "https://www.nike.com/",
"link_title": "Nike Primary",
"link_description": "Nike delivers innovative products, experiences and services to inspire athletes.",
"link_caption": "http://nike.com/",
"cta_text": "Shop now",
"cta_type": "SHOP_NOW",
"card_count": 6,
"image_urls_json": "[\"https://scontent.fakx3-1.fna.fbcdn.net/v/t39.35426-6/793144872_3319523978256315_89975…",
"video_urls_json": null,
"ad_categories_json": "[\"UNKNOWN\"]",
"paid_for_by": null,
"spend": null,
"spend_currency": null,
"impressions": null
}
]First 2 of 5 rows from a real run — sample_output.json. Long values shortened here.
Handles what breaks naive Meta Ad Library scrapers
An explicit country on every search
A library search without country= runs in the visitor's own country (a Kazakh address got KZ). Every URL the tool builds or accepts names one, so results do not depend on where you run it.
Scroll batches read off the wire
There are no page numbers: the first 30 ads come from the JSON the page embeds and each scroll brings 10 more in a GraphQL response, which the engine reads directly. Three scrolls with nothing new is reported as stalled and partial, never as the end.
A 403 with the full page is not a block
The library serves its results under HTTP 403, so the status code is not read as a refusal; the page's own content decides. A login page or a non-library page is reported as blocked, never as an empty search.
Rendered text, not templates
Catalogue and dynamic ads (352 of 549 captured) carry a {{product.name}} template as their top-level text; the tool uses the first card's rendered text instead. Duplicate ads within a search are dropped on the Library ID.
Scraping infrastructure included
The same tested core runs every 2scraper repository, so the hard parts work the same way on every site.
CAPTCHA handling
Detects the challenge a page really serves and, with a 2Captcha key, solves it only when it blocks the data — a capped number of solves per page.
Proxy rotation
Single proxy or a pool from a file, rotated on blocks with a fresh browser per exit. Credentials stay out of argv and logs.
Fingerprints
Optional complete device identity from the 2Captcha Fingerprint API — user agent, client hints, locale and timezone that agree with each other.
Scraping Browser API
Point --cdp-endpoint at a managed Chromium with its own exit country — no browser infrastructure to run yourself.
Honest run metadata
Every run writes a .meta.json sidecar and distinct exit codes: complete, blocked, empty and partial are never confused, and a failed run never overwrites good data.
Diff two runs
diff_runs.py compares two complete Meta Ad Library snapshots by id — new, removed and changed rows for monitoring.
What teams do with Meta Ad Library data
Competitor ad monitoring
Pull every active and inactive ad of a competitor's Page and diff runs to see which creatives, CTAs and links started or stopped.
Creative research
Collect ad text, formats, call-to-action types and media URLs for a keyword across countries to study what runs in a market.
Political ad transparency
Gather spend, impressions, reach ranges and "Paid for by" disclosures for political and issue ads in a country.
EU reach and targeting analysis
With --details, read EU and UK reach broken down by country, age and gender, plus targeted ages, gender and locations.
Free to run. Pay only for what the site requires.
Runs with no key, proxy or account
The Ad Library is public and this tool reads it logged out: no account, no cookies, no key. Measured on 2026-10-05 and 2026-10-06 from an ordinary residential IP with no proxy, 46 page loads in an hour and a 500-ad search read normally, with no captcha and no login wall. 2Captcha residential proxies help for many searches in a row or a specific exit country; the Scraper API mode (TWOCAPTCHA_KEY) runs without a browser, returning the first 30 ads of each search.
Optional 2Captcha services — captcha solving, the Scraping Browser API, residential proxies and fingerprints — work behind one key.
Good to know
- The broadest searches report 50,001 results; that is the library's ceiling, not the real count.
- Spend, impressions and reach are ranges, and only for political and issue ads; media URLs are signed links that expire after some days.
- The Scraper API mode returns the first 30 ads per search and does not support --details.
2scraper vs. hosted scraper APIs
| 2scraper | Typical hosted API | |
|---|---|---|
| Code | Open source, MIT — read and patch it | Closed |
| Cost | Free; optional services billed only when used | Subscription or per request / per result |
| Runs on | Your machine, server or CI | The vendor's infrastructure |
| Output | JSON/CSV files + run metadata | API responses |
| When the site changes | Fix it yourself or open an issue / PR | Wait for the vendor |
Meta Ad Library scraper FAQ
Do I need a Facebook account or an API key?
No. The tool reads what any logged-out visitor is shown; no .env is needed for the default run.
How many ads can one search return?
As many as --max-results allows (default 100): the page embeds 30 and each scroll adds 10. The run stops at the limit, at the end of results, or reports a stall as partial.
Can I scrape all ads of one advertiser?
Yes: pass its numeric Page ID with --page-id (the advertiser_page_id column), usually with --active-status all.
What output formats are supported?
JSON or CSV, each with a .meta.json sidecar holding status, stop reason, ads read against the library's own total, and failed searches. In CSV, formula-like text gets a leading apostrophe, which the sidecar records.
Can I paste a single-ad link?
No. Opened logged out, a ?id= link returned a different ad of the same advertiser, so the tool refuses it; search the advertiser with --page-id instead.
Is scraping the Ad Library allowed?
It is a transparency tool Meta publishes for anyone, but Meta's terms restrict automated collection and Meta offers an official API for some uses. This is public data; follow the site's terms and the law where you operate.