LinkedIn Ad Library Scraper
Pricing
from $1.20 / 1,000 scraped ads
LinkedIn Ad Library Scraper
Search the LinkedIn Ad Library by keyword, company or payer. Get each ad's full copy, creatives, landing page and run dates - plus, for EU-served ads, total impressions and the complete country-by-country split that LinkedIn's own page hides behind a show-more.
Pricing
from $1.20 / 1,000 scraped ads
Rating
0.0
(0)
Developer
Rouzbeh Vosough Ahmadi
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
LinkedIn Ad Library Scraper — competitor B2B ads, reach and targeting
Scrape the LinkedIn Ad Library by keyword, company or payer — no login, no LinkedIn API access, no cookies — and export each ad's full copy, creatives, landing page, run dates, and, for ads served in the EU, total impressions plus the complete country-by-country breakdown and the advertiser's own targeting choices. Download as JSON, CSV or Excel, or call it over the Apify API.
Ad-spy suites charge $100–500 a month and mostly cover Meta and TikTok. LinkedIn is where B2B money goes, its library is thinner ground, and this bills per ad.
What you can do with it
- Competitor B2B teardown — every ad a rival runs, with the full post copy and creatives
- Reach intelligence no rival returns —
impressions_by_country, the complete split LinkedIn's own page hides behind a "show more" - Read their targeting — which languages and locations the advertiser actually selected
- Trace who is really behind an ad —
paid_for_bynames the funding entity, usually the agency or parent company rather than the brand - Find the ads worth copying —
minSpanDaysfilters down to what an advertiser kept paying for rather than everything they tested - Agency prospecting — payer search surfaces every brand a given agency buys for
What you get
Every row carries:
advertiser_nameandadvertiser_urlpaid_for_by— the legal entity behind the ad, which is often not the advertiser and is the fastest way to trace agencies and holding companiesformat—single_image,video,carousel,article,messageortextad_copy— the full post body, not the truncated previewimage_urls,video_urls,landing_urlfirst_shown,last_shown,span_days— setminSpanDays: 30and the output becomes the ads an advertiser kept paying for rather than everything they testedimpressions_totalas a parsed range ({"min": 10000, "max": 20000, "text": "10k-20k"})impressions_by_country— every country with its share of impressionstargeting— the advertiser's own targeting choices per facet (Language, Location, ...), with the complete value list: LinkedIn's page collapses it behind an "N others" button, this actor returns every entry
Search an EU country to get reach data
This is the one thing worth knowing before your first run, and it is measured, not guessed:
| Search country | Total impressions | Country breakdown |
|---|---|---|
| EU — DE, FR, ES, IE, NL | essentially always | most ads, but it varies a lot by search |
| Non-EU — US, GB | only on ads that also served in the EU | same — a minority |
Measured, not guessed. EU searches returned a total on 6/6, 12/12, 20/20 and 100/100 ads in
separate runs. The country breakdown is the variable one: 98 of 100 ads on one
adidas / DE run, but 7/8, 5/8 and 4/8 on smaller samples for other keywords — so expect most
EU ads to carry it and some searches to carry it on nearly all.
The disclosure belongs to the ad, not to your search filter: an ad that ran in both the US
and the EU carries its EU reach data wherever you find it. On one US-only search, 2 of 10 ads
came back with the full split — one covering 30 countries — while the purely domestic ads
correctly reported has_eu_disclosure: false. You are only charged the country-split event
on the rows where the breakdown actually arrived.
LinkedIn publishes this to meet EU advertising-disclosure rules, so it tracks EU membership rather than market size — the UK gets nothing for its own ads post-Brexit. Non-EU searches always return copy, creatives, dates and the payer.
If you only want rows that carry the breakdown, set onlyWithCountrySplit: true.
Brand-new ads have a total but no split yet
LinkedIn publishes total impressions immediately but says on the ad's own page that
impression data by country can take up to 48 hours. So an ad that launched yesterday
comes back with impressions_total filled and impressions_by_country empty — and that row
carries country_split_pending: true so you can tell "not yet" from "never" instead of
wondering whether the scraper missed something. Search a broad keyword and you will hit a
run where most results are days old; add minSpanDays: 3 to skip them entirely.
The country-split event is not charged on a pending row.
The country breakdown is fuller than LinkedIn's own page
LinkedIn's ad detail page collapses the country list to four entries behind a "show more". The underlying page carries the whole list — one sampled ad had 74 countries. This actor returns all of them.
Input
{"keywords": ["adidas"],"countries": ["DE"],"maxAdsPerQuery": 100,"minSpanDays": 0,"onlyWithCountrySplit": false}
You can search three ways, and mix them: keywords (free text over copy and advertiser names),
companies (numeric LinkedIn company IDs, for one advertiser), and payers (the "Paid for by"
entity).
Every option
| Option | Default | What it does |
|---|---|---|
keywords | — | Free text over ad copy and advertiser names. |
companies | — | Numeric LinkedIn company IDs, e.g. 1035 for Microsoft. Use when you want one advertiser, not a keyword match. |
payers | — | The legal entity on the "Paid for by" line — the way to trace an agency's whole book. |
countries | ["DE"] | Two-letter ISO codes. Country names are rejected. EU codes are the ones that carry reach data. |
maxAdsPerQuery | 50 | Caps each search, so three targets at 50 is 150 ads and 150 charges. |
minSpanDays | 0 | Keep only ads that ran at least this many days. |
onlyWithCountrySplit | false | Keep only rows that actually carry the per-country breakdown. |
requestDelayMs | 500 | Pace between requests. 500 ms is the measured safe pace; raise it if a run starts backing off. |
proxyConfiguration | Apify Proxy on | Recommended — LinkedIn limits by IP, so rotation is what keeps a large run moving. Datacenter proxies are enough. |
Give none of the three target fields and the run falls back to a demo keyword rather than failing, with a warning in the log.
Speed, and why there is a delay setting
LinkedIn rate-limits by IP, and it is strict: six parallel requests trip a 429 immediately, and once tripped the block lasts about 90 seconds no matter how gently you retry. The actor therefore works serially with a delay rather than in parallel.
At the default 500 ms it sustains roughly 50 ads per minute. Leave the proxy enabled —
rotating IPs is what keeps a large run moving, and datacenter proxies are enough. If a run
starts backing off, raise requestDelayMs.
Watch a competitor on a schedule
Ad monitoring is worth more as a diff than as a snapshot. Save the run as a task, give it a schedule, and each run appends to the same dataset so you can see what launched and what was retired:
- Run it once with the companies or keywords you care about.
- Save as a new task, then Schedule → daily or weekly.
- Point the task's Integrations at Google Sheets, Slack, a webhook or your warehouse.
A daily 50-ad watch on one competitor costs about $3.00 a month.
Use it from your own code
curl -X POST "https://api.apify.com/v2/acts/adatlas~linkedin-ad-library-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H 'content-type: application/json' \-d '{"keywords":["marketing automation"],"countries":["DE"],"maxAdsPerQuery":50}'
Official clients exist for Python and JavaScript, and the actor also works over MCP, so an AI agent can call it directly.
Pricing
Pay per event, so a search that finds nothing costs almost nothing:
| Event | Price | Charged |
|---|---|---|
apify-actor-start | $0.0005 | once per run, by the platform, whatever the run returns |
ad-scraped | $0.0012 | per ad delivered |
country-split | $0.0008 | only when the country breakdown is actually present |
An EU run where most ads carry the breakdown lands near $2.00 per 1,000 ads; where fewer do, it falls towards the $1.20 base. A non-EU run, where no breakdown exists, is the base rate only — you are never charged for a field that came back empty. Targeting rides along free.
FAQ
Do I need a LinkedIn account or the LinkedIn Marketing API? No. The Ad Library is a public archive; this actor uses no login, no cookies and no LinkedIn API credentials, and accesses nothing private.
Can I get impressions and country reach? Yes, wherever LinkedIn discloses it — which is ads served in the EU. The disclosure travels with the ad, so an ad that also ran in the EU carries its reach data even on a US search.
Can I search by company instead of keyword?
Yes: keywords, companies (numeric LinkedIn company IDs) and payers (the "Paid for by"
entity) all work and can be mixed in one run.
Can I export to CSV or Excel? Yes — every Apify dataset exports to JSON, CSV, Excel, XML and HTML table.
Why is it slower than other scrapers? LinkedIn rate-limits hard by IP; going parallel trips a 429 and a ~90-second block. Running serially is what keeps a large job finishing at all.
Notes and limits
- Data comes from LinkedIn's public Ad Library, which LinkedIn publishes to meet advertising disclosure rules. No login is used and nothing private is accessed.
maxAdsPerQuerycaps each search. A keyword can match six figures of ads, so a run returns the cap, not the whole archive.- Impression figures are ranges, because that is how LinkedIn publishes them. Both the parsed bounds and LinkedIn's original text are returned.
- Ads with no EU disclosure report
has_eu_disclosure: falseand null impressions rather than guessing.