LinkedIn Ad Library Scraper - LinkedIn Ads Library Scraper avatar

LinkedIn Ad Library Scraper - LinkedIn Ads Library Scraper

Pricing

from $0.90 / 1,000 fast ad results

Go to Apify Store
LinkedIn Ad Library Scraper - LinkedIn Ads Library Scraper

LinkedIn Ad Library Scraper - LinkedIn Ads Library Scraper

Scrape the LinkedIn Ads Library by company, ad-text keyword, or payer name. Get ad copy, headlines and creatives in fast mode; add landing pages, impressions, targeting and transparency fields in full mode. Track competitor B2B campaigns across countries in a single run.

Pricing

from $0.90 / 1,000 fast ad results

Rating

0.0

(0)

Developer

jy-labs

jy-labs

Maintained by Community

Actor stats

0

Bookmarked

65

Total users

42

Monthly active users

3 days ago

Last modified

Share

LinkedIn Ad Library Multi-Search Scraper

This is an independent, unofficial tool. It is not affiliated with, endorsed by, or sponsored by LinkedIn. It only reads information exposed by the public LinkedIn Ad Library and does not accept LinkedIn credentials, cookies, or member data.

Search multiple company names, ad text keywords, and payer names in one run. Results from every search are merged by LinkedIn ad ID, while the matches field preserves exactly which searches found each ad.

Use Fast mode for affordable creative research or Full mode when you need the public transparency fields LinkedIn exposes on individual ad pages.

What it collects

  • Every mode, from the search card: advertiser name and logo (null on member post cards, which show the member's photo instead), ad format, ad text as shown on the card, headline, creatives, and source URL
  • Every mode: the post author's name and headline for promoted member posts (thought leader ads)
  • Every mode, creatives by format: an image ad's image, every carousel item, a document ad's pages (as images; LinkedIn serves no file), an article's cover, and a video ad's cover. Message ads have none. full mode makes a video's url its MP4 file and keeps the cover as thumbnailUrl
  • Every matching company, keyword, or payer search with its rank
  • full mode adds, from the ad detail page: the full ad text, the CTA label, company ID and public company URL, public payer name, run period, landing URL, impression range, country breakdown, and targeting
  • Restricted ads as explicit rows without inventing hidden values

In fast mode, adText is the card's visible copy, which LinkedIn shortens with "…" for long posts, and ctaLabel is normally null because search cards usually carry no call-to-action button (one is read when a card shows it). A Message ad's card runs its paragraphs together and drops straight apostrophes ("Im" where the ad page has "I'm"). full mode replaces them with the whole post copy (paragraph breaks kept) and the CTA label from the ad detail page. A Message ad's text and button label come from its ad page too, line breaks and apostrophes included. Spotlight and Follow Company ads have no post copy and keep the card's text. An ad without a CTA, such as most member posts, keeps ctaLabel: null. An article ad takes the article's own title as its headline, and when its advertiser wrote no commentary above the article, the article's summary becomes its adText.

When a company promotes a member's post, advertiser is the sponsoring company (LinkedIn's "Promoted by" line) and postAuthor holds the member's name and headline. Company ads have postAuthor: null. postAuthor is personal data: a member's public name and headline as shown in the Ad Library. You need a lawful basis, such as one under the GDPR, to store or process it.

The Actor does not scrape LinkedIn profiles, emails, contacts, or logged-in content.

Input

Provide at least one value in companyNames, keywords, or payerNames. Across the three lists, you can submit up to 20 unique searches.

{
"companyNames": ["HubSpot", "Salesforce", "Monday.com"],
"keywords": ["CRM", "AI agent"],
"countries": ["US", "GB", "DE"],
"dateOption": "last-30-days",
"detailMode": "fast",
"maxResultsPerSearch": 25,
"maxTotalResults": 100
}

Date filters

Choose last-30-days, current-month, current-year, last-year, or custom-date-range. A custom range requires both dates, must stay within the most recent year, and must end no later than the previous UTC day because LinkedIn only exposes completed days.

{
"keywords": ["cybersecurity"],
"countries": ["FR", "DE"],
"dateOption": "custom-date-range",
"startDate": "2026-07-01",
"endDate": "2026-07-23",
"detailMode": "full"
}

