# LinkedIn Ad Library Scraper - Ads, Impressions & Targeting (`jmlp/linkedin-ad-library-scraper`) Actor

Scrape every ad a company runs on LinkedIn from the public LinkedIn Ad Library. Get ad copy, headlines, images, videos, carousels, CTAs, landing pages, who paid, run dates, impressions by country and targeting. Search by advertiser, keyword or country. No login or cookies.

- **URL**: https://apify.com/jmlp/linkedin-ad-library-scraper.md
- **Developed by:** [Mary Lou](https://apify.com/jmlp) (community)
- **Categories:** Social media, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.15 / 1,000 ads

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## LinkedIn Ad Library Scraper — ads, impressions & targeting

See **every ad a company runs on LinkedIn**. Give it a competitor's name and
get back their ad copy, headlines, images, videos, carousels, calls to action
and landing pages, plus who paid for each ad, when it ran, how many
impressions it got in each country, and who it targeted.

The data comes from LinkedIn's public [Ad Library](https://www.linkedin.com/ad-library).
No login, no cookies, no LinkedIn account at risk.

***

### What you can do with it

- **Competitor ad intelligence**: see every message, offer and creative a
  competitor is testing on LinkedIn, and how long each one ran
- **B2B creative research**: collect hundreds of real LinkedIn ads in your
  industry for swipe files and briefs
- **Campaign and spend signals**: impressions ranges and per-country splits
  show where a competitor is investing (published for ads shown in the EU)
- **Targeting insight**: languages, locations and the audience parameters
  (company, job, demographic, interests) each EU ad used
- **Agency pitches and audits**: a prospect's full LinkedIn ad history in
  minutes
- **Lead generation**: companies advertising on LinkedIn are companies with a
  marketing budget

***

### How to scrape the LinkedIn Ad Library

**1.** Type one or more company names into **Advertisers**: `HubSpot`,
`Salesforce`. Their LinkedIn company page links work too.

**2.** Optionally narrow it down with **Keywords**, **Countries** and a
**Date range**.

**3.** Press Start. Leave **Proxy** on for speed.

You can also paste links straight from the Ad Library into **Ad Library
links**: a search, which keeps its own filters, or a single ad.

***

### Output

One row per ad. Export as **JSON, CSV, Excel, XML or RSS**.

```json
{
  "ad_id": "1578715144",
  "ad_library_url": "https://www.linkedin.com/ad-library/detail/1578715144",
  "advertiser_name": "HubSpot",
  "advertiser_url": "https://www.linkedin.com/company/68529",
  "paid_by": "Dola Akinduro",
  "format": "Video Ad",
  "ad_type": "SPONSORED_VIDEO",
  "body": "Recently, I have had the privilege of sitting on the other side...",
  "link_url": "https://lnkd.in/g9fp7HSB",
  "video_urls": ["https://dms.licdn.com/playlist/vid/v2/.../mp4-720p-30fp-crf28/..."],
  "video_poster": "https://media.licdn.com/dms/image/v2/.../videocover-high/...",
  "first_shown": "2026-09-29",
  "last_shown": "2026-09-30",
  "total_days_shown": 2,
  "impressions": "1k-5k",
  "impressions_min": 1000,
  "impressions_max": 5000,
  "impressions_by_country": [
    {"country": "Germany", "share": "100%", "share_percent": 100.0},
    {"country": "United Kingdom", "share": "< 1%", "share_percent": 0.5}
  ],
  "targeting": {
    "language": {"includes": ["English"]},
    "location": {"includes": ["Germany"]},
    "parameters": {
      "Company": {"targeted": true, "excluded": false},
      "Job": {"targeted": false, "excluded": false}
    }
  },
  "shown_in_eu": true,
  "searched_for": "advertiser 'HubSpot' | DE",
  "scraped_countries": ["DE"]
}
```

| Field | Notes |
| --- | --- |
| `advertiser_name`, `advertiser_url`, `advertiser_id` | The company running the ad |
| `paid_by` | Who paid, which is often a different legal entity or an agency |
| `format`, `ad_type` | Single Image, Video, Carousel, Document, Text, Event, Spotlight, Message and other ads |
| `body`, `headline`, `cta_text`, `link_url` | The creative's copy, button and landing page, with LinkedIn's tracking parameter removed |
| `image_urls`, `video_urls`, `carousel_cards` | Creative files: images, MP4s in each quality, each carousel card's image, title and link |
| `first_shown`, `last_shown`, `total_days_shown` | When it ran |
| `impressions`, `impressions_min`, `impressions_max` | LinkedIn's impressions range, also as numbers you can sort and sum |
| `impressions_by_country` | The share of impressions in each country |
| `targeting` | Language and location, and which audience parameters were targeted or excluded |
| `shown_in_eu` | Whether LinkedIn published EU transparency data for the ad |

**Why some ads have no dates, impressions or targeting.** LinkedIn publishes
those only for ads shown in the European Union, under the Digital Services
Act. For other ads the library shows the advertiser, who paid, the format and
the creative, and so does this scraper. Add EU countries such as `DE`, `FR`,
`NL` or the group `EU` to **Countries** to focus on ads with the full data.

***

### Input

| Field | What it does |
| --- | --- |
| **Advertisers** | Company names or LinkedIn company page links. One search each |
| **Keywords** | Words in the ad. Combined with each advertiser when both are given |
| **Ad Library links** | Searches or single ads copied from the Ad Library |
| **Countries** | Where the ads were shown: codes, names, or groups like `EU` and `DACH` |
| **Date range** | All time, last 30 days, this month, this year, last year, or a custom range |
| **Sort by** | Newest or oldest first |
| **Max ads** / **Max ads per search** | Caps for runtime and cost |
| **Scrape ad details** | On by default: who paid, dates, impressions, targeting, CTA, landing pages, videos |
| **Exact advertiser match** | Keep only the named company, not partners or agencies whose names contain it |
| **Proxy** | Apify Proxy by default, which makes the run several times faster |

Inputs are read forgivingly: `uk`, `Deutschland` and `DEU` all mean a
country; `last 30 days`, `30d` and `this year` all work as date ranges; and
a company page link or a website is read as the advertiser's name. Anything
that cannot be read is skipped with a note in the log. A whole run is never
lost to one odd entry.

***

### Reliable on big runs

- **Rate limits handled for you.** LinkedIn limits each IP to about one
  request every two seconds. The scraper paces itself and spreads the work
  over many exit IPs.
- **A refused search is retried.** It moves to new IPs, and searches still
  refused at the end get a second pass on another proxy. The other searches
  carry on either way.
- **Resumable.** Progress is saved every 30 seconds and resumes on the exact
  page it stopped at, so nothing is fetched or charged twice.
- **Deduplicated.** An ad found by several searches appears once.

***

### FAQ

**Do I need a LinkedIn account or cookies?**
No. The Ad Library is public and the scraper reads it without signing in.

**Why do I get ads from companies with similar names?**
LinkedIn's advertiser search matches names loosely, so `HubSpot` also finds
`HubSpot Platinum Partner`. Turn on **Exact advertiser match** to keep only
the company itself.

**How far back does it go?**
As far back as the Ad Library itself goes. Use **Date range** to look at a
particular period.

**How many ads can I get?**
Every ad the library returns for your search. Searches of thousands of ads
are normal.

**Can I run it on a schedule?**
Yes. Use **Date range: Last 30 days** and run it weekly to track what your
competitors launch.

**Is scraping the LinkedIn Ad Library legal?**
The Ad Library is a public transparency tool that LinkedIn publishes so that
anyone can see the ads on its platform. This scraper reads only that public
data. As with any data, how you use it is your responsibility.

***

### Related scrapers

- **Meta Ads Library Scraper**: Facebook and Instagram ads
- **Google Ads Transparency Center Scraper**: Google Search, YouTube and
  Display ads
- **TikTok Ad Library Scraper**: TikTok ads in the EU and UK
- **LinkedIn Jobs Scraper**: what companies are hiring for

# Actor input Schema

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

Company names as LinkedIn shows them, or their LinkedIn company page links (linkedin.com/company/hubspot). One search per advertiser. LinkedIn matches names loosely: 'HubSpot' also finds 'HubSpot Platinum Partner' and agencies with HubSpot in their name - turn on 'Exact advertiser match' to keep only the company itself.

## `keywords` (type: `array`):

Words in the ad. With advertisers given too, each advertiser is searched for each keyword.

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

Paste LinkedIn Ad Library links straight from your browser: a search (linkedin.com/ad-library/search?...), which keeps its own filters, or a single ad (linkedin.com/ad-library/detail/...).

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

Where the ads were shown: ISO codes or names (GB, Germany, United States), or a group such as EU or DACH. Empty means every country. Impressions and targeting are published only for ads shown in the EU.

## `dateRange` (type: `string`):

When the ads ran. 'Custom' uses the start and end dates below.

## `startDate` (type: `string`):

For a custom range: YYYY-MM-DD, or relative such as '90 days'.

## `endDate` (type: `string`):

For a custom range: YYYY-MM-DD, or 'today'.

## `sortBy` (type: `string`):

Newest first is LinkedIn's default.

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

Stop after this many unique ads across every search. Caps both runtime and cost. Empty means every ad found.

## `maxAdsPerSearch` (type: `integer`):

Cap each advertiser or keyword separately, so one big advertiser does not use up 'Max ads' alone.

## `scrapeAdDetails` (type: `boolean`):

Open each ad's detail page for who paid, the run dates, impressions by country and targeting (published for ads shown in the EU), the call to action, landing URLs and video files. One extra request per ad. Off: only what the search results show.

## `exactAdvertiserMatch` (type: `boolean`):

Keep only ads whose advertiser name is exactly the one searched (ignoring case and punctuation), not partners or agencies whose names contain it.

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

How many advertisers or keywords to search in parallel, 1 to 10. Each uses its own exit IPs. Without a proxy everything runs one request at a time.

## `detailConcurrency` (type: `integer`):

How many ad detail pages each search reads in parallel, each on its own exit IP, 1 to 10. LinkedIn allows one IP about one request every two seconds.

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

Recommended. LinkedIn limits each IP to about one request every two seconds; Apify Proxy spreads the requests over many IPs, so the run is several times faster.

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

How many times a refused search moves to new exit IPs before it is kept for a second pass.

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

Save progress every ~30s so a run that gets migrated or restarted by the platform picks up where it stopped, without repeating ads.

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

If your previous run with the same input was interrupted, scrape only what it missed. Ads already collected stay in THAT run's dataset.

## Actor input object example

```json
{
  "advertisers": [
    "HubSpot"
  ],
  "dateRange": "all-time",
  "sortBy": "newest",
  "maxAds": 100,
  "scrapeAdDetails": true,
  "exactAdvertiserMatch": false,
  "maxConcurrency": 3,
  "detailConcurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "proxyRotations": 4,
  "resume": true,
  "continueFromLastRun": false
}
```

# Actor output Schema

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

One record per unique ad.

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

How many ads and advertisers were found, the totals LinkedIn reports per search, the format breakdown, and whether anything was refused.

# 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 = {
    "advertisers": [
        "HubSpot"
    ],
    "maxAds": 100,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("jmlp/linkedin-ad-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 = {
    "advertisers": ["HubSpot"],
    "maxAds": 100,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("jmlp/linkedin-ad-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 '{
  "advertisers": [
    "HubSpot"
  ],
  "maxAds": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call jmlp/linkedin-ad-library-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jmlp/linkedin-ad-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/0ifaobO10IUhYquqU/builds/0wnuoCnXcYCBdzXoD/openapi.json
