Google publishes the ads of verified advertisers in the Ads Transparency Center. That covers Search, YouTube, Shopping, Maps and Play, with the first and last date each ad was shown.
It's great for browsing, but it's a website: one advertiser at a time, no export button. If you want to analyze competitors' ads — which creatives they keep paying for, which landing pages they push, what their YouTube ads look like — you want the data as JSON or a spreadsheet.
This tutorial gets a competitor's ads into Python in about 15 lines.
Disclosure: I built the tool used below, the Google Ads Transparency Scraper on Apify. It's pay-per-result ($1.50 per 1,000 ads), and Apify's free plan includes monthly credit, so you can follow along without paying anything.
What you get for each ad
- advertiser name and ID, and the ad format (
text,imageorvideo) -
firstShown,lastShown,daysShownandisActive - headlines, descriptions and landing pages, where Google publishes them
- preview images, YouTube video links, duration and channel
- a link to the ad in the Transparency Center
- optionally: the countries the ad ran in, and impressions and targeting for ads shown in the EU
Step 1: Get an Apify API token
Create a free account at apify.com, then copy your token from Settings → API & Integrations and put it in an environment variable:
export APIFY_TOKEN="your-token-here"
Step 2: Fetch a competitor's ads
Apify's run-sync-get-dataset-items endpoint starts the scraper, waits for it to finish and returns the results, all in one HTTP request. So all you need is requests:
import os
import requests
URL = ("https://api.apify.com/v2/acts/upendra_11~google-ads-transparency-scraper"
"/run-sync-get-dataset-items")
ads = requests.post(
URL,
headers={"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"},
json={
"targets": ["shopify.com"], # a website, a brand name or an advertiser ID
"region": "US",
"maxAdsPerTarget": 20,
},
timeout=300,
).json()
print(len(ads), "ads")
for ad in ads[:6]:
first_line = (ad.get("headlines") or ["(picture of a text ad)"])[0]
print(f'{ad["format"]:5} {ad["daysShown"]:>4} days {first_line}')
Output from a run on 10 November 2026:
20 ads
text 1549 days (picture of a text ad)
text 282 days (picture of a text ad)
text 456 days (picture of a text ad)
text 6 days (picture of a text ad)
text 70 days (picture of a text ad)
text 6 days (picture of a text ad)
Why "picture of a text ad"? For most Search text ads, Google's archive stores an image of the ad, not the text. You get that image in previewImageUrl. The written copy (headlines, descriptions) comes from image and video ads, and from text ads that also run on Maps.
Step 3: Find the ads they keep paying for
Advertisers switch off ads that don't perform. An ad that has been running for months is probably a winner. Sort the active ones by daysShown:
winners = sorted(
(ad for ad in ads if ad["isActive"]),
key=lambda ad: ad["daysShown"],
reverse=True,
)
for ad in winners[:3]:
print(ad["daysShown"], ad["previewImageUrl"], ad["adUrl"])
In this run, Shopify's top three active Search ads had been running for more than 1,500 days. That's over four years of paying for the same creative, so it's worth a close look if you compete with them.
Only YouTube ads from the last 30 days
Filter by platform and date and use a brand name instead of a website:
json={
"targets": ["Canva"],
"platform": "youtube", # google_search, youtube, google_shopping, google_maps, google_play
"shownInLastDays": 30,
"maxAdsPerTarget": 10,
}
Each video ad comes with videoUrls (YouTube links you can open or download for analysis). Where Google publishes them, you also get videoDuration, youtubeChannel and the ad copy. Here are two of Canva's YouTube ads from the same day:
https://www.youtube.com/watch?v=bqfs1hrV7gU 0:18 "Edit each layer" 70 days, active
https://www.youtube.com/watch?v=9jLPQ7b7CaU (no copy published) 51 days, active
Bonus: which websites advertise at all?
If you're an agency qualifying leads, switch mode to advertisers and pass a list of websites:
json={"targets": ["nike.com", "allbirds.com", "my-local-bakery.com"], "mode": "advertisers", "region": "US"}
You get one row per website: runsGoogleAds, an ad count range (adCountMin / adCountMax), latestAdLastShown and whether the advertiser is active. That's $2 per 1,000 websites, which is handy when you have a list of 5,000 prospects.
Monitor competitors every day
Add "onlyNewAds": true and a "monitorName" to the input, then create a daily schedule in Apify. The first run saves a baseline, and every later run returns only ads you haven't seen before. Add a Slack, email, Google Sheets or webhook integration, and new competitor ads arrive on their own.
Good to know
-
EU-only stats: impressions and targeting data exist only for ads shown in the EU, because the Digital Services Act requires it. Turn them on with
"includeRegionStats": true. It's slower (about 60 ads a minute) because each ad needs its own request. - Ranges, not exact numbers: ad counts and impressions are ranges, because that's how Google publishes them.
- Rate limits: Google limits how fast the Transparency Center can be read. The scraper paces itself. If Google temporarily limits it, it returns what it found so far and asks you to run again later. It never tries to get around those limits.
- Not affiliated with Google. It reads only the public ad library.
Wrap-up
Fifteen lines of Python turn the Transparency Center into a dataset you can sort, filter and monitor. The scraper's page has all the input options.
What would you build with competitor ad data? I'm happy to take feature requests in the comments.
Top comments (0)