ALL must be used by itself. Otherwise, use up to five ISO two-letter country codes.

Fast and Full modes

fast mode returns fields visible on search cards and is the best choice for creative discovery and competitor monitoring. It is billed per saved result at the Fast price on the Pricing tab.

full mode opens each public ad detail page and is billed per saved result at the Full price, twice the Fast price. LinkedIn only exposes some transparency fields for eligible ads and regions. Missing non-EU transparency data is returned as null and is not considered an error. If a detail page fails, the search-card result is retained with detailStatus: "failed".

Output

Each Dataset row represents one unique ad ID. A full mode row:

{
"adId": "1455310303",
"matches": [
{
"type": "company",
"value": "JetBrains",
"rank": 1,
"countries": ["FR", "DE"],
"searchUrl": "https://www.linkedin.com/ad-library/search?accountOwner=JetBrains"
}
],
"advertiser": {
"name": "JetBrains",
"companyId": "12515",
"companyUrl": "https://www.linkedin.com/company/12515",
"logoUrl": "https://media.licdn.com/..."
},
"postAuthor": null,
"payerName": "JetBrains s.r.o.",
"adFormat": "Single Image Ad",
"adText": "Build better software.",
"headline": "Try IntelliJ IDEA",
"ctaLabel": "Learn more",
"landingUrl": "https://www.jetbrains.com/idea/",
"creatives": [
{
"type": "image",
"url": "https://media.licdn.com/...",
"thumbnailUrl": null
}
],
"runPeriod": {
"startDate": "2026-07-22",
"endDate": "2026-07-23"
},
"restricted": false,
"impressions": {
"display": "30k-50k",
"min": 30000,
"max": 50000,
"byCountry": [{ "country": "France", "percentage": 60 }]
},
"targeting": [
{
"category": "Location",
"included": ["France", "Germany"],
"excluded": [],
"isTargeted": true,
"isExcluded": null
},
{
"category": "Company",
"included": [],
"excluded": [],
"isTargeted": true,
"isExcluded": false
}
],
"transparency": "published",
"detailStatus": "complete",
"retrievalMode": "http",
"sourceUrl": "https://www.linkedin.com/ad-library/detail/1455310303",
"scrapedAt": "2026-07-24T00:00:00.000Z"
}

Each targeting entry is one category. included and excluded list the segments LinkedIn names, such as languages and locations. For categories LinkedIn only marks in its "Targeted / Excluded" table (Audience, Company, Job and others), the lists stay empty and isTargeted / isExcluded record the mark: true for a check, false for a dash. A listed "Targeting includes" or "Targeting excludes" segment sets the matching flag to true; a flag the page does not state is null.

Every row read from its detail page carries transparency: "published" when LinkedIn showed the impressions and targeting sections for that ad, "unavailable" when it published neither. LinkedIn publishes them for ads it served in the EU, so a non-EU campaign comes back "unavailable" with its advertiser, payer, text, CTA and landing URL all filled. That is the site's own gap, not a failed read — a detail page the Actor could not fetch is marked detailStatus: "failed" and charged at the Fast price. The run status message and SUMMARY report the split (transparencyPublishedCount / transparencyUnavailableCount), so a run says how much transparency data LinkedIn actually had.

impressions.display is LinkedIn's total-impressions bucket as shown, and min/max are its bounds: "30k-50k" gives 30000/50000, "< 1k" gives 0/1000, and an open-ended bucket such as "1M+" gives 1000000/null. A bucket the Actor does not recognise keeps display with null bounds; the country breakdown is kept either way.

The default Key-Value Store record SUMMARY contains search-level statuses, raw and deduplicated counts, confirmed empty searches, partial results, browser fallbacks, bounded error messages, final output count, the transparency split, and billing-limit state. Summary data is never mixed into the Dataset.

Empty results and blocking

An HTTP page is considered empty only when LinkedIn shows an explicit empty-result message. A 403, 429, error shell, meaningless 404, or page without both ads and empty-result evidence is treated as blocking or a parse failure.

