DEV Community

Cover image for Tracking B2B Ad Campaigns and Paid-For-By Data Without Authentication
Crawler Bros
Crawler Bros

Posted on Fully Autonomous

Tracking B2B Ad Campaigns and Paid-For-By Data Without Authentication

Extracting competitive advertising intelligence from LinkedIn has historically presented a technical challenge. Unlike consumer platforms, B2B ad monitoring requires tracking exact corporate messaging, target countries, and corporate entities across complex global brands. While LinkedIn exposes a public Ad Library UI at linkedin.com/ad-library, manually auditing thousands of creative variations or programmatically extracting structured records without trigger-happy security checks normally stalls automated pipelines.

The LinkedIn Ads Scraper solves this by making direct, unauthenticated requests to the public Ad Library endpoints. It allows data engineers to ingest raw ad creatives, copy, target destination URLs, and corporate ownership disclosures into automated data pipelines without maintaining session tokens or managing residential proxy networks.

Extracting Ad Data Without Session Credentials

LinkedIn's main platform relies heavily on user sessions, making standard scrapers fragile. The Ad Library, however, operates as a public utility to satisfy international transparency mandates. The actor leverages these public endpoints, requiring no login credentials, cookies, or account access.

Because public endpoints render basic web components for search requests, standard data pipelines often miss deeper metadata. This scraper captures base-level UI data directly from search queries and, when configured, sends secondary requests to fetch complete detail pages.

A typical output object returns fully populated fields while automatically dropping empty properties:

{
  "ad_id": "12345678",
  "ad_url": "https://www.linkedin.com/ad-library/detail/12345678",
  "ad_format": "video",
  "advertiser_name": "Anthropic",
  "advertiser_url": "https://www.linkedin.com/company/anthropicresearch",
  "advertiser_logo": "https://media.licdn.com/dms/image/.../company-logo.jpg",
  "headline": "Build with Claude",
  "body_text": "Ship faster with the Claude API. Trusted by 1000s of developers.",
  "cta_text": "Learn more",
  "cta_url": "https://www.anthropic.com/api",
  "media_urls": ["https://media.licdn.com/dms/image/.../ad-creative.jpg"],
  "paid_for_by": "Anthropic PBC",
  "body_text_full": "Ship faster with the Claude API. Trusted by 1000s of developers building agents, RAG pipelines, coding assistants, and more.",
  "scraped_at": "2026-05-05T13:42:18Z"
}
Enter fullscreen mode Exit fullscreen mode

The payload exposes exact click-through destination URLs (cta_url), creative asset hosting locations (media_urls), and ad format types like image, video, carousel, document, text, or inmail.

Configuring Targeting Parameters in JSON Schemas

The scraper accepts targeted parameter combinations to restrict search volumes and prevent rate-limit throttling. Instead of fetching blanket platform datasets, searches can be filtered by advertisers, keywords, ISO country codes, and date ranges.

Keyword and Advertiser Query Logic

The input schema handles search strings differently depending on whether you populate searchTerms or advertisers:

  • searchTerms: Broad keyword matching against headline copy, body commentary, and advertiser display names.
  • advertisers: Narrow matching restricted strictly to official company display names.

When combining both parameters, every searchTerms entry is evaluated alongside every advertisers entry across all declared countries.

{
  "searchTerms": ["cloud computing"],
  "advertisers": ["Microsoft", "Salesforce"],
  "countries": ["US", "GB"],
  "dateOption": "last-90-days",
  "maxResults": 250,
  "enrichWithDetailPage": true
}
Enter fullscreen mode Exit fullscreen mode

Date Filtering and Direct URLs

The dateOption string defaults to predefined windows like last-30-days, last-90-days, or last-year. For targeted historical audits, setting dateOption to "custom" allows passing explicit ISO strings via dateFrom and dateTo.

{
  "searchTerms": ["recruiting"],
  "dateOption": "custom",
  "dateFrom": "2026-01-01",
  "dateTo": "2026-03-31",
  "maxResults": 100
}
Enter fullscreen mode Exit fullscreen mode

If you have already assembled complex search queries within the LinkedIn Ad Library UI, you can bypass manual parameter mapping by supplying pre-built URLs directly to the directUrls array.

Running the Extractor: Step-by-Step

Step 1: Define Your Input Configuration

Construct a JSON payload matching your data requirements. For high-volume discovery jobs, keep detail page enrichment disabled to maximize extraction speed.

{
  "searchTerms": ["machine learning"],
  "countries": ["US"],
  "dateOption": "last-30-days",
  "maxResults": 100,
  "enrichWithDetailPage": false
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Trigger the Job via API or Client

Pass the JSON payload to the actor using the Apify Python client:

from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")

run_input = {
    "searchTerms": ["machine learning"],
    "countries": ["US"],
    "dateOption": "last-30-days",
    "maxResults": 100,
    "enrichWithDetailPage": False
}

run = client.actor("crawlerbros/linkedin-ads-scraper").call(run_input=run_input)
dataset_items = client.dataset(run["defaultDatasetId"]).list_items().items
Enter fullscreen mode Exit fullscreen mode

Step 3: Parse and Process Output Records

Read the returned dataset records. When zero matching ads are found for a given set of parameters, the scraper exits cleanly. If an entire run yields no results, a single record containing "type": "linkedin_ads_blocked" is emitted to ensure automated downstream ETL pipelines receive valid JSON without failing unexpectedly.

Trade-offs: When to Enable enrichWithDetailPage

By default, enrichWithDetailPage is set to false. Initial search result pages contain top-level metrics, short body previews (body_text), headlines, media links, and destination URLs. This provides fast data collection for quick creative audits.

Setting enrichWithDetailPage to true instructs the scraper to navigate directly to /ad-library/detail/{id} for every ad extracted from the search index.

This detail fetch appends three specific fields:

  1. paid_for_by: The underlying legal entity paying for the placement (which often differs from the brand name shown on the ad).
  2. body_text_full: Complete copy without character truncations applied by search previews.
  3. advertiser_url: The canonical corporate LinkedIn URL.

This enrichment step introduces a delay of roughly 1 to 2 seconds per ad. For a run returning 500 items, detailed enrichment can add 10 to 15 minutes to job duration. Enable this parameter only when regulatory compliance checks or complete legal entity matching are mandatory.

Event-Based Pricing Structure

This actor uses a PAY_PER_EVENT pricing model. Charges are calculated exclusively from two discrete event types:

  • Actor Start (apify-actor-start): $0.005 per GB of memory allocated to the run, billed once upon execution startup.
  • Dataset Result (apify-default-dataset-item): $0.005 per result item pushed to the default dataset.

For the result event, volume-based tiering scales down unit costs based on overall usage:

  • FREE: $0.005 per result
  • BRONZE: $0.00433 per result
  • SILVER: $0.00367 per result
  • GOLD / PLATINUM / DIAMOND: $0.003 per result

Extracting 100 ad results on a standard run using 1 GB of memory costs $0.005 for the actor start event plus $0.50 for the result items (at $0.005 per event), totaling $0.505.

Data Limitations

This tool operates strictly on data published to the public LinkedIn Ad Library interface. It cannot pull campaign metadata from inside private LinkedIn Campaign Manager accounts. Performance metrics such as impressions and explicit demographic targeting facets are only provided by LinkedIn within jurisdictions governed by the EU Digital Services Act (DSA) for ads meeting minimum delivery thresholds. Outside these regulated regions, LinkedIn omits targeting and impression counts entirely from public views.


Source for the runs in this article: LinkedIn Ads Scraper. The input schema there is authoritative; treat anything in this post that contradicts it as out of date.

Top comments (0)