Facebook Ads Library Scraper - Meta Ad Library Monitoring avatar

Facebook Ads Library Scraper - Meta Ad Library Monitoring

Pricing

from $9.00 / 1,000 results

Go to Apify Store
Facebook Ads Library Scraper - Meta Ad Library Monitoring

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

jy-labs

Maintained by Community

Actor stats

2

Bookmarked

342

Total users

55

Monthly active users

6 days ago

Last modified

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 carries advertiserAdCount — 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: true and 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 matchedBy and matchEvidence, 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. Set relevanceMode: "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, or ended against previous runs of the same search, with firstSeenDate, lastSeenDate and runsSeen. 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?

FeatureManual browsingThis scraper
Speed~2 min per ad100 ads in about 2 minutes
Multi-keywordOne search at a timeUp to 5 keywords per run
Data formatCopy-pasteStructured JSON/CSV/Excel
CTA linksClick each adExtracted automatically
Video URLsNot downloadableDirect video URLs
Match evidenceYour own judgementmatchedBy on every row
Unique accountsScroll and de-dupe by eyeOne row per advertiser, on request
What's newCompare against memoryonlyNewAds returns only the delta
SchedulingManual effortAutomate with Apify schedules
API accessNoneFull REST API
What you payYour time$10 per 1,000 results, nothing metered

Use cases

  • Scheduled competitor monitoring — Run daily or weekly and read the new rows: 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

  1. Go to the Meta Ad Library Scraper page on Apify Store.
  2. Click Try for free to open the Actor in Apify Console.
  3. Enter your search keywords (e.g., "Nike", "Adidas").
  4. (Optional) Enter a Facebook Page ID for each keyword to restrict results to a specific advertiser's page.
  5. Select your target country and filters.
  6. (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.
  7. Click Start and wait for the results.
  8. 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

ParameterTypeDescriptionDefault
keywordsarraySearch keywords — brand names, product names, or topics (max 5 per run). Pair with pageIds to target a specific advertiser's page.Required
pageIdsarray(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)-
countrystringCountry whose Ad Library is searched (US, GB, DE, JP, SG, KR, FR, and 15 more). Also selects the residential proxy exit country.US
adTypestring"all" or "political_and_issue_ads"all
activeStatusstring"all", "active", or "inactive"all
mediaTypestring"all", "image", "meme", "video", "none"all
startDateMinstringFilter ads started on or after this date (YYYY-MM-DD)-
startDateMaxstringFilter ads started on or before this date (YYYY-MM-DD)-
sortBystring"relevancy_monthly_grouped" (Most Recent) or "total_impressions" (Highest Impressions)relevancy_monthly_grouped
searchTypestringexact_phrase (default) or unordered. How Meta matches a multi-word keyword. See Keyword matching below.exact_phrase
maxResultsintegerMaximum results to collect per keyword (1–200). Counts ads by default; counts distinct advertisers when dedupeBy: "advertiser".100
dedupeBystringnone (default) or advertiser. advertiser returns one ad per advertiser and makes maxResults count accounts instead of ads. See Prospecting for advertisers below.none
trackChangesbooleanLabels 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
onlyNewAdsbooleanReturns 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
monitorKeystring(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.-
includeEndedAdsbooleanAlso 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
requestHandlerTimeoutSecsintegerCeiling 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
relevanceModestringbalanced (default), strict, or all. Controls how strictly a row must prove it matches your keyword. See Relevance below.balanced
includeUnverifiedMetaSearchResultsbooleanDeprecated. true is equivalent to relevanceMode: "balanced"; leaving it off no longer forces strict filtering. Use relevanceMode: "strict" instead.false
proxyobjectProxy 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)
blockMediaAssetsbooleanStops 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
debugModebooleanOpt 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"
}
  1. Replace empty pageIds with exact numeric Page IDs when you need advertiser-level targeting.
  2. Run the input once and inspect the Dataset and charge breakdown before enabling a schedule.
  3. Create a daily or weekly time-based schedule in Apify with the verified input.
  4. Filter each run's Dataset on isNew to 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:

  1. Go to Meta Ad Library and search for the advertiser by name.
  2. Click on the advertiser's name in the results to open their Ad Library page.
  3. 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:

  • pageIds is index-matched to keywords: pageIds[0] corresponds to keywords[0], pageIds[1] to keywords[1], and so on.
  • Leave an entry as "" (empty string) to use keyword-only search for that position.
  • If pageIds is shorter than keywords, 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 Death returns Liquid Death ads.
  • unordered — Meta's wide net: any of the words, anywhere, in any order. Liquid Death also 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 before maxResults is 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. Check matchEvidence and the creative before relying on it.