A search page gets up to six attempts, with a 1–3 second pause before each retry (five when you set the proxy yourself); above a 60-second request timeout it gets four (three when you set the proxy yourself). Detail pages get up to four (three when you set the proxy yourself), see Proxy below. Independent HTTP searches run with a maximum concurrency of five. If a search still fails, only that search is reprocessed in a browser, with at most two browser fallbacks at once. LinkedIn blocks per exit IP, so when a proxied browser fallback's first page is blocked or shows neither ads nor a no-results notice, it retries on a new proxy session (a new exit IP on Apify Proxy) after a 1–3 second pause: five attempts at most, and no new attempt once the run is within one request timeout plus 30 seconds plus 150 ms per result row of its timeout. A browser fallback without a proxy gets one attempt, since a new session would leave from the same IP. An explicit no-results page is never retried. A run succeeds partially when at least one search succeeds. It fails only when every search is blocked or unparsable; an all-empty run succeeds normally.

Proxy

Leave Proxy configuration at its default and the Actor goes direct first. It switches to Apify Proxy only when LinkedIn rate-limits or keeps blocking:

  • Every search starts without a proxy. LinkedIn's occasional 403 is often random per request, so a page is first retried on the same route. A search that keeps getting blocked climbs on its own: two direct attempts, then one through Apify Proxy (datacenter), then up to three through residential proxy (US), then up to two through Apify's Unblocker proxy (built for sites behind anti-bot protection such as Cloudflare), each proxy attempt on a new exit IP: eight attempts in all. Above a 60-second request timeout the steps are one, one, two and one: five in all. Residential and Unblocker are used only by a search that needs them; if your account cannot use one of them, that step is skipped. Unblocker is billed per successful request ($1–1.5 per 1,000 requests depending on your Apify plan), so a search that gets through earlier never uses it. A search still blocked after these is named in the run's status message.
  • The first 429 or 999 answer switches the rest of the run to Apify Proxy (datacenter). The search that got it retries through the proxy, and every search that starts later begins on the proxy. If the proxy cannot be set up, the run stays direct.
  • A proxied search keeps its proxy and one proxy session (the same exit IP and cookies) for all its pages. It moves to a new exit IP only after one of its requests is blocked.
  • Detail pages start the same way: direct unless the run has switched to the proxy. A blocked detail page climbs on its own: two attempts on its starting route, then one through Apify Proxy (datacenter), one through residential proxy (US) and one through Unblocker, each proxy attempt on a new exit IP: five attempts at most. Other failures (a missing page, unusable content) end it after three attempts on its route.
  • A search's browser fallback goes through residential when the search reached it, else the proxy its search got through on (residential in place of Unblocker, which a browser does not use); otherwise it follows the run's route. At most five searches per run use residential this way; later ones go through datacenter. A proxied browser fallback skips images, video, fonts and stylesheets.
  • Any detail page whose HTTP attempts failed, blocked or not, goes to a browser fallback, except one that ended on a 404 or 410 (the ad page is gone). On the default proxy setting it goes through residential (else datacenter, else direct), two pages at a time. The browser loads only the page's HTML document, with no script, style, image or other requests (inline scripts still run), since a logged-out detail page is fully rendered on the server. A blocked page (403/429/999), an unusable one (such as a Cloudflare challenge) or one that failed to load is retried on a new proxy session, a new exit IP on Apify Proxy, after a 1–3 second pause: five attempts per page at most. A run makes at most 20 detail browser attempts or 3 per page sent to the browser, whichever is more; retries never use the attempts still needed for other pages' first try. No attempt starts, first ones included, once the run is within one request timeout plus 30 seconds plus 150 ms per result row of its timeout, so the rows are saved in time. Without a proxy a page gets one attempt. A page that still fails keeps its search card data with detailStatus: "failed" and is charged as a Fast result.

