DEV Community

Upendra Kumar
Upendra Kumar

Posted on

How to Pull a Competitor's Google Ads into Python (Google Ads Transparency Center)

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, image or video)
  • firstShown, lastShown, daysShown and isActive
  • 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"
Enter fullscreen mode Exit fullscreen mode

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}')
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

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)