Facebook Ads Library Scraper - Meta Ad Library Monitoring
Pricing
from $9.00 / 1,000 results
Facebook Ads Library Scraper - Meta Ad Library Monitoring
Monitor Facebook and Instagram competitor ads on a schedule. Every result comes back labelled new, ongoing, or ended against your previous runs, so you read what changed instead of the same list again. Up to 5 keywords, exact Page ID targeting, creatives, copy and CTA links.
Pricing
from $9.00 / 1,000 results
Rating
5.0
(1)
Developer
jy-labs
Maintained by CommunityActor stats
2
Bookmarked
342
Total users
55
Monthly active users
6 days ago
Last modified
Categories
Share
Facebook Ads Library Scraper
Monitor Facebook and Instagram competitor ads from the Meta Ad Library with up to 5 keyword or advertiser Page ID searches in one run. Put it on a daily or weekly Apify schedule and ask for only the ads that are new since your last run, so a scheduled run is a change feed rather than the same list again. Prospecting for accounts instead of creatives? Switch on one result per advertiser and Max Results counts distinct advertisers — 200 results means 200 different accounts. Export creatives, videos, images, ad copy, CTA links, and more from 22+ countries without writing code.
$10 per 1,000 results, platform usage and residential proxy included. No separate compute or proxy line on your invoice. You are billed per row delivered — so with one result per advertiser you are paying per account, and with only-new-ads you are paying for the delta, not the list.
What is Meta Ad Library Scraper?
Meta Ad Library Scraper is an Apify Actor that collects current ads from Meta's Ad Library for one-off research or recurring scheduled snapshots. Instead of manually browsing and copy-pasting ad data, you can run a repeatable batch for the competitors and countries you track.
Meta Ad Library is Facebook's public database of all ads running across Facebook, Instagram, Messenger, and Audience Network. This scraper turns that public data into structured, exportable datasets you can use for competitor analysis, market research, and creative inspiration.
What can this scraper do?
- One result per advertiser (new) — Set
dedupeBy: "advertiser"and Max Results counts distinct advertisers instead of ads, so a single heavy spender can no longer fill your result set with its own creatives. The ad kept for each advertiser is its best keyword match, and every row carriesadvertiserAdCount— how many ads that account was running. Built for prospecting: finding who is advertising an offer, not collecting the offer's creatives. - Only what changed (new) — Set
onlyNewAds: trueand a run delivers only the ads this search has not returned before. A run that finds nothing new returns nothing and costs you nothing in result charges, which is what makes a daily schedule cheap to keep. - Multi-keyword search — Search up to 5 keywords (brand names, products, topics) in a single run. No need to run the scraper multiple times.
- Transparent match evidence — Every row carries
matchedByandmatchEvidence, so you can see whether the keyword was actually observed in the advertiser name, ad copy or CTA link, or whether Meta simply ranked the ad for your query. Rows with visible evidence fill your result quota first. SetrelevanceMode: "strict"to emit and pay for only those, or"all"to take Meta's ordering untouched. - Target specific advertisers by Page ID — Pair any keyword with a Facebook Page ID to restrict results to that exact advertiser's page. Ideal when multiple brands share a similar name and you only want one.
- Change monitoring across runs — Every result is labelled
new,ongoing, orendedagainst previous runs of the same search, withfirstSeenDate,lastSeenDateandrunsSeen. A scheduled run tells you which creatives your competitors launched or retired since the last one, instead of handing you the same list again. The delta is computed when the run executes; this is not real-time alerting. - 22+ countries supported — Target ads in specific countries: US, UK, Germany, Japan, South Korea, and more. Or search all countries at once.
- Advanced filters — Filter by active/inactive status, media type (image, video, meme), date range, and sort order.
- Complete ad data extraction — Get ad creatives (images & videos), ad copy, CTA destination URLs, advertiser info, platform targeting, start dates, and Library IDs.
- Automatic deduplication — The same ad never appears twice, even when scrolling loads it repeatedly. Turn on
dedupeBy: "advertiser"to also collapse repeat advertisers down to one row each. - Export anywhere — Download results as JSON, CSV, or Excel. Connect to Google Sheets, Airtable, Zapier, Make, or access via REST API.
Why use this scraper instead of browsing manually?
| Feature | Manual browsing | This scraper |
|---|---|---|
| Speed | ~2 min per ad | 100 ads in about 2 minutes |
| Multi-keyword | One search at a time | Up to 5 keywords per run |
| Data format | Copy-paste | Structured JSON/CSV/Excel |
| CTA links | Click each ad | Extracted automatically |
| Video URLs | Not downloadable | Direct video URLs |
| Match evidence | Your own judgement | matchedBy on every row |
| Unique accounts | Scroll and de-dupe by eye | One row per advertiser, on request |
| What's new | Compare against memory | onlyNewAds returns only the delta |
| Scheduling | Manual effort | Automate with Apify schedules |
| API access | None | Full REST API |
| What you pay | Your time | $10 per 1,000 results, nothing metered |
Use cases
- Scheduled competitor monitoring — Run daily or weekly and read the
newrows: the creatives that appeared since your last run. - Competitor analysis — Review how competitors advertise on Facebook and Instagram, including their creatives, messaging, and CTA strategies.
- Market research — Discover what types of ads are running in your industry across different countries.
- Creative inspiration — Build a swipe file of high-performing ad creatives for your team.
- Brand monitoring — Track how your brand (or clients' brands) appear in ads across Meta platforms.
- Prospecting for advertisers — Search the offer or angle you sell against and get a list of the accounts running it, one row each, ranked by how heavily they advertise.
- Agency pitches — Gather ad intelligence to support client proposals and competitive analyses.
How to scrape Meta Ad Library
- Go to the Meta Ad Library Scraper page on Apify Store.
- Click Try for free to open the Actor in Apify Console.
- Enter your search keywords (e.g., "Nike", "Adidas").
- (Optional) Enter a Facebook Page ID for each keyword to restrict results to a specific advertiser's page.
- Select your target country and filters.
- (Optional) Set One result per advertiser if you want a list of accounts rather than creatives, and Only return ads that are new since the last run if you are putting this on a schedule.
- Click Start and wait for the results.
- Download your data as JSON, CSV, or Excel — or connect to Google Sheets, Airtable, Zapier, or Make.
No coding, no API keys, no setup required.
Input parameters
| Parameter | Type | Description | Default |
|---|---|---|---|
keywords | array | Search keywords — brand names, product names, or topics (max 5 per run). Pair with pageIds to target a specific advertiser's page. | Required |
pageIds | array | (Optional) Numeric Facebook Page IDs matched by index to keywords. Provide the exact view_all_page_id value to restrict a search to that advertiser. Leave an entry blank to use keyword-only search at that position. Non-numeric Page IDs are rejected before browser work starts. (max 5) | - |
country | string | Country whose Ad Library is searched (US, GB, DE, JP, SG, KR, FR, and 15 more). Also selects the residential proxy exit country. | US |
adType | string | "all" or "political_and_issue_ads" | all |
activeStatus | string | "all", "active", or "inactive" | all |
mediaType | string | "all", "image", "meme", "video", "none" | all |
startDateMin | string | Filter ads started on or after this date (YYYY-MM-DD) | - |
startDateMax | string | Filter ads started on or before this date (YYYY-MM-DD) | - |
sortBy | string | "relevancy_monthly_grouped" (Most Recent) or "total_impressions" (Highest Impressions) | relevancy_monthly_grouped |
searchType | string | exact_phrase (default) or unordered. How Meta matches a multi-word keyword. See Keyword matching below. | exact_phrase |
maxResults | integer | Maximum results to collect per keyword (1–200). Counts ads by default; counts distinct advertisers when dedupeBy: "advertiser". | 100 |
dedupeBy | string | none (default) or advertiser. advertiser returns one ad per advertiser and makes maxResults count accounts instead of ads. See Prospecting for advertisers below. | none |
trackChanges | boolean | Labels every ad against what previous runs of the same search returned, adding adStatus, isNew, firstSeenDate, lastSeenDate and runsSeen. Adds fields only — nothing is removed or filtered. See Change monitoring below. | true |
onlyNewAds | boolean | Returns only ads this search has not returned before, instead of the full list every run. Turns change tracking on automatically. The first run returns everything, because everything is new. | false |
monitorKey | string | (Optional) Names the monitoring stream. Leave empty and the stream is identified by the search itself, so a saved task on a schedule lines up automatically. Set it to keep separate histories for separate clients from one input. | - |
includeEndedAds | boolean | Also emits a row per tracked ad that has stopped appearing (adStatus: "ended"). Off by default because those rows are charged like any other result. | false |
requestHandlerTimeoutSecs | integer | Ceiling on per-keyword processing time across navigation, readiness checks, scrolling, and extraction (60–600 seconds). A fast keyword still finishes fast; lower it only to cap how long any single keyword may take. Large requests need the headroom. | 600 |
relevanceMode | string | balanced (default), strict, or all. Controls how strictly a row must prove it matches your keyword. See Relevance below. | balanced |
includeUnverifiedMetaSearchResults | boolean | Deprecated. true is equivalent to relevanceMode: "balanced"; leaving it off no longer forces strict filtering. Use relevanceMode: "strict" instead. | false |
proxy | object | Proxy configuration. Leave it alone. Residential is required and its cost is covered by the Actor, not billed to you — see Pricing below. | Apify Proxy (RESIDENTIAL) |
blockMediaAssets | boolean | Stops the browser downloading ad images, video and fonts it never reads. Every media URL is still extracted, so the output is identical — a measured 100-ad run moved 3.4 MB instead of 222 MB. Turn off only if a search stops returning results. | true |
debugMode | boolean | Opt in to storing PAGE_SCREENSHOT for troubleshooting. Disabled by default because screenshots contain visible search-page content. | false |
Quick validation input (maxResults: 5)
{"keywords": ["Nike"],"pageIds": [""],"country": "US","activeStatus": "active","mediaType": "all","maxResults": 5}
Scheduled bulk competitor monitoring (maxResults: 100 per keyword)
After the quick validation succeeds, use a larger input for recurring collection:
{"keywords": ["Nike", "Adidas", "Puma", "Under Armour", "New Balance"],"pageIds": ["", "", "", "", ""],"country": "US","activeStatus": "active","mediaType": "all","sortBy": "relevancy_monthly_grouped","maxResults": 100,"relevanceMode": "balanced"}
- Replace empty
pageIdswith exact numeric Page IDs when you need advertiser-level targeting. - Run the input once and inspect the Dataset and charge breakdown before enabling a schedule.
- Create a daily or weekly time-based schedule in Apify with the verified input.
- Filter each run's Dataset on
isNewto see only what changed since the previous run.
maxResults applies per keyword. Five keywords with maxResults: 100 can emit up to 500 Dataset items — about $5.04 — before accounting for the ads actually available, relevance filtering and deduplication. Check the run detail page once before enabling a schedule.
This is scheduled/polled collection, not real-time alerts or guaranteed complete Meta Ad Library coverage. Cross-run change tracking is computed when a run executes, not continuously, and the Actor does not measure spend, ROAS, or conversions.
Using Page IDs for precise advertiser targeting
pageIds is an optional field. You do not need to provide it for the scraper to work — keyword-only search is the default behavior.
However, if you want to collect ads from a specific advertiser with precision, you can provide the view_all_page_id value of that advertiser's Facebook Page. When a Page ID is set for a keyword, the scraper restricts the search to that exact page, which significantly improves the accuracy of collected data — eliminating ads from other brands that happen to match the same keyword.
How to find a view_all_page_id:
- Go to Meta Ad Library and search for the advertiser by name.
- Click on the advertiser's name in the results to open their Ad Library page.
- The URL will contain
view_all_page_id=XXXXXXXXX— copy that number.
Alternatively, open the advertiser's Facebook Page and look for the numeric ID in the page URL or page source.
Rules:
pageIdsis index-matched tokeywords:pageIds[0]corresponds tokeywords[0],pageIds[1]tokeywords[1], and so on.- Leave an entry as
""(empty string) to use keyword-only search for that position. - If
pageIdsis shorter thankeywords, the remaining keywords use keyword-only search. - Only provide the numeric ID value (e.g.
"123456789"). Blank entries select keyword-only search; non-numeric Page IDs are rejected before browser work starts.
Example — mixing keyword-only and Page ID search:
{"keywords": ["Nike", "Adidas", "Puma"],"pageIds": ["", "123456789", ""]}
In this example, "Nike" and "Puma" use keyword-only search, while "Adidas" is restricted to the page with ID 123456789. Only "Adidas"'s own ads will be collected for that keyword.
Keyword matching
searchType decides what you are asking Meta for.
exact_phrase(default) — the words must appear together, in order.Liquid Deathreturns Liquid Death ads.unordered— Meta's wide net: any of the words, anywhere, in any order.Liquid Deathalso returns an ad that says "liquid" in one line and "death" in another. Use it only when you want maximum recall and intend to filter afterwards.
Single-word keywords behave identically in both modes.
unordered was the only behaviour before 2026-08-24, and it is why a two-word brand search could come back full of unrelated ads. If you have a saved task from before that date, it now searches the phrase; set searchType: "unordered" to restore the old result set.
Understanding match evidence
Meta Ad Library decides which cards belong to a keyword search and can return ads whose visible advertiser, body, and CTA do not contain the submitted keyword. This Actor does not describe those rows as exact brand matches.
matchedBy: "page_id"— the request used an explicit Page ID filter. This is the strongest advertiser-level evidence.matchedBy: "visible_text"— every normalized keyword term was observed across the extracted advertiser name, ad body, or CTA URL. These rows are prioritized beforemaxResultsis applied.matchedBy: "visible_partial"— some, but not all, query terms appear in the extracted visible fields.matchedBy: "meta_search"— Meta returned the row, but the extracted visible fields do not prove the keyword match. CheckmatchEvidenceand the creative before relying on it.
relevanceMode controls the policy:
balanced(default) — rows are rankedpage_id→visible_text→visible_partial→meta_search, and the highest-evidence rows fillmaxResultsfirst. Lower-evidence rows are only used as backfill, and every row carriesmatchedByso you can filter afterwards. Use this when you want the results Meta actually returns for your query.strict— onlypage_idandvisible_textrows are emitted or charged. Highest precision. Note that Meta's search often returns ads whose visible fields never repeat the keyword (slogan-only brand creatives), sostrictcan legitimately return very few rows for such keywords; the run logs a[yield-alarm]warning when that happens.all— Meta's own ranking order, unfiltered and un-reranked.
Explicit Page ID rows are always eligible because the request supplies advertiser-level evidence.
matchEvidence.phraseMatch says the words were seen together and in order in one field, rather than scattered across the ad; it orders results within an evidence tier but never removes them. matchEvidence.visibleMatch, fields, and terms expose the observed evidence. Common English stopwords such as no, the, and of are removed from relevance terms; meaningful exact tokens such as AI remain eligible. A creative image may contain relevant text that is not available in the extracted text fields, so visibleMatch: false means “not proven by extracted fields,” not necessarily “definitely unrelated.”
Empty-result diagnostics
Some keywords genuinely have no ads, and a run that reports that is doing its job. A zero-row keyword is treated as a normal completed search when the search page rendered, when Meta showed explicit no-results text, when ad data arrived over GraphQL, or when candidates were loaded and then removed by the strict relevance policy.
It is only treated as a failure when the page never rendered at all — a challenge, a verification wall, a JavaScript-disabled shell, or a rate-limited response. In that case the Actor rotates the page and proxy route while budget allows, and if every attempt still comes back with nothing, SUMMARY.status becomes completed_partial and the keyword summary carries the reason rather than claiming a clean scrape.
Change monitoring — what a scheduled run tells you
Running this once gives you a list of ads. Running it on a schedule should tell
you what your competitors changed, and that is what trackChanges does. It is
on by default.
Every result carries its own history against the same search:
| Field | Meaning |
|---|---|
adStatus | new — no earlier run of this search returned it. ongoing — an earlier run already reported it. ended — it was tracked and has now been absent for two consecutive runs. |
isNew | Convenience boolean for adStatus === "new". |
firstSeenDate | When this monitoring stream first returned the ad. |
lastSeenDate | The previous sighting, before this run. null on a new ad. |
runsSeen | How many runs have returned it — a proxy for how long a creative has been in rotation. |
So a Monday run that finds 100 ads, 6 of them new, tells you six creatives
launched since Friday. Filter on isNew and you have the week's competitive
brief. The run's status message says it too —
112 ads collected from 5 keyword(s); 6 new, 106 ongoingStreams. History is grouped by what you searched: keywords, page IDs,
country, ad type, status and media type. Two runs of the same saved task line up
automatically, and keyword order does not matter. Set monitorKey only if you
want several separate histories from one input — one per client, for example.
Where the history lives. In a key-value store named meta-ad-monitor inside
your own Apify account. It is your data, it is not shared, and deleting that
store resets the stream — the next run will report every ad as new.
Ads that stopped running. Set includeEndedAds: true and each run also emits
a row for every tracked ad that has disappeared, carrying the advertiser name and
how long it ran. This is off by default because those rows are charged like any
other result. An ad is only called ended after two consecutive absences:
maxResults caps what a run sees and Meta reorders its own ranking, so a single
absence is not evidence that a campaign stopped.
Overlapping runs. Two runs of the same stream running at the same time each fold their own sightings into the history rather than overwriting each other. If you schedule this, still leave the interval longer than a run takes — a 200-result advertiser run scrolls for about four and a half minutes — so the delta you get each run is the delta since the previous one finished.
When history is not updated. A run that fails or completes only partially
saw less of the Ad Library than it meant to, so it is never folded into the
history. Otherwise a broken run would retire ads that are still running — and,
with includeEndedAds on, bill you for a false ending.
Turning it off. Set trackChanges: false. The five fields disappear and the
output is exactly what it was before. If the history store cannot be read, the
run still returns your ads — unlabelled rather than mislabelled — and says so in
the log.
Prospecting for advertisers instead of collecting ads
Sometimes the ad is not the point. If you are looking for accounts running a particular offer, a result set where half the rows are the same three advertisers is half a result set — and you paid for all of it.
Set dedupeBy: "advertiser" and the meaning of maxResults changes: it counts
distinct advertisers, not ads. Ask for 200 and you get up to 200 different
accounts.
{"keywords": ["free consultation"],"country": "US","dedupeBy": "advertiser","maxResults": 200}
Three things follow from that switch:
- The ad you keep is the advertiser's best match, not whichever one Meta returned first. Deduplication runs after relevance ranking, so a row represents an account by its most relevant creative.
- Every row carries
advertiserAdCount— how many ads that advertiser was running among the ads scanned for the keyword. That is the "who is spending hardest" column, and it is the number you would otherwise count by hand. - Deduplication spans the whole run. An advertiser found under two of your keywords is still one row, because it is still one account.
The scraper loads more cards per keyword in this mode, since it has to look past
the repeats to fill your quota — up to 400 ads per keyword. Measured on
free consultation at maxResults: 200: 423 ads scanned, 200 distinct
advertisers delivered, 19 scroll rounds, no rate limiting.
If a run delivers fewer rows than you asked for it says why, and the three reasons are different problems:
| The log line says | What to do |
|---|---|
| ran out of time, not out of advertisers | raise requestHandlerTimeoutSecs and run it again |
| Meta had nothing further to load | the search really is that small — widen the keyword |
| repeat rate for this keyword is too high | widen the keyword, or lower Max Results |
That distinction matters. A 200-advertiser request on a 300-second ceiling stops at about 191 because the clock ran out, not because the keyword was thin — and the message used to say the opposite. The default ceiling is now 600 seconds, so a 200-result request fits without tuning.
The Console dataset has an Advertisers view laid out for this: advertiser, ads running, destination link, best matching ad.
Do not combine this with a Page ID. A Page ID search already restricts results to a single advertiser, so one result per advertiser leaves exactly one row — correct, but almost certainly not what you wanted. Use a Page ID to collect one advertiser's creatives, and advertiser mode to discover advertisers.
Getting only what changed
trackChanges labels each ad new or ongoing. It does not remove anything, so
two runs of the same search return the same rows with different labels. If you
want the second run to return the delta rather than the list, that is a
separate switch:
{"keywords": ["Liquid Death"],"country": "US","onlyNewAds": true}
- The first run returns everything, because on the first run everything genuinely is new. From the second run on you get only what appeared since.
- A run that finds nothing new returns nothing and costs you nothing in result charges.
- Filtered-out ads are still recorded in your history, so they are never re-reported as new later.
- If the history cannot be read, the run delivers every row rather than silently returning none, and says so in the log.
Combine it with a schedule and the Actor becomes a change feed: a Monday run that returns four rows means four creatives launched over the weekend.
What data can you extract from Meta Ad Library?
Each ad in the output includes:
| Field | Type | Description |
|---|---|---|
libraryID | string | Unique Meta Ad Library identifier |
keyword | string | Search keyword submitted to Meta Ad Library |
matchedBy | string | page_id, visible_text, visible_partial, or meta_search — see Match evidence |
matchEvidence | object | Visible-match boolean plus matched extracted fields and normalized terms |
pageId | string|null | Facebook Page ID used to target a specific advertiser. null if no Page ID was provided. |
brand | string | Advertiser name |
advertiserAdCount | integer | Ads this advertiser was running among the ads scanned for the keyword. Present only with dedupeBy: "advertiser". |
brandImg | string | Advertiser profile image URL |
body | string | Ad copy / text content |
startDate | string | When the ad started running (YYYY-MM-DD) |
isActive | boolean | Whether the ad is currently running |
platforms | array | Platforms — Facebook, Instagram, Messenger, Audience Network, Threads |
images | array | Image URLs from the ad (carousel supported) |
videos | array | Video URLs with poster images |
ctaLink | string | Call-to-action destination URL (decoded) |
scrapeDate | string | When the data was collected (ISO 8601) |
adStatus | string | new, ongoing, or ended — only when change monitoring is on |
isNew | boolean | True when no previous run of this search returned the ad |
firstSeenDate | string | Date this monitoring stream first returned the ad |
lastSeenDate | string|null | Previous sighting, before this run. null on a newly discovered ad |
runsSeen | integer | Runs of this stream that have returned the ad, including this one |
Example output
{"libraryID": "123456789012345","keyword": "Nike","matchedBy": "visible_text","matchEvidence": {"visibleMatch": true,"fields": ["brand", "body", "ctaLink"],"terms": ["nike"]},"pageId": null,"brand": "Nike","brandImg": "https://scontent.xx.fbcdn.net/v/...","body": "Just Do It. Shop the latest Nike Air Max collection.","startDate": "2024-01-15","isActive": true,"platforms": ["Facebook", "Instagram"],"images": [{ "url": "https://scontent.xx.fbcdn.net/v/..." }],"videos": [{ "url": "https://video-iad6-1.xx.fbcdn.net/o1/v/...", "posterUrl": "https://scontent.xx.fbcdn.net/v/..." }],"ctaLink": "https://nike.com/air-max","scrapeDate": "2024-01-20T10:30:00.000Z"}
Integrations
Connect your scraped data to the tools you already use:
- Google Sheets — Auto-sync results to a spreadsheet
- Airtable — Build a searchable ad database
- Zapier — Trigger workflows when new ads are found
- Make (Integromat) — Automate complex data pipelines
- Webhooks — Send data to your own backend in real time
- REST API — Access results programmatically from any language
- Slack / Email — Get notifications when scraping completes
You can also schedule this scraper to run daily or weekly to track ad changes over time.
Supported countries
| Code | Country | Code | Country |
|---|---|---|---|
| US | United States | GB | United Kingdom |
| DE | Germany | FR | France |
| NL | Netherlands | IT | Italy |
| ES | Spain | SG | Singapore |
| CA | Canada | AU | Australia |
| JP | Japan | KR | South Korea |
| IN | India | BR | Brazil |
| MX | Mexico | TW | Taiwan |
| HK | Hong Kong | TH | Thailand |
| VN | Vietnam | ID | Indonesia |
| PH | Philippines | MY | Malaysia |
| ALL | All Countries |
How much does it cost to scrape Meta Ad Library?
$10 per 1,000 results, and that is the whole bill. Platform usage — compute, storage and the residential proxy this Actor needs — is paid by the Actor, not added to your invoice. The price on the pricing tab is what you pay.
| Event | Price |
|---|---|
| Actor start | $0.02 per GB of allocated memory |
| Successfully emitted ad row | $0.010 per Dataset item, falling to $0.009 on higher Apify subscription tiers |
| Platform usage and proxy | Included — $0 |
What counts as a result depends on the options you set, and the price per result does not change:
| Setting | One result is | What you are billed for |
|---|---|---|
| default | one ad | every ad the run delivers |
dedupeBy: "advertiser" | one advertiser | one row per account, not per creative |
onlyNewAds: true | one ad that is new to this search | the delta only — ads already reported are free |
includeEndedAds: true | also one ad that stopped running | those rows too, like any other result |
Deduplicating by advertiser costs you nothing extra and gives you more per row: the Actor reads further past the repeats to fill your quota, and that extra reading is platform usage, which is on us. Measured on a 100-result run, the advertiser option added 4.6% to the Actor's own cost and nothing at all to the invoice.
The default 2048 MB memory allocation produces two 1 GB Actor-start events, or $0.04 in start-event charges. So a single 100-ad run costs about $1.04, and running five keywords at 100 ads each costs about $5.04. Result pricing is tiered, so the exact item price can differ by account. The run detail page in Apify Console is the source of truth for the final charge.
If no rows are emitted, there is no Dataset-item event charge. Actor-start charges still apply. That is what makes onlyNewAds cheap to schedule: a run that finds nothing new bills only the start events, no matter how much of the Ad Library it had to read to be sure.
Why proxy is included
Meta Ad Library cannot be paginated from datacenter IPs. Facebook rate-limits the GraphQL requests that load each additional page (error code 1675004), so the first screen renders and then nothing more arrives — measured directly, the same 100-ad search returned 25 ads over a datacenter proxy and 100 over residential, with all 17 pagination requests rate-limited.
Residential proxy is therefore not optional here, and charging you for something you cannot turn off would be a fee in disguise. Instead the Actor keeps its own transfer small enough to absorb it: media bytes are never downloaded, only their URLs are read, which took a 100-ad run from 222 MB to 3.4 MB of proxy traffic with byte-identical output. Leave the proxy setting on its default.
Tips for best results
- Use specific keywords — Brand names ("Nike", "Coca-Cola") work better than generic terms ("shoes", "drinks").
- Use a Page ID for exact advertiser targeting —
pageIdsis optional, but if you want to collect ads from a specific advertiser with precision, provide their exactview_all_page_idvalue. This restricts results to that advertiser's page only, significantly improving data accuracy. Find the Page ID by searching the advertiser in Meta Ad Library and copying the numeric ID from the URL (view_all_page_id=XXXXXXXXX). - Start small — Test with
maxResults: 5to verify results before running large extractions. - Filter by active status — Use "active" to focus on currently running ads.
- Use date filters — Narrow results to recent ads for the freshest insights.
- Try different countries — The same brand may run different ad creatives in different markets.
FAQ
Is this legal?
Meta Ad Library exposes public advertising data for transparency, but whether a particular collection or use is permitted depends on the applicable terms, laws, jurisdiction, and use case. You are responsible for reviewing Meta's terms and obtaining legal advice when appropriate.
Will Meta block the scraper?
This scraper includes built-in proxy support and anti-detection measures, and it uses Apify Proxy with the RESIDENTIAL group by default, at no cost to you. Do not switch to datacenter proxies: in a direct comparison the same search returned 25 ads on datacenter against 100 on residential, because Facebook rate-limited every pagination request.
How long does it take?
Measured on the Apify platform, per keyword:
- 10 ads: under 2 minutes
- 100 ads: about 2 minutes
- 3 keywords × 30 ads: under 2 minutes total
Keywords are processed one after another, so plan roughly 2 minutes per keyword and set requestHandlerTimeoutSecs accordingly. Meta's own responsiveness varies, so treat these as typical rather than guaranteed.
Can I schedule automatic runs?
Yes. Apify supports scheduled runs — set the scraper to run daily, weekly, or at any custom interval to monitor ad changes over time.
Does it extract ad spend or impressions data?
Meta only makes spend and impression data publicly available for political and issue ads. For regular commercial ads, this data is not available in the Ad Library.
Support
- Issues & feature requests: Actor issues
- Apify documentation: docs.apify.com
- Apify community: discord.gg/apify
Legal notice
This Actor is designed to collect publicly visible data from Meta Ad Library without accessing private accounts. Users are responsible for ensuring that collection and use of extracted data comply with Meta's terms and applicable laws and regulations.