The run log shows each search's route (Search route: direct, datacenter, residential or unblocker), every switch, and each search page's size, and each detail page fetched through a proxy with its size. The proxyTiers field of the SUMMARY record counts search page attempts, pages and bytes per route, the same for detail pages under proxyTiers.details, search browser fallbacks and their approximate bytes under proxyTiers.browserFallback (with retries, the new-session retries of blocked browser pages, also counted in each search's retryCount), and moves onto each proxy under proxyTiers.climbs. Detail browser fallbacks, with their pages, bytes and new-session retries, are under proxyTiers.detailBrowserFallback.

If you set the proxy yourself (proxy groups such as residential, a country, your own proxy URLs, or turning the proxy off), the Actor uses exactly that setting for every request and never switches routes.

Pricing

ChargePrice
Fast saved resultper result, see Pricing tab
Full saved resultper result, see Pricing tab
Actor start event$0.00005 per charged start unit

The current per-result prices, including the discount for your Apify plan, are on the Actor's Pricing tab. A Full result costs twice a Fast result. Apify automatically scales the synthetic start-event count with allocated memory; at the default 1 GB, a run charges one start unit for a total of $0.00005. The start event also makes the first five seconds of compute eligible for Apify's PPE startup coverage.

The Pricing tab also shows the minimum maximum charge a run accepts. Set a higher maximum charge when requesting more results.

Before Full detail work, the Actor checks how many full-ad-result events still fit within the user's limit. Fast mode checks fast-ad-result instead. Each row is validated, saved, and charged together. When another result cannot be charged, the Actor stops and records chargeLimitReached: true in SUMMARY.

Ads with no extractable content (no advertiser name, or no text, headline, or creative) are delivered free, marked contentStatus: "empty".

In Full mode, an ad whose detail page could not be fetched is delivered with its Fast data, marked detailStatus: "failed", and charged fast-ad-result instead of full-ad-result. SUMMARY.fastChargedDetailFailures counts them.

Result estimate:

Fast = unique saved results × Fast result price
Full = unique saved results × Full result price
Default-memory start = $0.00005

SUMMARY.pricing records the observed Fast, Full, and start prices and the highest price this build accepts for each. Production runs refuse to start when a deployed price is above that ceiling, preventing accidental overbilling.

Run through the API

Run and wait for Dataset output with an Apify API token in the authorization header:

curl --request POST \
"https://api.apify.com/v2/acts/jy-labs~linkedin-ad-library-multi-search-scraper/run-sync-get-dataset-items?format=json&clean=true" \
--header "Authorization: Bearer $APIFY_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"companyNames": ["HubSpot", "Salesforce"],
"countries": ["US", "DE"],
"detailMode": "fast",
"maxResultsPerSearch": 10,
"maxTotalResults": 20
}'

For larger runs, start the Actor asynchronously with /runs, then read the default Dataset and the SUMMARY record using the storage IDs returned by the run.

Common use cases

  • Compare LinkedIn ads from multiple competitors
  • Search B2B categories such as CRM, cybersecurity, or AI agents
  • Find ads by the disclosed payer entity
  • Inspect public EU impression and targeting transparency
  • Compare creative strategies across countries

FAQ and troubleshooting

Why did a successful search return zero rows?

confirmed_no_results means LinkedIn explicitly showed that no ads matched the filters. Broaden the date range, use ALL, or try the advertiser's exact public name. A blocked or ambiguous page is never reported as an empty result.

Why are impressions or targeting null?

LinkedIn exposes these fields only for eligible ads and regions. Use Full mode and include relevant European countries. Missing public transparency data is represented as null; the Actor does not infer it.

What does completed_partial mean?

At least one search succeeded, but another search, pagination page, or detail page was unavailable. Review SUMMARY.searches, detailErrors, and browserFallbackSearches. Successfully collected card rows remain in the Dataset.

Why did output stop before maxTotalResults?

The run's maximum charge may not allow another result. Increase the maximum charge and rerun. SUMMARY.chargeLimitReached distinguishes this from search limits or deduplication.

Does the Actor require a LinkedIn account?

No. It accepts no LinkedIn login, cookie, or member data and reads only the public Ad Library.

Something is missing or wrong — what helps most?

Open an issue on the Actor's page with the run ID: the SUMMARY record and the run log carry what is needed to reproduce it, and both stay in your account. If a run did what you needed, a short review is what other buyers read before they trust a scraper with their own searches.

Responsible use

You are responsible for using the output in accordance with applicable laws, LinkedIn's current terms, robots policies, and Apify's platform rules. Rows for promoted member posts include personal data (postAuthor: the member's public name and headline), so make sure you have a lawful basis, such as one under the GDPR, before storing or processing it. Public web content can change without notice. Before Store publication, the latest relevant policies must be reviewed; if publication is not permitted, keep the Actor private.