# Google Ads Transparency Center Scraper (`jmlp/google-ads-transparency-center-scraper`) Actor

Scrape every ad an advertiser runs on Google Search, YouTube, Display, Shopping, Maps and Play from the Google Ads Transparency Center. Extract creative IDs, formats, image URLs, YouTube video IDs, first & last shown dates and regions. Search by brand, domain or advertiser ID. No login or API key.

- **URL**: https://apify.com/jmlp/google-ads-transparency-center-scraper.md
- **Developed by:** [Mary Lou](https://apify.com/jmlp) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 2 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.15 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Google Ads Transparency Center Scraper

**See every ad your competitors run on Google — Search, YouTube, Display, Shopping, Maps and Play — and export it all to a spreadsheet.**

Google publishes every ad that every verified advertiser runs, in a public registry called the [Ads Transparency Center](https://adstransparency.google.com). It is genuinely all of it: the creatives, when each one started, when it last ran, and how many days it has been live. What Google does not give you is a way to get it *out* — the site shows you a few ads at a time, in a grid you have to scroll forever.

This scraper does that part. Give it a brand name and it hands you every ad that advertiser is running, as JSON, CSV or Excel.

**No Google account. No API key. No coding.**

***

### What you can use it for

- 🔍 **Competitor research** — see exactly which ads a rival is running right now, and which ones they have kept running for years (a long-running ad is a winning ad)
- 🎨 **Creative inspiration** — pull thousands of real ad images in one run and study what actually ships in your category
- 📺 **YouTube ad teardowns** — get the video IDs behind competitors' YouTube ads and watch the actual creatives
- 📅 **Campaign timelines** — first-shown and last-shown dates on every ad, so you can reconstruct when a campaign launched, paused or relaunched
- 🌍 **Market comparison** — run the same advertiser across several countries and see what they say differently in each

***

### How to use it — step by step

1. **Type a brand name** into *Brand names* — for example `Nike`.
2. *(Optional)* Add a **region** like `US` or `GB` to see only ads that ran there. Leave it empty for everywhere.
3. *(Optional)* Set **Max ads** to keep the first run small and cheap. Start with 100.
4. Click **Start**.
5. Open the **Output** tab and export to CSV, Excel or JSON.

> **Tip:** if a brand name is ambiguous, the run log lists every other advertiser that matched, with its ID. Copy the one you want into *Advertiser IDs* to pin it exactly. You can also paste a Transparency Center URL straight from your browser.

***

### Input options

| Setting | What it does |
| --- | --- |
| **Brand names** | Company names to look up, e.g. `Nike`, `Booking.com` |
| **Advertiser domains** | A verified domain like `nike.com` — more precise than a name |
| **Advertiser IDs** | The `AR…` id from a Transparency Center URL — the most precise input of all |
| **Transparency Center URLs** | Paste a URL straight from your browser |
| **Regions** | 2-letter country codes. Empty means every country |
| **Platform** | Limit to Search, YouTube, Display, Shopping, Maps or Play |
| **Ran on or after / Started on or before** | Keep only ads whose run overlaps your date window |
| **Max ads** | Stop after this many ads. Caps both time and cost |
| **Fetch region breakdown and all variants** | Adds every country each ad ran in — but costs one extra request per ad, so it is much slower |
| **Resolve YouTube video IDs** | Gets the actual YouTube video behind video ads — also one extra request per ad |

#### Example: one competitor

```json
{
  "searchTerms": ["Nike"],
  "maxAds": 500
}
```

#### Example: several competitors in one market

```json
{
  "searchTerms": ["Nike", "Adidas", "Puma"],
  "regions": ["GB"],
  "maxAds": 3000
}
```

#### Example: a cheap first test

```json
{
  "advertiserIds": ["AR16735076323512287233"],
  "regions": ["GB"],
  "maxAds": 25
}
```

***

### What you get

One row per ad. A real record:

```json
{
  "creative_id": "CR07202690838638362625",
  "advertiser_id": "AR18378488041124659201",
  "advertiser_name": "Nike Retail BV",
  "format": "image",
  "first_shown": "2023-11-16",
  "last_shown": "2026-08-23",
  "total_days_shown": 893,
  "image_url": "https://tpc.googlesyndication.com/archive/simgad/18213698926567542268",
  "youtube_video_id": null,
  "transparency_url": "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR07202690838638362625?region=anywhere"
}
```

The fields you will care about most:

| Field | What it tells you |
| --- | --- |
| **advertiser\_name** | The company as registered with Google (often the legal name, not the brand) |
| **format** | `image`, `video`, `shopping` or `text` |
| **first\_shown** / **last\_shown** | When the ad started and when it last ran |
| **total\_days\_shown** | How many days it actually ran — the single best signal of a winning ad |
| **image\_url** | A direct link to the ad image. Downloadable as-is |
| **youtube\_video\_id** | Open at `youtu.be/<id>` |
| **transparency\_url** | Opens the exact ad on Google, so you can verify any row |

Export as **JSON, CSV, Excel, XML or RSS** from the Output tab.

***

### Speed and limits

| Ads | Roughly |
| --- | --- |
| 100 | a few seconds |
| 1,000 | ~15 seconds |
| 5,000 | ~1 minute |
| 20,000+ | several minutes, and a proxy is essential |

Turning on *Fetch region breakdown* or *Resolve YouTube video IDs* changes this completely — those add one request **per ad**, so a 5,000-ad run goes from ~50 requests to ~5,000. Leave them off unless you need those fields.

Google blocks an IP that requests too much too fast. The scraper handles this: it rotates to a new proxy IP and carries on **from the page it had reached**, not from the beginning. If a job still cannot finish, it saves its progress and the run ends **successfully** with everything it did collect. Turn on **Continue from last run** and run it again to pick up only what was missed — that also works after a run that stopped because it hit **Max ads**.

***

### Cost

**From $0.15 per 1,000 ads, plus Apify platform usage.** Nothing for runs that
find nothing.

$0.17 per 1,000 on the Free and Bronze tiers, $0.15 on Silver and above. Every
other Google Ads Transparency scraper on the Store charges between $0.77 and
$1.20 per 1,000 ads.

This one keeps the two halves separate instead of rolling infrastructure into
one flat per-result price with a markup baked in: you pay per ad for the
scraping itself, and Apify bills the compute and proxy traffic at *your plan's
own rate* — the same credits your subscription already includes.

That second half is unusually small here. Unlike the Meta Ad Library, these
endpoints answer a bare POST and are happy with datacenter proxies, so there is
no residential traffic to pay for and a thousand ads take about ten seconds:

| What you scrape | Scraper charge | + Platform usage | Roughly |
| --- | --- | --- | --- |
| 100 ads (a quick look at a brand) | $0.017 | ~$0.001 | **~$0.02** |
| 1,000 ads | $0.17 | ~$0.01 | **~$0.18** |
| 10,000 ads | $1.70 | ~$0.06 | **~$1.76** |
| 100,000 ads | $17.00 | ~$0.60 | **~$17.60** |

Both columns are quoted at the **Free** plan rate, which is the most expensive
this gets — see below.

Usage figures are measured from real runs on the **Free** plan rate and rounded
up. The single biggest line in them is not compute or proxy traffic — it is the
dataset write itself, at $0.005 per 1,000 records. A measured 1,000-ad run
billed $0.003 of compute, $0.005 of dataset writes and $0.0002 of everything
else.

Turning on **Fetch region breakdown** or **Resolve YouTube video IDs** is the
one thing that changes this picture: they add a request *per ad*, so a 5,000-ad
run goes from ~50 requests to ~5,000. The scraper charge does not move — you
still pay per ad — but the run takes far longer and the compute half grows with
it.

#### It gets cheaper as your plan goes up

Both halves are discounted at higher tiers, so the same scrape costs less on a
bigger plan:

| Tier | Scraper charge | Compute |
| --- | --- | --- |
| Free / Bronze | $0.17 / 1,000 ads | $0.20 / CU |
| Silver | $0.15 / 1,000 ads | $0.16 / CU |
| Gold and above | $0.15 / 1,000 ads | $0.13 / CU |

Platform usage is drawn from the prepaid usage credit your plan already includes,
so on a paid plan a good part of it is money you've spent regardless. See
[Apify pricing](https://apify.com/pricing) for the full rate card.

#### Keeping it predictable

- **Set `Max ads`.** The scraper stops exactly there, so it caps runtime and cost
  in one number. Perfect for testing, and for putting a hard ceiling on any
  single run.
- **Set a maximum cost per run** (in the run options). The scraper reads it
  before it starts and stops as soon as it has delivered what your budget
  covers — it will never quietly keep scraping past what you're paying for.
- **Add a region.** One country is a small fraction of a global advertiser's
  total.
- **Use `Max pages per job`** while testing — one page is up to 100 ads.

A whole-archive scrape of a very large advertiser is genuinely large — Nike
Retail BV alone reports ~100,000 ads — so start with a `Max ads` limit if you
just want a snapshot.

***

### Frequently asked questions

**Is this legal?**
It scrapes a public registry that Google publishes deliberately, under EU and US transparency rules. There is no login, no paywall and no personal data involved — these are commercial advertisements.

**Do I need a Google account or an API key?**
No. Neither.

**Does Google have an official API for this?**
No. The Ads Transparency Center is a website with no public API, which is why this actor exists.

**Why did my brand name find the wrong company?**
Several companies can register similar names, and Google's own name search only matches on prefix — for `Nike` it returns ten small advertisers called Nikena, nikey, Nikesh and so on, and never the real one. The scraper therefore also looks up the brand's own domain and takes whichever candidate is larger, which is how `Nike` lands on Nike Retail BV and its ~100,000 ads. Whatever it picks, the run log lists the other matches with their advertiser IDs — copy the right one into *Advertiser IDs* to pin it.

**Why do I get fewer ads than the number Google shows?**
For large advertisers Google reports a rounded range, not a count — "8,000 to 9,000" rather than 8,437. For small advertisers the number is exact and the scraper matches it precisely.

**Can I get the headline and description text of search ads?**
No. Google returns text ads as a JavaScript program that draws the ad rather than as text, so the copy cannot be extracted without running a browser. Image URLs and YouTube video IDs are available.

**Why does the same ad appear under several regions?**
Because it ran in several. If you scrape multiple regions, each ad is still written **once** — duplicates are removed automatically.

**What does "days shown" mean?**
The number of days the ad actually ran. It is usually less than the gap between first and last shown, because campaigns pause and resume.

**Can I schedule this?**
Yes. Use Apify Schedules to run it daily or weekly and track how a competitor's creative mix changes.

**Do I need a proxy?**
For small runs, no. For anything over a few thousand ads, yes — leave the default Apify Proxy on. Datacenter proxies are fine; you do not need residential. Measured without one: about 5,400 ads from a single IP before Google blocked it, and that block was still in place 90 minutes later. With the proxy on, the scraper just moves to a new IP and carries on from the page it had reached.

**Can I run several competitors at once?**
Yes. Put them all in *Brand names*. They are scraped in parallel and **Max ads** applies across the whole run.

***

### Support

Found a bug or need a field that is not there? Open an issue on the actor's page and include your run ID.

# Actor input Schema

## `searchTerms` (type: `array`):

Brand or company names to look up, e.g. 'Nike' or 'Booking.com'. Each is resolved to the largest advertiser Google matches, by name and by the brand's own domain - searching 'nike' finds Nike Retail BV and its ~100,000 ads rather than the 34-ad Bulgarian advertiser called 'Nikena', which is all Google's name search returns. The log lists the other matches and their advertiser ids, so you can pin a different one if the wrong company was picked.

## `domains` (type: `array`):

Verified advertiser domains, e.g. nike.com. More precise than a brand name when several companies share one.

## `domain` (type: `string`):

A single advertiser domain.

## `advertiserIds` (type: `array`):

Advertiser ids like AR16735076323512287233 - the AR value in a Transparency Center URL. The most precise input: nothing has to be resolved, so nothing can resolve to the wrong company.

## `advertiserId` (type: `string`):

A single advertiser id. Not needed when a brand name, domain or URL is given above.

## `transparencyUrls` (type: `array`):

Full advertiser URLs copied from the browser, e.g. https://adstransparency.google.com/advertiser/AR16735076323512287233?region=GB. Each URL brings its own region and platform; the settings below fill in whatever a URL does not carry.

## `transparencyUrl` (type: `string`):

Paste a single advertiser URL. Its region and platform take precedence over the settings below.

## `regions` (type: `array`):

2-letter country codes, e.g. US, GB, DE. Leave empty to cover every region an ad ran in. Listing several scrapes each one as a separate job, which is also how this actor parallelises - but note the same ad appears under every region it ran in, so the totals overlap. Duplicates are removed before anything is written.

## `region` (type: `string`):

A single 2-letter country code, used when Regions above is empty.

## `platform` (type: `string`):

Limit results to one Google surface. Leave as 'All platforms' for everything. Note that an ad can run on several surfaces at once, so the per-platform counts add up to more than the advertiser's total.

## `minDate` (type: `string`):

YYYY-MM-DD. Keeps ads that were still running on or after this date. Google's endpoint has no date filter, so this is applied to the dates in the results - it reduces what you are charged for, not how much is fetched, though the run does stop early once it is clearly past the window.

## `maxDate` (type: `string`):

YYYY-MM-DD. Keeps ads that first ran on or before this date. Combined with the field above it selects ads whose run overlaps the window.

## `proxyConfiguration` (type: `object`):

Google blocks an IP that sends too many requests, so a proxy is strongly recommended for large runs. Datacenter is usually enough here - unlike Meta, these endpoints do not require residential IPs.

## `maxConcurrency` (type: `integer`):

How many jobs run in parallel. A job is one advertiser in one region. Google paginates a single advertiser strictly sequentially and offers no way to split that walk, so this only speeds up runs that cover several advertisers or several regions - one advertiser in one region always runs on one worker.

## `maxAds` (type: `integer`):

Stop after this many unique ads, counted globally across every advertiser and region. The crawl terminates as soon as the cap is hit, so this caps both runtime and cost. Clear it to scrape everything with no limit.

## `maxPages` (type: `integer`):

Safety cap while testing, applied to each job separately. One page is up to 100 ads. Empty = all.

## `fetchAdDetails` (type: `boolean`):

Adds the list of regions each ad ran in, the date it last ran in each, and every creative variant. This costs ONE EXTRA REQUEST PER AD: an advertiser that takes 100 requests to list takes 10,000 with this on. Leave it off unless you need the region detail, and pair it with a low Max ads while testing.

## `resolveMedia` (type: `boolean`):

For video ads, fetches the preview and extracts the YouTube video id so you can open the actual video. Also one extra request per ad, and it only succeeds for ads that really are YouTube videos - display video ads have no public URL.

## `pageSize` (type: `integer`):

Ads per request. Google rejects anything above 100 by returning an empty response - measured - so values are clamped to 100, which is also the fastest setting.

## `delayMs` (type: `integer`):

Milliseconds to sleep between requests. Google answers a page in roughly 300-800 ms, which is the real floor. Raise this if the log reports blocked jobs.

## `raw` (type: `boolean`):

Push records without the null-filling and key ordering of the fixed output schema. Field names are the same.

## `proxyRotations` (type: `integer`):

If a job is refused, the actor mints a new proxy session and retries this many times.

## `resume` (type: `boolean`):

Save progress every ~30s so a run that gets migrated or restarted by the platform picks up where it stopped, without duplicating anything. No effect on fresh runs.

## `continueFromLastRun` (type: `boolean`):

If your previous run with the same input was interrupted, blocked, or stopped because it hit Max ads, scrape only what it missed - picking up from the exact page it had reached. Note the earlier ads stay in THAT run's dataset, so this run's dataset contains only the remainder, and remember to raise Max ads or the budget will already be spent. Leave off to always get a complete dataset in one place.

## `impersonate` (type: `string`):

Which browser TLS fingerprint to present. This matters more than it looks: Google refuses several of them outright, serving an 'unusual traffic' page on the very first request. chrome131, chrome124, firefox144 and safari260 were all measured working; chrome136 and newer Chrome builds were all refused. If a run is blocked immediately, the actor already retries the others automatically - set this only to pin one.

## `proxySessionId` (type: `string`):

Pin a specific exit IP, for reproducing a run while debugging. Ignored when scraping several jobs or with Max concurrency above 1.

## Actor input object example

```json
{
  "searchTerms": [
    "Nike"
  ],
  "platform": "all",
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 4,
  "maxAds": 1000,
  "fetchAdDetails": false,
  "resolveMedia": false,
  "pageSize": 100,
  "delayMs": 500,
  "raw": false,
  "proxyRotations": 3,
  "resume": true,
  "continueFromLastRun": false,
  "impersonate": "chrome131"
}
```

# Actor output Schema

## `ads` (type: `string`):

One record per unique ad creative: the advertiser, the format, when it first and last ran, how many days it ran, and a link to the image, the preview or the YouTube video.

## `summary` (type: `string`):

How many ads were collected versus how many Google reported, which advertisers and regions were covered, the breakdown by ad format, and whether anything was cut short by a block or the run's maximum cost.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchTerms": [
        "Nike"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    },
    "maxAds": 1000
};

// Run the Actor and wait for it to finish
const run = await client.actor("jmlp/google-ads-transparency-center-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchTerms": ["Nike"],
    "proxyConfiguration": { "useApifyProxy": True },
    "maxAds": 1000,
}

# Run the Actor and wait for it to finish
run = client.actor("jmlp/google-ads-transparency-center-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchTerms": [
    "Nike"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxAds": 1000
}' |
apify call jmlp/google-ads-transparency-center-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jmlp/google-ads-transparency-center-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/CSyznqHk70ppa9kxC/builds/J5jeRvD4iIhgixB03/openapi.json