relevanceMode controls the policy:

  • balanced (default) — rows are ranked page_idvisible_textvisible_partialmeta_search, and the highest-evidence rows fill maxResults first. Lower-evidence rows are only used as backfill, and every row carries matchedBy so you can filter afterwards. Use this when you want the results Meta actually returns for your query.
  • strict — only page_id and visible_text rows 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), so strict can 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:

FieldMeaning
adStatusnew — 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.
isNewConvenience boolean for adStatus === "new".
firstSeenDateWhen this monitoring stream first returned the ad.
lastSeenDateThe previous sighting, before this run. null on a new ad.
runsSeenHow 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 ongoing
— so the delta is visible in the run list without opening anything.

Streams. 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 saysWhat to do
ran out of time, not out of advertisersraise requestHandlerTimeoutSecs and run it again
Meta had nothing further to loadthe search really is that small — widen the keyword
repeat rate for this keyword is too highwiden 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:

FieldTypeDescription
libraryIDstringUnique Meta Ad Library identifier
keywordstringSearch keyword submitted to Meta Ad Library
matchedBystringpage_id, visible_text, visible_partial, or meta_search — see Match evidence
matchEvidenceobjectVisible-match boolean plus matched extracted fields and normalized terms
pageIdstring|nullFacebook Page ID used to target a specific advertiser. null if no Page ID was provided.
brandstringAdvertiser name
advertiserAdCountintegerAds this advertiser was running among the ads scanned for the keyword. Present only with dedupeBy: "advertiser".
brandImgstringAdvertiser profile image URL
bodystringAd copy / text content
startDatestringWhen the ad started running (YYYY-MM-DD)
isActivebooleanWhether the ad is currently running
platformsarrayPlatforms — Facebook, Instagram, Messenger, Audience Network, Threads
imagesarrayImage URLs from the ad (carousel supported)
videosarrayVideo URLs with poster images
ctaLinkstringCall-to-action destination URL (decoded)
scrapeDatestringWhen the data was collected (ISO 8601)
adStatusstringnew, ongoing, or ended — only when change monitoring is on
isNewbooleanTrue when no previous run of this search returned the ad
firstSeenDatestringDate this monitoring stream first returned the ad
lastSeenDatestring|nullPrevious sighting, before this run. null on a newly discovered ad
runsSeenintegerRuns 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

CodeCountryCodeCountry
USUnited StatesGBUnited Kingdom
DEGermanyFRFrance
NLNetherlandsITItaly
ESSpainSGSingapore
CACanadaAUAustralia
JPJapanKRSouth Korea
INIndiaBRBrazil
MXMexicoTWTaiwan
HKHong KongTHThailand
VNVietnamIDIndonesia
PHPhilippinesMYMalaysia
ALLAll 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.

EventPrice
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 proxyIncluded — $0

What counts as a result depends on the options you set, and the price per result does not change:

SettingOne result isWhat you are billed for
defaultone adevery ad the run delivers
dedupeBy: "advertiser"one advertiserone row per account, not per creative
onlyNewAds: trueone ad that is new to this searchthe delta only — ads already reported are free
includeEndedAds: truealso one ad that stopped runningthose 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

  1. Use specific keywords — Brand names ("Nike", "Coca-Cola") work better than generic terms ("shoes", "drinks").
  2. Use a Page ID for exact advertiser targetingpageIds is optional, but if you want to collect ads from a specific advertiser with precision, provide their exact view_all_page_id value. 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).
  3. Start small — Test with maxResults: 5 to verify results before running large extractions.
  4. Filter by active status — Use "active" to focus on currently running ads.
  5. Use date filters — Narrow results to recent ads for the freshest insights.
  6. Try different countries — The same brand may run different ad creatives in different markets.

FAQ

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

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.