DEV Community

Cover image for I ran the same Nike search on the Meta Ad Library three times and got three different answers
Agnes the Dev
Agnes the Dev

Posted on

I ran the same Nike search on the Meta Ad Library three times and got three different answers

I built a Meta Ad Library scraper and my own export still confused me.

Three different things in it get called "country". So I ran one advertiser through three country filters on the same morning and joined the results on ad ID. 122 distinct ads came back. Zero of them were in all three runs.

The problem

A few days ago someone in r/FacebookAds asked what tool people use to research competitor Meta ads. They were checking the Meta Ad Library against a paid tool and seeing differences in the data, "especially around countries".

My guess is both were right and they were reading different fields. There are three:

  1. The search country. That's the filter you set before searching. country: "DE" returns ads that were shown in Germany. It says nothing about where else the ad ran.
  2. Target locations. Where the advertiser asked Meta to show the ad. Meta publishes this only for ads shown in the EU, and sometimes not even then.
  3. Reach by country. How many people in each EU country actually saw the ad. That's behind a second click on the Library card, and it's EU only too.

A tool that shows the first one and calls it targeting is wrong. A tool that shows the third one and calls it "countries the ad runs in" is also wrong, because an ad that only ran in the US has no entry there at all.

Three different meanings of country in one ad export

Limits before numbers. A Meta Ad Library export has no spend, no impressions, no clicks and no conversions. Meta publishes spend only for political and social issue ads, and Nike isn't that. A blank cell in my tables is Meta not publishing a number. It is not a zero.

What the three runs returned

Advertiser: Nike, Facebook page ID 15087023444. Same input three times, only the country changed:

{ "advertisers": ["Nike"], "country": "ALL", "activeStatus": "active", "maxAdsPerSearch": 50, "fetchEuDetails": true }
Enter fullscreen mode Exit fullscreen mode

Then "DE", then "US". All three started within eight seconds of each other on 2026-10-03 at 09:38 UTC. The cap of 50 means each run is the first 50 ads the Library returned for that filter, not a full census.

Filter Ads With EU reach With target locations Top landing domains Days running
ALL 50 5 5 nike.com 15, play.google.com 14, nike.com.br 10 3 to 544
DE 50 50 49 nike.com 49, nike.in 1 1 to 52
US 34 1 0 nike.com 33, nike.in 1 3 to 470

ALL is not the union of the other two. It returned a global mix: Brazil, Saudi Arabia, app store installs. Only 4 of its 50 ads show up in the DE run and 7 in the US run.

DE is fully published. Every ad in the DE run has EU reach, which makes sense, they all ran in the EU. 49 of 50 also have target locations and a payer name. 25 of the 50 have been running 30 days or more.

US is almost all blank. The US run stopped at 34 ads, under my cap of 50, because the Library had nothing more to return for the Nike page. Other Nike pages like Jordan aren't in it, since I searched one page. 33 of the 34 have null reach, null targeting and null payer.

Same ads, side by side:

adId ALL DE US Days running Countries in EU reach EU reach Target locations
1870984290951787 yes yes no 31 6 6,951,768 Italy, Netherlands, Spain, Poland, France, Germany
1382287983621487 no yes yes 3 33 24,396 null
987178264408636 no yes no 25 1 108,985 Germany
1016451784037642 yes no yes 470 0 null null
2088910168339416 yes no no 136 0 null null

The first row is the biggest ad in the DE sample. A catalog ad (the DE copy showed the Air Monarch IV), 31 days old, 6.95 million people reached across six EU countries, France first at 1.8 million. Target locations list the same six countries, ages 18 to 65, payer NIKE, Inc. That's what a fully disclosed EU ad looks like.

The second row is a festive sale ad that lands on nike.in. The day before it had 22,032 reach. This morning it had 24,396, spread over 33 country codes (the 27 EU members plus six French overseas regions), Portugal first. Target locations are still null. Reach and targeting are separate disclosures and Meta fills them separately.

