# Ad Library Scraper — Google, Meta & LinkedIn (`mediocre_interest/ads-library-scraper`) Actor

Scrape ads from the Google Ads Transparency Center, Meta Ad Library (Facebook and Instagram) and LinkedIn Ad Library in one run. Find competitor ads by keyword, brand or domain and get ad copy, creatives, images, video URLs, impressions and run dates. Export to JSON, CSV or Excel.

- **URL**: https://apify.com/mediocre\_interest/ads-library-scraper.md
- **Developed by:** [Mediocre\_Interest](https://apify.com/mediocre_interest) (community)
- **Categories:** Automation, Lead generation, Social media
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $12.00 / 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

## Ad Library Scraper — Google, Meta & LinkedIn

**Ad Library Scraper** lets you scrape ads from the **Google Ads Transparency Center**, the **Meta Ad Library** (Facebook and Instagram) and the **LinkedIn Ad Library** in a single run, and returns everything in one consistent dataset. Search competitor ads by keyword, brand name or domain, or paste ad library URLs directly, then export to JSON, CSV or Excel.

No login, no API keys and no ad account. Every ad in these three libraries is published by the platforms themselves for transparency — this Actor just makes all of it queryable in one place.

### What does the Ad Library Scraper do?

Most ad scrapers cover one platform. This one covers three, and normalizes them into a single row shape so you can compare a brand's **Facebook creative** against its **Google search ads** and its **LinkedIn campaigns** without writing any glue code.

- 🔍 **Competitor ad research** — see exactly what a brand is running today and how long each ad has been live
- 🖼️ **Ad creative extraction** — ad copy, headlines, images, video URLs and destination links
- 📊 **Category monitoring** — search one keyword across three ad libraries in one run
- 🌍 **Country comparison** — filter by market and see where a campaign is actually delivering
- 💡 **Swipe files** — export creative and copy for inspiration or analysis
- 📈 **Dashboards and alerts** — schedule runs and push results into your own tooling

### What ad data can you extract?

|                          | Google Ads Transparency Center | Meta Ad Library     | LinkedIn Ad Library   |
| ------------------------ | ------------------------------ | ------------------- | --------------------- |
| Search by keyword        | matches advertisers/domains    | ✅ searches ad copy | ✅ searches ad copy   |
| Search by advertiser     | ✅                             | ✅                  | ✅                    |
| Search by domain         | ✅ native filter               | keyword match       | keyword match         |
| Ad copy & headline       | —                              | ✅                  | ✅                    |
| Images                   | ✅ most ads                    | ✅                  | ✅                    |
| Video URLs               | browser preview link           | ✅ direct URLs      | —                     |
| Destination URL          | —                              | ✅                  | ✅ most ads           |
| First / last shown dates | ✅                             | ✅                  | some ads              |
| Impressions              | —                              | political ads only  | some ads (as a range) |
| Country delivery split   | —                              | —                   | some ads              |

The table above is what these platforms actually publish, not what we wish they published. Google's Transparency Center is metadata-first: you get the advertiser, the dates, the archived creative image and a link to the ad, but Google does not publish ad text over the wire. Meta is the richest source for ad copy and media. LinkedIn is the only one of the three that publishes a per-country delivery split alongside an impressions range for ordinary commercial ads rather than just political ones — though only for a minority of them (roughly 10–20% in our sampling).

### Who is it for?

- Performance marketers running competitor teardowns before a launch.
- Agencies building pitch decks that need real creative from a prospect's category.
- Growth and SEO teams tracking which messages a rival is putting money behind.
- Market researchers sizing how crowded a category is.
- Developers who want ad library data behind a plain HTTP API instead of three different scraping projects.

### How to scrape ads: step by step

1. Click **Try for free** to open the Actor.
2. Choose your **platforms** — Google, Meta, LinkedIn, or all three.
3. Add what you want to search: **search terms**, **advertisers**, **domains**, or paste **direct URLs** from any of the three ad libraries.
4. Set **max ads per query** to control the size of the run.
5. Pick your **countries** (defaults to `US`).
6. Click **Start** and watch results appear in the dataset tab.
7. **Export** as JSON, CSV, Excel, or pull them through the Apify API.

To run it on a schedule, open the **Schedules** tab and pick a cadence — daily competitor monitoring is the most common setup.

### How to scrape Google ads from the Ads Transparency Center

Google's Ads Transparency Center indexes ads by **advertiser**, not by ad text, so the way you ask matters more here than on the other two platforms.

The most reliable route is a domain. Put `nike.com` in `domains` and the Actor queries Google's native domain filter and goes straight to the verified advertiser. Searching the brand name instead goes through Google's suggestion endpoint, which ranks by spelling similarity — a search for `Nike` can surface `nikey` and `Nikesh` above Nike, Inc. The Actor re-ranks whole-word matches to the top to compensate, but a domain is still the sharper tool.

You can also paste a Transparency Center advertiser URL (`https://adstransparency.google.com/advertiser/AR…`) into `startUrls`, or pass the bare `AR…` advertiser ID in `advertisers` for an exact match.

Each Google row carries the advertiser, first and last shown dates, the archived creative image and a direct link to view the ad. Google's own region filter applies, so an advertiser with no ads in your selected `countries` returns nothing even if it advertises heavily elsewhere — the run log tells you when that happens.

### How to scrape Facebook and Instagram ads from the Meta Ad Library

Meta is the richest of the three for creative work. Its Ad Library searches actual ad copy, so keyword searches behave the way you would expect, and each row comes back with the headline, body copy, call-to-action text, the destination URL, image URLs and direct video URLs — plus which surfaces the ad ran on (`FACEBOOK`, `INSTAGRAM`, `AUDIENCE_NETWORK`, `MESSENGER`).

Search a brand with `advertisers`, a topic with `searchTerms`, or a numeric Facebook page ID for an exact page match. Meta has no domain filter, so a domain is searched as a keyword there.

Meta runs need **residential proxies** — see the proxy section below. Meta blocks datacenter IP ranges outright.

### How to scrape LinkedIn ads from the LinkedIn Ad Library

LinkedIn is the best source for B2B ad research, and the only platform here that publishes a per-country delivery breakdown for ordinary commercial ads.

The Actor searches by keyword, by LinkedIn company ID, or by account owner name, then opens each ad's detail page to collect the full untruncated ad copy, the creative images, the destination URL, the run dates, an impressions range like `1k-5k` and the country percentage split. That detail pass is what makes LinkedIn rows rich, and it costs roughly one request per ad — set `linkedinFetchDetails` to `false` for a faster, cheaper, shallower pass that reads the search cards only.

LinkedIn matches multi-word phrases loosely, so single specific keywords work far better here: `cybersecurity` returns much sharper results than `enterprise cybersecurity software`.

### Input example

```json
{
    "platforms": ["google", "meta", "linkedin"],
    "domains": ["nike.com"],
    "searchTerms": ["running shoes"],
    "countries": ["US"],
    "maxAdsPerQuery": 100,
    "matchMode": "phrase",
    "filterIrrelevant": true
}
```

#### Input options

**What to scrape**

| Field         | Description                                                                           |
| ------------- | ------------------------------------------------------------------------------------- |
| `platforms`   | Which ad libraries to search: `google`, `meta`, `linkedin`                            |
| `searchTerms` | Keywords to search for                                                                |
| `advertisers` | Brand names, Google advertiser IDs, Meta page IDs or LinkedIn company IDs             |
| `domains`     | Advertiser websites, e.g. `nike.com` — the most reliable way to find a specific brand |
| `startUrls`   | Paste ad library URLs to scrape directly                                              |

**Relevance and filters**

| Field                 | Description                                                           |
| --------------------- | --------------------------------------------------------------------- |
| `matchMode`           | `phrase` (default, precise) or `broad` (more results, noisier)        |
| `filterIrrelevant`    | Drop ads that don't actually mention your search terms. On by default |
| `countries`           | ISO-2 country codes, e.g. `["US", "GB"]`                              |
| `dateFrom` / `dateTo` | Limit to ads delivered in a date range                                |

**Per-platform settings**

| Field                         | Description                                                |
| ----------------------------- | ---------------------------------------------------------- |
| `googleMaxAdvertisersPerTerm` | How many matching advertisers a Google keyword fans out to |
| `metaActiveStatus`            | `all`, `active` or `inactive`                              |
| `metaMediaType`               | `all`, `image`, `video` or `meme`                          |
| `metaAdType`                  | Restrict to a category such as `political_and_issue_ads`   |
| `linkedinFetchDetails`        | Full detail per ad (default), or a faster, lighter pass    |

**Limits and output**

| Field                 | Description                                                      |
| --------------------- | ---------------------------------------------------------------- |
| `maxAdsPerQuery`      | Cap per search term, advertiser or domain                        |
| `maxRequestsPerCrawl` | Overall safety limit                                             |
| `includeRaw`          | Keep each platform's original payload alongside the clean fields |
| `proxyConfiguration`  | Proxy settings — see below                                       |

### Output example

Every ad comes back in the same shape, whichever ad library it came from. Here is a real Meta result:

```json
{
    "platform": "meta",
    "adId": "1520310666349109",
    "adUrl": "https://www.facebook.com/ads/library/?id=1520310666349109",
    "advertiser": {
        "id": "15087023444",
        "name": "Nike",
        "url": "https://www.facebook.com/nike/",
        "domain": "nike.com"
    },
    "creative": {
        "title": "Find Nike Near You",
        "body": "Add a retro touch to any fit.",
        "caption": "nike.com",
        "ctaText": "Shop now",
        "linkUrl": "https://www.nike.com/w/womens-summer-essentials-lifestyle...",
        "format": "image",
        "imageUrls": ["https://scontent.fbom12-2.fna.fbcdn.net/v/t39.35426-6/680632406_..."],
        "videoUrls": []
    },
    "publisherPlatforms": ["FACEBOOK", "INSTAGRAM"],
    "firstShown": "2026-04-29T07:00:00.000Z",
    "lastShown": "2026-05-25T07:00:00.000Z",
    "isActive": false,
    "regions": [],
    "metrics": null,
    "query": { "type": "domain", "value": "nike.com" },
    "scrapedAt": "2026-08-26T06:08:29.194Z"
}
```

A Google result carries the advertiser, both dates, the archived creative image and a link to the ad in the Transparency Center:

```json
{
    "platform": "google",
    "adId": "CR09978845720385421313",
    "adUrl": "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR09978845720385421313",
    "advertiser": { "name": "Nike, Inc.", "domain": "nike.com" },
    "creative": {
        "format": "text",
        "imageUrls": ["https://tpc.googlesyndication.com/archive/simgad/430467805130317951"]
    },
    "firstShown": "2022-11-30T14:49:50.000Z",
    "lastShown": "2026-08-26T05:37:14.000Z"
}
```

And where LinkedIn publishes delivery data, you get an impressions range plus the country split:

```json
{
    "platform": "linkedin",
    "advertiser": { "name": "Profound", "url": "https://www.linkedin.com/company/104065246" },
    "firstShown": "2026-08-21T00:00:00.000Z",
    "lastShown": "2026-08-26T00:00:00.000Z",
    "metrics": { "impressions": "1k-5k" },
    "regionBreakdown": [
        { "region": "United Kingdom", "sharePct": 27 },
        { "region": "Germany", "sharePct": 14 },
        { "region": "France", "sharePct": 12 },
        { "region": "Netherlands", "sharePct": 7 },
        { "region": "Spain", "sharePct": 6 }
    ]
}
```

> Two things to keep an eye on: `maxAdsPerQuery` is **per query**, so three search terms and two domains at 100 each is up to 500 ads, not 100. And `maxRequestsPerCrawl` (default 1000) is the overall brake — multi-platform LinkedIn runs reach it faster than you would expect.

### Tips for better results

**Search a brand by domain, not by name.** Google ranks its advertiser suggestions by spelling similarity, so `nike.com` in `domains` beats `Nike` in `advertisers` every time. This is the single biggest quality win in the Actor.

**Use single, specific keywords on LinkedIn.** Multi-word phrases match loosely there and most results get filtered out.

**Leave `filterIrrelevant` on.** Ad library keyword search is broad by nature and will happily return a long advertorial that happens to contain your words in unrelated sentences. The filter checks each ad's own text, drops the ones that don't genuinely match, and reports how many it removed.

**Start small.** Run with `maxAdsPerQuery: 20` to confirm you're getting the right advertiser before spending a full run.

### Proxy configuration

Use **Apify Proxy with the `RESIDENTIAL` group**. Meta blocks datacenter and platform IP ranges outright, so residential proxies are required for Meta runs. The Actor handles session management and rotation for you — it keeps one sticky IP per Meta session (Meta ties its anti-bot cookie to the IP) and rotates automatically when an IP gets blocked. Google and LinkedIn are more forgiving but benefit from the same setting.

### Integrations and API

Results can be piped anywhere Apify integrates: **Google Sheets**, **Slack**, **Zapier**, **Make**, **GitHub**, **Google Drive**, **AWS S3**, or any **webhook**. Combine that with the **Schedules** tab for hands-off daily competitor monitoring.

You can also start runs programmatically. The Apify API exposes a **Run Actor** endpoint, and official clients exist for **Python** and **JavaScript**, so the Actor works as an ad library API behind your own code. The **API** tab on this page has copy-paste snippets with your token filled in, and results can be fetched as JSON, JSONL, CSV, Excel, XML or HTML.

### FAQ

<details>
<summary><strong>Is it legal to scrape ad libraries?</strong></summary>

The Google Ads Transparency Center, the Meta Ad Library and the LinkedIn Ad Library are public transparency tools that the platforms publish deliberately, in large part to satisfy regulations like the EU Digital Services Act. The Actor collects only what those pages show to any visitor, with no login and no personal data behind an account wall. As with any scraping, you are responsible for how you use the data and for your own compliance obligations.

</details>

<details>
<summary><strong>Do I need a Facebook, Google or LinkedIn account?</strong></summary>

No. All three ad libraries are public. The Actor needs no login, no cookies and no API keys.

</details>

<details>
<summary><strong>Can I get ad text from Google?</strong></summary>

Google's Ads Transparency Center does not publish ad copy or destination URLs. You do get the advertiser, first and last shown dates, the archived creative image for most ads, and a direct link to view the ad. For full ad copy, use Meta or LinkedIn.

</details>

<details>
<summary><strong>Why did a Google search return no results?</strong></summary>

Two common reasons. Google's domain filter is exact, so the Actor normalizes what you enter (`Nike.com` becomes `nike.com`). And the region filter is applied by Google itself — an advertiser with no ads in your selected `countries` returns nothing even though it advertises elsewhere. The run log tells you which happened; clearing `countries` searches every region.

</details>

<details>
<summary><strong>Why do some LinkedIn ads have impressions and others don't?</strong></summary>

LinkedIn only publishes delivery data for a subset of ads. Where it does, you get an impressions range like `1k-5k` and a per-country percentage split. Where it doesn't, the page itself has no such section — the ad still returns with its creative and copy.

</details>

<details>
<summary><strong>How do I monitor a competitor's ads automatically?</strong></summary>

Put the competitor's domain in `domains`, select all three platforms, then open the **Schedules** tab and set a daily run. Connect the output to Google Sheets, Slack or a webhook through Apify integrations and you get a running log of every new ad they launch.

</details>

<details>
<summary><strong>Can I scrape ads from one specific advertiser?</strong></summary>

Yes. Put the brand in `advertisers`, or use its exact identifier — a Google advertiser ID (`AR…`), a Meta page ID, or a LinkedIn company ID — for an exact match. You can also paste an ad library URL into `startUrls`.

</details>

<details>
<summary><strong>Can I scrape ads from a specific country?</strong></summary>

Yes. Set `countries` to ISO-2 codes such as `["US", "GB"]`. Google and Meta apply the filter server-side. LinkedIn has no country filter on search, but where it discloses delivery data you get a per-country percentage split in `regionBreakdown`.

</details>

<details>
<summary><strong>Are results deduplicated?</strong></summary>

Yes. Overlapping searches never produce repeat rows; every ad appears once per run.

</details>

<details>
<summary><strong>Can I export to CSV or Excel?</strong></summary>

Yes. Results can be downloaded as JSON, JSONL, CSV, Excel, XML or HTML from the dataset tab, or fetched through the Apify API.

</details>

### Support

Found a bug or need a field the Actor doesn't return yet? Open an issue on the Actor's **Issues** tab with your input configuration and the run ID.

Ad libraries change their layouts from time to time. If results suddenly look wrong, please report it — that's the fastest way to get it fixed for everyone.

# Actor input Schema

## `platforms` (type: `array`):

Which ad platforms to scrape. LinkedIn costs roughly one request per ad, so large runs there are slower than Google or Meta.

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

Free-text keywords. Meta and LinkedIn search ad copy. Google has no ad-text search, so terms are matched against advertiser names or domains instead.

## `advertisers` (type: `array`):

Advertiser names, Google advertiser IDs (AR…), Meta numeric page IDs, or LinkedIn company IDs.

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

Advertiser websites, e.g. nike.com. Google filters natively; Meta and LinkedIn have no domain filter and fall back to a keyword search.

## `startUrls` (type: `array`):

Ad Library or Ads Transparency Center URLs to scrape directly.

## `matchMode` (type: `string`):

How strictly search terms must match. "phrase" requires the words to appear together and uses Meta exact-phrase search; "broad" matches the words anywhere, returning far more but much noisier results.

## `filterIrrelevant` (type: `boolean`):

Discard ads whose own text does not contain the search terms. Applies to keyword and domain searches on Meta and LinkedIn only — Google exposes no ad copy and is never filtered, and advertiser/URL searches are always kept.

## `countries` (type: `array`):

ISO-2 country codes. Meta uses the first entry as its country filter; Google maps them to region codes. LinkedIn has no country filter on search.

## `dateFrom` (type: `string`):

Earliest ad delivery date, YYYY-MM-DD. Google filters client-side; Meta filters server-side; LinkedIn ignores it.

## `dateTo` (type: `string`):

Latest ad delivery date, YYYY-MM-DD. Google filters client-side; Meta filters server-side; LinkedIn ignores it.

## `googleMaxAdvertisersPerTerm` (type: `integer`):

How many matching advertisers to pull creatives from when a keyword is resolved on Google.

## `metaActiveStatus` (type: `string`):

Filter by whether the ad is still running.

## `metaMediaType` (type: `string`):

Restrict results to a creative media type.

## `metaAdType` (type: `string`):

Restrict results to a special ad category, such as political and issue ads. Meta only reports spend and impressions for political and issue ads.

## `linkedinFetchDetails` (type: `boolean`):

Fetch each ad's detail page for full ad copy, run dates, the impressions range and the per-country breakdown. This costs one extra request per ad. Turning it off returns only what the search card shows: advertiser, headline and truncated copy.

## `maxAdsPerQuery` (type: `integer`):

Cap on ads collected for each search term, advertiser, or domain. This is per query, so several queries multiply it.

## `maxRequestsPerCrawl` (type: `integer`):

Overall safety limit on the number of requests. LinkedIn uses roughly one request per ad, so raise this for large LinkedIn runs.

## `includeRaw` (type: `boolean`):

Keep each platform's original response under a `raw` key alongside the normalized fields.

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

Optional locally, but required on the Apify platform for Meta, which blocks platform IPs. Use the RESIDENTIAL group; datacenter proxies are blocked too.

## Actor input object example

```json
{
  "platforms": [
    "google",
    "meta",
    "linkedin"
  ],
  "searchTerms": [
    "running shoes"
  ],
  "matchMode": "phrase",
  "filterIrrelevant": true,
  "countries": [
    "US"
  ],
  "googleMaxAdvertisersPerTerm": 3,
  "metaActiveStatus": "all",
  "metaMediaType": "all",
  "metaAdType": "all",
  "linkedinFetchDetails": true,
  "maxAdsPerQuery": 20,
  "maxRequestsPerCrawl": 1000,
  "includeRaw": true
}
```

# Actor output Schema

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

No description

# 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": [
        "running shoes"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mediocre_interest/ads-library-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": ["running shoes"] }

# Run the Actor and wait for it to finish
run = client.actor("mediocre_interest/ads-library-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": [
    "running shoes"
  ]
}' |
apify call mediocre_interest/ads-library-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mediocre_interest/ads-library-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/UstWWSV2pQCGDpBjO/builds/Ef4DZ33Eugfpjhv8T/openapi.json
