Facebook Ad Library Scraper
Pricing
$0.50 / 1,000 results
Facebook Ad Library Scraper
Scrapes ads from the Facebook Ad Library. Accepts search URLs or Facebook page URLs and returns structured ad data.
Pricing
$0.50 / 1,000 results
Rating
0.0
(0)
Developer
Practical Tools
Maintained by CommunityActor stats
0
Bookmarked
104
Total users
14
Monthly active users
8 days ago
Last modified
Categories
Share
Extract ads from the Facebook Ad Library — creative, landing pages, platforms, impressions, spend, and advertiser page details — without a Facebook account or API key.
Search by keyword, pull every ad an advertiser page is running, or search for a keyword inside one advertiser's ads. Output is one clean row per ad with dates as YYYY-MM-DD, ready for CSV, Excel, or JSON.
Quick start
Search the Ad Library for a keyword
{ "keyword": "running shoes", "country": "US", "activeStatus": "active", "limitPerSource": 100 }
Get all ads from advertiser pages
{ "pages": ["https://www.facebook.com/ZapierApp", "nike", "254515547747520"], "limitPerSource": 0 }
Search for a keyword inside an advertiser's ads
{ "keyword": "free trial", "pages": ["https://www.facebook.com/ZapierApp"], "keywordWithinPages": true }
Monitor a competitor every week — schedule this and set a run tag per run:
{"pages": ["https://www.facebook.com/ZapierApp"],"datePreset": "last7d","limitPerSource": 0,"includePageSummary": true,"runTag": "2026-W39"}
Political ads about a topic, exact phrase, Instagram only
{"keyword": "climate change","searchType": "keyword_exact_phrase","adType": "political_and_issue_ads","publisherPlatforms": ["instagram"],"country": "US","datePreset": "last30d"}
Features
- No login required — scrapes the public Ad Library over plain HTTP, no browser
- Three ways in — keyword search, advertiser pages, or raw Ad Library URLs
- Search within an advertiser — turn on
keywordWithinPagesto find only one advertiser's ads about a topic - Advertiser pages in any form — page URL, handle (
ZapierApp), numeric page ID, or an Ad Library link - Every Ad Library filter — country, active status, ad category (political, housing, employment, credit), media type, date range, platforms, languages, exact-phrase matching
- Per-page summary rows — total ads, active ads, platform breakdown, date range for each advertiser
- EU transparency — EU reach with country / age / gender breakdown, targeting, payer and beneficiary, no login needed
- Rich ad-level fields — publisher platforms, impressions band, spend and reach (political ads), collation count (number of near-identical variants), page likes and categories, AI-generated flag
- Clean creatives — headline, body, CTA, landing page, image, video and thumbnail; carousels nested under
cards - Two output shapes — one row per ad (default) or one row per creative card
- Cost control — per-source and global ad limits
- Errors in the dataset — a page that cannot be resolved produces an error row, never a silent empty run
- Proxy-ready — optional Apify or custom proxies, usually not needed
Input
Sources
| Field | Type | Description |
|---|---|---|
keyword | string | A search of the whole Ad Library for this term, like the Ad Library search box. |
pages | string[] | Advertiser pages: every ad each one is running. Each entry may be a page URL, a handle, a numeric page ID, or an Ad Library URL containing view_all_page_id. |
urls | string[] | Searches copied from the Ad Library website, one per entry ({ "url": "…" } objects are accepted too). Everything in the URL is kept; filters you change are added only when the URL does not set them. Facebook page URLs here behave like entries in pages. |
keywordWithinPages | boolean | Default false: every entry above is its own source. true: the keyword stops being a search of its own and instead filters the pages' ads, e.g. "Model Y" inside Tesla's ads. |
Provide at least one of keyword, pages, or urls. Each one is scraped independently and the results are combined in one dataset, so the default input (a keyword, a page, and a URL) yields three sets of ads. Empty entries are ignored.
Filters (apply to keyword and pages)
| Field | Type | Default | Description |
|---|---|---|---|
country | string | ALL | ISO 3166-1 alpha-2 code (US, GB, DE) or ALL |
activeStatus | string | all | all, active, inactive |
adType | string | all | all, political_and_issue_ads, housing_ads, employment_ads, credit_ads. Political and issue ads carry spend and reachEstimate. |
mediaType | string | all | all, image, video, meme, none |
datePreset | string | — | last24h, last7d, last14d, last30d, last90d. Ads that were running at any point in the window. |
startDateMin / startDateMax | string | — | YYYY-MM-DD. Explicit window; overrides datePreset. Same "running at any point" semantics as Facebook's own date filter. |
publisherPlatforms | string[] | [] | Any of facebook, instagram, audience_network, messenger, threads. Empty = all. |
contentLanguages | string[] | [] | ISO 639-1 codes (en, es, de). Empty = all. |
searchType | string | keyword_unordered | keyword_unordered (any of the words) or keyword_exact_phrase. |
sortBy | string | impressions_desc | Sent to Facebook as the sort preference. Facebook's server-rendered results currently ignore it and come back in Facebook's own order. |
Limits
| Field | Type | Default | Description |
|---|---|---|---|
limitPerSource | integer | 10 | Max ads per keyword, page, or URL. 0 = all. |
maxItems | integer | 0 | Max ads across the whole run. 0 = no limit. The run stops as soon as it is reached. |
Output options
| Field | Type | Default | Description |
|---|---|---|---|
outputMode | string | ad | ad = one row per ad (first card flattened, all cards under cards). card = one row per creative card. |
includePageSummary | boolean | false | Add one summary row per advertiser page (see below). |
includeEuTransparency | boolean | false | Fetch each ad's details: EU reach with country / age / gender breakdown, targeting, payer and beneficiary, Instagram handle and followers, page verification. One extra request per ad. |
includeRawData | boolean | false | Attach the untouched Facebook ad object as rawData. |
runTag | string | — | Copied into the runTag column of every row. |
proxyConfiguration | object | — | Optional proxy settings. |
Output
Ad rows (default, outputMode: "ad")
{"adArchiveID": "899524914329334","adUrl": "https://www.facebook.com/ads/library/?id=899524914329334","pageName": "Wealthsimple","pageID": "268452016650171","pageUrl": "https://www.facebook.com/wealthsimple/","pageProfilePictureUrl": "https://scontent.../pp.jpg","pageLikeCount": 123456,"pageCategories": ["Financial service"],"isActive": false,"startDate": "2021-10-20","endDate": "2022-01-12","publisherPlatforms": ["FACEBOOK", "INSTAGRAM", "AUDIENCE_NETWORK", "MESSENGER"],"impressions": ">1M","impressionsIndex": 39,"spend": "CA$350K - CA$400K","currency": "CAD","reachEstimate": ">1M","categories": ["POLITICAL"],"isPolitical": true,"paidForBy": "Wealthsimple Technologies Inc.","disclaimer": null,"collationCount": 1,"collationID": "165980345735977","isAiGenerated": false,"stateMediaLabel": null,"targetedCountries": [],"totalActiveTimeSeconds": null,"displayFormat": "VIDEO","format": "video","isDCO": false,"primaryText": "Receive one free stock (up to $4.5k) just for signing up!","headline": "Buy & sell stocks in just a few taps","body": "Receive one free stock (up to $4.5k) just for signing up!","cta": "Install Now","ctaType": "INSTALL_MOBILE_APP","destinationUrl": "http://itunes.apple.com/app/id1403491709","displayDomain": "itunes.apple.com","linkDescription": null,"imageUrl": "https://scontent.../thumb.jpg","videoUrl": "https://video.../video.mp4","videoThumbnailUrl": "https://scontent.../thumb.jpg","cardCount": 1,"cards": [{ "cardIndex": 0, "headline": "Buy & sell stocks in just a few taps", "body": "…", "cta": "Install Now", "ctaType": "INSTALL_MOBILE_APP", "destinationUrl": "…", "displayDomain": "itunes.apple.com", "linkDescription": null, "imageUrl": "…", "videoUrl": "…", "videoThumbnailUrl": "…" }],"sourceUrl": "https://www.facebook.com/wealthsimple","position": 1,"totalResults": 1032,"runTag": "2026-W39","scrapedAt": "2026-09-22T16:00:00.000Z"}
| Field | Description |
|---|---|
adArchiveID, adUrl | Ad Library ID and direct link |
pageName, pageID, pageUrl, pageProfilePictureUrl, pageLikeCount, pageCategories | Advertiser page |
isActive, startDate, endDate | Run status and dates (endDate is null while still running) |
publisherPlatforms | Where the ad runs: FACEBOOK, INSTAGRAM, AUDIENCE_NETWORK, MESSENGER, THREADS |
impressions, impressionsIndex | Impressions band (e.g. 10K - 15K) and Facebook's ordinal index; only shown by Facebook for some ads |
spend, currency, reachEstimate | Political / issue ads only |
categories, isPolitical, paidForBy, disclaimer | Ad category labels and political disclaimers |
collationCount, collationID | Number of near-identical variants of this ad, and their group ID |
isAiGenerated | Facebook's "digitally created" flag |
stateMediaLabel, targetedCountries, totalActiveTimeSeconds | Transparency labels when Facebook provides them |
displayFormat, format, isDCO | Facebook's format (IMAGE, VIDEO, CAROUSEL, DCO, DPA…), our derived image / video / carousel / text, and whether the ad uses dynamic creative templates |
primaryText | Post text above the creative (null for DCO, where Facebook sends template variables) |
headline, body, cta, ctaType, destinationUrl, displayDomain, linkDescription, imageUrl, videoUrl, videoThumbnailUrl | First creative card |
cardCount, cards | All creative cards (carousel cards or DCO variants), deduplicated |
sourceUrl | The keyword URL, page, or URL you supplied |
position, totalResults | Rank within its source, and the total number of matching ads Facebook reports (capped at 50,001) |
runTag, scrapedAt | Your tag and the scrape timestamp |
rawData | The untouched Facebook object, only when includeRawData is on |
EU transparency and payer details (includeEuTransparency: true)
Adds these columns to every ad row (or card row), plus the full adDetails object:
| Field | Description |
|---|---|
euTotalReach | Estimated Meta accounts in the EU that saw the ad at least once |
payer, beneficiary | Who paid for and who benefits from the ad, as declared for EU delivery |
igUsername, igFollowers, pageVerification | Advertiser's Instagram handle and followers, and Facebook verification status (BLUE_VERIFIED…) |
adDetails.euTransparency | targetsEu, totalReach, ageAudience {min,max}, genderAudience, locationAudience[] (included/excluded countries, regions, cities), reachBreakdown[] rows of {country, ageRange, male, female, unknown} |
adDetails.payerBeneficiary | All payer / beneficiary pairs |
adDetails.advertiser | pageLikes, pageCategory, igVerified, isPoliticalPage, isRestricted |
adDetails.isTakenDown, violationTypes, isViolatingEuRules | Enforcement flags |
Ads never shown in the EU have euTransparency: null. The data comes from the same request the Ad Library website makes when you click See ad details; it needs no login. If Facebook changes that query, the run emits one AD_DETAILS_QUERY_INVALID error row and continues without details.
Card rows (outputMode: "card")
Same ad-level fields, but one row per creative card with a cardIndex column instead of cardCount / cards. Use this for creative analysis where each carousel card matters.
Page summary rows (includePageSummary: true)
One per advertiser page, marked rowType: "pageSummary". Shown in the Pages view of the Output tab.
{"rowType": "pageSummary","sourceUrl": "https://www.facebook.com/ZapierApp","pageName": "Zapier","pageID": "309796912374932","pageUrl": "https://www.facebook.com/ZapierApp/","pageProfilePictureUrl": "https://scontent.../pp.jpg","pageLikeCount": 56539,"pageCategories": ["Software"],"pageIsDeleted": false,"totalAds": 1032,"adsCollected": 50,"activeAdsCollected": 12,"platformBreakdown": { "FACEBOOK": 50, "INSTAGRAM": 31, "MESSENGER": 9 },"earliestStartDate": "2024-01-24","latestStartDate": "2026-06-04","runTag": "2026-W39","scrapedAt": "2026-09-22T16:00:00.000Z"}
totalAds is the count Facebook reports for your filters (so with activeStatus: "active" it is the number of ads currently live). The other counts describe the ads actually collected.
Error rows
If a source fails, the dataset gets a row with errorCode and error instead of a silent empty result. Shown in the Errors view.
errorCode | Meaning |
|---|---|
INVALID_PAGE | An entry in pages is not a Facebook page URL, handle, or ID |
PAGE_ID_NOT_FOUND | The Facebook page could not be resolved to an advertiser ID (private, deleted, or not a page) |
DUPLICATE_SOURCE | Two entries resolved to the same page; its ads appear once |
NO_AD_DATA | The Ad Library returned a page without ad data |
CHALLENGE_FAILED | Facebook's anti-bot challenge could not be solved |
AD_DETAILS_QUERY_INVALID | Facebook rejected the ad-details query (emitted once; the run continues without EU data) |
REQUEST_FAILED | Network or HTTP failure after retries |
How it works
The Ad Library server-renders the first page of results as JSON inside the HTML. The scraper:
- Sends a plain HTTP request with browser-like headers (no browser, so runs are cheap and fast)
- Solves Facebook's lightweight
__rd_verifychallenge if it is returned - Parses the embedded
search_results_connectionJSON - Follows the
end_cursorto the next page until the limits are reached - For advertiser pages, first resolves the page's numeric ID, then queries the Ad Library with
view_all_page_id
Pricing
Pay per event. You are charged for what lands in your dataset, not for rows:
| Event | When it is charged |
|---|---|
ad | Once per ad, in both output modes. A six-card carousel in "one row per creative card" mode is still one ad. |
ad-details | Once per ad whose EU transparency details were fetched successfully (only with includeEuTransparency). |
apify-actor-start | Once per run, by the platform. |
Error rows and page-summary rows are free. Set Maximum cost per run in the run options to cap spending; the actor stops cleanly when the cap is reached and you keep everything scraped up to that point.
Proxy
The Ad Library is a public transparency tool and works without a proxy in normal use. If you scrape very large volumes and hit blocks, add residential proxies:
"proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }