The Meta Ad Library shows every active ad running on Facebook, Instagram and the rest of Meta's apps, but the website only lets you scroll through it one card at a time. This guide shows how to scrape Facebook ads from the library with Python, using a Facebook Ad Library scraper on Apify, and get one clean row per ad: text, headline, images, videos, landing page, call to action, dates, platforms and the advertiser's Page. You don't need a Facebook account or a Meta developer app.
Quick start
pip install apify-client
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("automly/facebook-ad-library-scraper").call(run_input={
"searchTerms": ["meal kit"],
"country": "US",
"activeStatus": "active",
"maxAds": 100,
"outputFormat": "clean",
})
for ad in client.dataset(run["defaultDatasetId"]).iterate_items():
print(ad["pageName"], ad["startDate"], ad["activeDays"], ad["linkUrl"])
Get your token from the Apify Console under Settings > API & Integrations. That's the whole thing. The rest of this post covers the inputs, the output, and when Meta's own API is the better choice.
Why not the official Meta Ad Library API?
Meta does have an Ad Library API. Before you can call it you have to confirm your identity and location at Facebook.com/ID (Meta says this "can take a few days"), create a Meta for Developers account and set up an app.
The bigger issue is what it returns. Per Meta's own page, the API covers ads about social issues, elections or politics from anywhere in the world over the past 7 years, plus ads of any type delivered to the EU or UK during the past year. The EU part comes from the Digital Services Act: in 2023 Meta expanded the library to archive every ad shown to people in the EU for a year. The ads_archive reference spells out the catch: ads that did not reach any EU location only come back if they are political or issue ads. So a US-only ad for a skincare brand isn't in the API at all.
The fields are mostly text lists (bodies, link titles, captions, descriptions), dates, the Page, platforms and an ad_snapshot_url that opens the ad. Spend, impressions and demographics are only filled for political and issue ads; reach and targeting only for EU/UK ads. There is no field for image or video files, the CTA button or the landing page URL.
| Option | Access needed | Cost | What you get |
|---|---|---|---|
| Ad Library website | None. Anyone can view and search it | Free | All active ads across Meta apps, one at a time, no export |
| Meta Ad Library API | ID confirmation, developer account, app | No fee listed by Meta | Political/issue ads worldwide (7 years); other ads only if delivered to the EU/UK (past year); text fields and a snapshot link |
| Facebook Ads Scraper | Free Apify account | $0.70 per 1,000 ads on the Free plan, less on paid plans | Any ad the library shows, in any country, with media links, landing page, CTA and Page data |
If you only research political ads, or only care about EU ads, the official API is a good fit. For commercial ads elsewhere, a Facebook ads library scraper is the practical route.
Step 1: Pick your input
There are three ways in, and you can mix them in one run.
Keywords. searchTerms takes a list, and each term runs as its own search. Set searchType to keyword_exact_phrase if you want the words in order.
Advertiser Pages. Put Page links in urls to get every ad one advertiser runs:
run_input = {
"urls": [{"url": "https://www.facebook.com/nike"}],
"country": "ALL",
"activeStatus": "all",
"maxAds": 1000,
"outputFormat": "clean",
}
Ad Library search links. If you already set up a search on the Ad Library website, copy the link from the address bar and paste it into urls. Its own filters are used as they are. A single ad link (https://www.facebook.com/ads/library/?id=...) works too.
Step 2: Set the filters
-
country: a two-letter code (US,GB,DE), a country name, orALL. Defaults toUS. -
activeStatus:active(default),inactiveorall. -
mediaType:all,image,video,memeornone(text only). -
adType:all, or a special category such aspolitical_and_issue_adsorhousing_ads. -
startDateMin/startDateMax: only ads that started in that window, asYYYY-MM-DD. -
maxAds: cap per search term or URL. Default 100, maximum 10,000.
Always pass "outputFormat": "clean" from code. Runs that leave it out get the older field layout so existing integrations don't break.
Step 3: Read the output
Here is a real row from a netflix keyword search, trimmed (media links shortened, emoji removed from the ad text):
{
"adArchiveId": "742409480628500",
"adLibraryUrl": "https://www.facebook.com/ads/library/?id=742409480628500",
"isActive": true,
"startDate": "2023-03-21",
"endDate": null,
"activeDays": 1145,
"adFormat": "video",
"mediaType": "video",
"bodyText": "Wow! These are the most comfortable headphones for relaxing...",
"headline": "Get Your CozyBand Today",
"linkUrl": "https://www.cozyband.com/products/cozyband",
"linkDomain": "cozyband.com",
"ctaText": "Shop now",
"videos": [{"hdUrl": "https://video.xx.fbcdn.net/...mp4", "sdUrl": "...", "previewImageUrl": "..."}],
"platforms": ["facebook", "instagram", "audience_network", "messenger"],
"pageName": "CozyBand",
"pageUrl": "https://www.facebook.com/getcozyband/",
"pageCategory": "Electronics",
"searchTerm": "netflix"
}
Image ads carry imageUrls, and carousels come with a cards list where each card has its own headline, link, CTA and image. activeDays is the column I sort by first. An ad that has kept running for months is usually one that pays for itself.
Step 4: Save it to CSV
import csv
cols = ["pageName", "headline", "bodyText", "linkUrl", "ctaText",
"startDate", "activeDays", "mediaType", "adLibraryUrl"]
with open("ads.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=cols, extrasaction="ignore")
writer.writeheader()
for ad in client.dataset(run["defaultDatasetId"]).iterate_items():
writer.writerow(ad)
You can also skip the code and download the dataset from the Console as CSV, Excel, JSON, HTML or XML.
What else can you scrape?
There are three optional switches, all off by default. Set includeAdDetails to true and ads shown in the EU or UK come back with their reach, targeted ages, gender and places, and who paid for them. Political and issue ads get their audience breakdown and the spend range Meta publishes.
includePageTransparency adds when the advertiser's Page was created, its past names and how many ads it runs. includeAdvertiserDetails adds the advertiser's Instagram account and follower count, verified badge and About text.
A note on spend, since people ask: Meta only publishes spend ranges for political and issue ads. For ordinary commercial ads no tool reading the Ad Library can give you a real spend figure.
Scaling up
Add more terms or Page links to the same run, and raise maxAds up to 10,000 per search. For ongoing competitor tracking, save your input as a task in the Console and put it on a schedule. Each run can push its dataset to Google Sheets, Slack or a webhook, and n8n, Make and Zapier can start runs too.
No-code option: the Apify Console
- Open the actor page and click Try for free.
- Fill in Search Terms or paste links into Ad Library URLs.
- Pick Country, Ad Status and Max Ads per Search. Output format is already set to Clean.
- Click Start, then export the results.
What it costs
You pay per ad saved: $0.70 per 1,000 ads on the Free plan, with lower rates on paid Apify plans. Each run also has a $0.05 start fee at the default settings. Every Apify account gets $5 of free usage each month, which covers roughly 7,000 ads.
Responsible use
Stick to public ad data and a clear purpose for collecting it. Advertiser Pages and political ad disclaimers can contain personal data, which laws like GDPR and CCPA may protect, so follow Meta's terms and check with a lawyer if you're unsure. The scraper isn't affiliated with Meta.
Links
- Facebook Ads Scraper on Apify
- Apify Python client docs
- Meta Ad Library
- Meta Ad Library API and its field reference
If you want your competitors' Facebook and Instagram ads in a spreadsheet today, try the Facebook Ads Scraper on Apify. The free monthly credit covers a few thousand ads, which is plenty for a first look at three or four brands.
Top comments (0)