Rows four and five are the ones that cause support tickets. Every EU field is null. Both are real, active, correctly scraped ads. Meta lists the 470 day one as active with a start date of 20 June 2025, and it has no EU disclosure at all. A tool with a "countries" column shows them empty and someone files a bug. There's nothing to fix.

The solution

I use my own Actor for this, the Meta Ad Library Scraper on Apify. It reads the public Ad Library with no login and returns one row per ad: copy, picture or video links, start date, daysRunning, landing domain, and for EU ads the reach by country, age and gender, targeting and payer.

Two things matter for this comparison. fetchEuDetails opens each EU card a second time to read reach and targeting, which the other Store listings I checked don't mention. And it keeps nulls as nulls, so you can tell "not published" apart from "zero".

It isn't the cheapest option. If you only need raw ad cards in bulk there are scrapers at a fraction of the price. This one is for when you need days running and EU reach in the same row.

Quick start (3 minutes)

  1. Open the Meta Ad Library Scraper and click Try for free.
  2. Put a Facebook page name, page URL or page ID in Advertisers. Pick a country. Set Maximum ads per search to 50.
  3. Click Start, then open the Ads table or download CSV or JSON.

Run it three times with three countries and you have my experiment for your competitor.

Code example

This runs all three filters and joins them on adId:

from apify_client import ApifyClient
import pandas as pd

client = ApifyClient("YOUR_APIFY_TOKEN")
frames = {}
for country in ["ALL", "DE", "US"]:
    run = client.actor("agnes.developer.queen/meta-ad-library-scraper").call(
        run_input={"advertisers": ["Nike"], "country": country,
                   "activeStatus": "active", "maxAdsPerSearch": 50,
                   "fetchEuDetails": True}
    )
    items = client.dataset(run["defaultDatasetId"]).list_items().items
    frames[country] = pd.DataFrame(items).set_index("adId")

ids = pd.DataFrame({c: f.index.to_series().notna() for c, f in frames.items()}).fillna(False)
print(ids.sum())               # ads per filter
print(ids.all(axis=1).sum())   # ads in all three, zero for me

for c, f in frames.items():
    print(c, f["euTotalReach"].isna().sum(), "of", len(f), "rows have no EU reach")
Enter fullscreen mode Exit fullscreen mode

Keep the JSON export too. euReachByCountry and euReachByAgeGender are nested objects, and CSV spreads them over dozens of columns, one per country and age band.

Use cases

  • See where a competitor's EU budget actually lands. Sort by euTotalReach and read euReachByCountry.
  • Find the ads they keep paying for. Set minDaysRunning to 30 and search their page.
  • Watch them weekly. Turn on onlyNewSinceLastRun and you get new ads, ads that crossed 30, 60 or 90 days, and ads that stopped.
  • Check another tool's country column against the source before you trust it.

Pricing

$0.005 per ad, plus $0.00005 per run. 100 ads cost $0.50. My three Nike runs delivered 134 ads, so $0.67.

Rows without a start date or a creative are delivered free with the reason. EU reach and targeting are included in the ad price. Apify gives every account $5 of free credit a month, which covers about 1,000 ads.

FAQ

Can I see how much Nike spends?

No. Meta only publishes spend for political and social issue ads. Reach is people, not dollars, and only EU people.

Why is reach blank on almost every US ad?

Meta publishes reach, targeting and payer only for ads shown in the EU. An ad that never ran there has nothing to read.

Does ALL give me every country's ads?

It gives you the first ads the Library returns with no country filter. With a cap, that's a sample. Run the countries you care about separately.

Do I need a Facebook account?

No. It reads the public Ad Library without logging in.

Get started

Open the Meta Ad Library Scraper, click Try for free and paste the input above. The free credit covers the three runs with plenty left.

Run on Apify

Which country mismatch have you hit when checking the same ad in two tools?

Top comments (0)