Advertisers turn off ads that lose money. So an ad that a brand has kept running for months is, more often than not, one that works for them. Media buyers and creative strategists use this as a quick read on a competitor: their longest-running active ads show the offers, hooks, formats and landing pages that survived testing.
The Meta Ad Library shows every active ad, with the date each one started, but it has no sort by run time and no export. This guide pulls one advertiser's ads and sorts them.
Disclosure: I'm Aurelix, and I built the Facebook Ad Library Scraper used below. It is a paid tool, priced per ad returned; the current price is on its tool page.
1. Find the advertiser's page id
Open the Meta Ad Library, search the brand, and click its page. The URL now contains view_all_page_id=.... Either copy that number, or paste the whole URL into the tool: a normal facebook.com/brand link is treated as a keyword search, not as the page.
2. The input
{
"queries": ["page:20409006880"],
"country": "ALL",
"activeStatus": "active",
"maxResultsPerQuery": 500
}
page:<id>(or just the digits) returns that advertiser's ads only.activeStatus: "active"keeps ads running now; use"all"to include stopped ones and see how long past ads ran.countryis where the ads ran;ALLcovers every country.
No Facebook account, cookies or developer token are used; the tool reads the public Ad Library pages.
3. Sort by run time in Python
pip install apify-client
import csv
import os
from datetime import datetime, timezone
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("aurelix/facebook-ad-library-scraper").call(run_input={
"queries": ["page:20409006880"],
"country": "ALL",
"activeStatus": "active",
"maxResultsPerQuery": 500,
})
ads = list(client.dataset(run["defaultDatasetId"]).iterate_items())
now = datetime.now(timezone.utc)
for a in ads:
start = datetime.fromisoformat(a["start_date"].replace("Z", "+00:00"))
a["days_running"] = (now - start).days
ads.sort(key=lambda a: a["days_running"], reverse=True)
with open("longest-running-ads.csv", "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=["days_running", "page_name", "display_format", "title", "ad_text",
"cta_text", "link_url", "platforms", "ad_library_url"],
extrasaction="ignore")
w.writeheader()
for a in ads:
w.writerow({**a, "platforms": ", ".join(a.get("platforms") or [])})
for a in ads[:10]:
print(f"{a['days_running']:>5} days {a['display_format']:<8} {(a.get('ad_text') or '')[:70]}")
Read the top of longest-running-ads.csv for:
- Offers and hooks that keep coming back in
ad_textandtitle. - Formats in
display_format(for example IMAGE, VIDEO, or DCO for dynamic creative) and the creative files inimage_urlsandvideo_urls. - Landing pages in
link_url: the URL's path and UTM tags often name the campaign.
Meta groups near-identical versions of an ad; each version comes back as its own row with variants_count.
Real output
Active ads from a run of the tool's example input (a keyword search and a pasted Ad Library search for "shopify"), with the fields used above:
| advertiser | format | platforms | ad text (start) |
|---|---|---|---|
| Shopify | DCO | facebook, instagram, audience_network, messenger | The Summer'24 Edition is here. Explore 150+ new product updates... |
| Intuit Mailchimp | DCO | facebook, instagram, threads | Run an ecomm store? Our integrations help automate emails and texts... |
| Perplexity AI | DCO | facebook, instagram, threads | 20+ AI models run your Shopify store... |
| wetracked.io | IMAGE | facebook, instagram, messenger, threads | Stop losing 60% of valuable ad conversion data... |
At the time of that run, the Shopify ad had been active for more than two years, the others for weeks to a few months. Each row also has ad_id, page_id, start_date, end_date, cta_text, link_url, image and video URLs and the Ad Library link.
Make it a weekly check
Add "incremental": true with a stateKey per competitor and schedule it weekly: each run then returns only ads you have not seen, so you see what they launched. Add slackWebhookUrl for a digest in Slack. For a ready-made weekly digest across ads, pages, prices and jobs, see Monitor your competitors' Facebook and Instagram ads with Python.
Limits
- Spend and impressions are empty for most ads: Meta publishes them only for political and issue ads (excluded here) and some EU-delivered ads.
start_dateis the start date the Ad Library shows for the ad.- The data comes from the public Ad Library web pages, not Meta's official API. Meta rate-limits the Ad Library from time to time; a query that stops early is marked PARTIAL in the run's status and you pay only for the ads returned.