If you work in marketing, someone eventually asks: "What ads are our competitors running right now?" Since 2023, Google has had a public answer: the Ads Transparency Center (adstransparency.google.com). It lists the ads every verified advertiser runs across Search, YouTube, Shopping, Maps and Play, with first-shown and last-shown dates.
It's a great resource for one-off checks. This post is about turning it into data you can work with: a table of a competitor's creatives, the signals worth pulling out of it, and a daily alert when a competitor launches something new.
What you can see manually
Search an advertiser name or a domain, pick a region, and you get a grid of creatives. Click one and you see:
- the format (text, image or video) and a rendered preview,
- first shown and last shown dates,
- the advertiser's verified name and its location.
The UI is fine for looking at one competitor once. It gets slow when you want to compare five competitors, sort by run time, or notice new creatives the day they appear. There's no export button, and no official API for the ad library.
The signals that matter
Before collecting anything, decide what you'll do with it. Three signals carry most of the value:
- Days running. Advertisers stop paying for ads that don't work. A creative that has run for two years is probably a winner, so study its angle and offer.
- Active now. "Last shown" within a day or two means it's live. Comparing active ads across competitors tells you who's pushing hardest right now.
- New creatives. A batch of new ads usually means a launch, a promotion or a new market.
Getting it as data
Disclosure: we (SteadyScrape) built the tool below and get paid when it's used.
Google Ads Transparency Scraper (steady-scrape/google-ads-transparency) is an Apify Actor that returns Transparency Center data as one row per creative. You can query by domain (hubspot.com), by advertiser ID, or by company name (it finds the advertiser accounts for you). It doesn't use a browser, proxies or a login. The data comes from a licensed ads-data API, so runs don't break when Google changes the page.
Here's a script that pulls a competitor's US ads and summarizes them:
import os, statistics
from collections import Counter
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("steady-scrape/google-ads-transparency").call(run_input={
"targets": ["hubspot.com"],
"region": "US",
"maxAdsPerTarget": 40,
})
ads = [a for a in client.dataset(run["defaultDatasetId"]).iterate_items() if a.get("creativeId")]
print(Counter(a["format"] for a in ads))
print("active now:", sum(a["isActive"] for a in ads))
print("median days running:", statistics.median(a["daysRunning"] for a in ads))
for a in sorted(ads, key=lambda a: -a["daysRunning"])[:5]:
print(a["daysRunning"], a["format"], a["adUrl"])
In our test run, that returned 40 ads in about 14 seconds. Google estimated around 2,000 ads in total for the domain:
- 28 text, 11 video, 1 image, all 40 shown in the last 48 hours.
- Median days running: about 370. Half of the returned creatives had run for more than a year, and the longest for 1,828 days, roughly five years.
-
Not every ad was HubSpot's own. A domain query returns every ad that points to that domain, so a few came from partners. The
advertiserNamecolumn shows who paid. To see one company's own ads only, query its advertiser ID or use a name search.
Each row has advertiserName, advertiserId, verified, creativeId, format, previewImageUrl or previewUrl, firstShown, lastShown, daysRunning, isActive and adUrl (a link to the creative in the Transparency Center). The longest-running ads are the ones to open first.
Finding the advertiser by name
Don't know a competitor's domain or ID? Search by name and, optionally, pull ads for the best match in the same run:
run = client.actor("steady-scrape/google-ads-transparency").call(run_input={
"advertiserSearch": ["mailchimp"],
"fetchAdsForFoundAdvertisers": True,
"region": "US",
})
In our test, the "mailchimp" search returned one advertiser account with its related domains (mailchimp.com, mailchimpsites.com).
A daily new-ads alert
Monitor mode remembers which creatives you've already received and returns only new ones:
run = client.actor("steady-scrape/google-ads-transparency").call(run_input={
"targets": ["competitor-one.com", "competitor-two.com"],
"onlyNewAds": True,
"lastShownWithinDays": 2,
})
new_ads = list(client.dataset(run["defaultDatasetId"]).iterate_items())
Schedule that daily on Apify (or call it from cron), and send new_ads to Slack, email or a sheet. You can also connect it without code through Make, Zapier or n8n.
Honest limits
-
120 creatives per query. That's how much the Transparency Center returns, newest first. With format
all, the Actor queries text, image and video separately, which lifts this to 360 unique creatives per target. Very large advertisers are covered by their most recently shown ads. - No ad copy text yet, and no regional impression ranges. For text and image ads, the preview image shows the rendered copy.
- Old date ranges can come back empty. Google keeps fewer historical creatives.
- Counts like
totalAdsApproxare Google's rounded estimates.
What it costs
| Event | Price |
|---|---|
| Ad row | $0.001 ($1 per 1,000) |
| Advertiser from a name search | $0.003 |
| Monitor check, per target after the baseline run | $0.008 |
| New ad found in monitor mode | $0.003 |
Errors, "no ads" results and filtered-out ads are free, and Apify's $0.00005 start fee applies per run.
Examples:
- The 40-ad pull above costs $0.04.
- A full 360-ad snapshot of one competitor costs $0.36.
- A daily monitor on 5 competitors costs 5 × $0.008 × 30 = $1.20 a month in checks, plus $0.003 per new creative. Twenty new ads in a month adds $0.06.
You can set a maximum cost per run, and the Actor only fetches what fits in that limit.
The ad library has been public for years, but most teams still check it by hand, if they check it at all. Even a weekly table of "ads running over 180 days" for each competitor tells you a lot about what's working in your market.
Top comments (0)