Back to Blog
Tutorial

Ad Library Scraping: Track Competitor Ads on Facebook, Google & LinkedIn

April 25, 2026
7 min read
S
By SociaVault Team
Ad LibraryFacebook AdsGoogle AdsLinkedIn AdsCompetitor AnalysisAPI

Ad Library Scraping: Track Competitor Ads on Facebook, Google & LinkedIn

Every major ad platform now has a public ad library. Facebook, Google, and LinkedIn all let you see any advertiser's active ads. The problem? Their interfaces are slow, clunky, and impossible to use at scale.

With SociaVault's ad library endpoints, you can pull competitor ads programmatically, analyze creative patterns, track when ads go live and die, and spot messaging shifts before they're obvious.


Facebook Ad Library

Meta's Ad Library is the most comprehensive. Every active ad on Facebook and Instagram is visible, along with spending ranges for political ads.

Search for Competitor Ads

const API_KEY = process.env.SOCIAVAULT_API_KEY;
const BASE = 'https://api.sociavault.com/v1/scrape';
const headers = { 'X-API-Key': API_KEY };

async function searchFacebookAds(query) {
  const res = await fetch(
    `${BASE}/facebook-ad-library?query=${encodeURIComponent(query)}`,
    { headers }
  );
  const ads = (await res.json()).data || [];

  console.log(`\nFacebook Ad Library: "${query}" — ${ads.length} ads found\n`);

  ads.slice(0, 10).forEach((ad, i) => {
    console.log(`${i + 1}. ${ad.page_name || ad.advertiser || 'Unknown advertiser'}`);
    console.log(`   Status: ${ad.ad_delivery_start_time ? 'Active since ' + ad.ad_delivery_start_time : 'Active'}`);
    console.log(`   Platform: ${(ad.publisher_platforms || []).join(', ') || 'Facebook'}`);
    console.log(`   Creative: ${(ad.ad_creative_body || ad.body || ad.text || '').substring(0, 150)}`);
    
    if (ad.spend) {
      console.log(`   Spend: ${ad.spend.lower_bound || '?'} - ${ad.spend.upper_bound || '?'} ${ad.currency || 'USD'}`);
    }
    console.log('');
  });

  return ads;
}

searchFacebookAds('hubspot');

Analyze Ad Creative Patterns

Break down what a competitor's ad strategy looks like:

async function analyzeAdCreatives(query) {
  const res = await fetch(
    `${BASE}/facebook-ad-library?query=${encodeURIComponent(query)}`,
    { headers }
  );
  const ads = (await res.json()).data || [];

  // Categorize by format
  const formats = { image: 0, video: 0, carousel: 0, other: 0 };
  const ctaTypes = {};
  const headlines = [];

  for (const ad of ads) {
    // Format detection
    const type = ad.ad_creative_link_captions?.length > 1 ? 'carousel'
               : ad.ad_creative_videos?.length ? 'video'
               : ad.ad_creative_bodies?.length ? 'image'
               : 'other';
    formats[type]++;

    // CTA tracking
    const cta = ad.ad_creative_link_caption || ad.cta_type || 'none';
    ctaTypes[cta] = (ctaTypes[cta] || 0) + 1;

    // Collect headlines
    const headline = ad.ad_creative_link_title || ad.title || '';
    if (headline) headlines.push(headline);
  }

  console.log(`\nAd Creative Analysis: "${query}"`);
  console.log('─'.repeat(50));
  console.log(`  Total ads: ${ads.length}`);
  console.log(`\n  Format Mix:`);
  for (const [format, count] of Object.entries(formats)) {
    if (count === 0) continue;
    const pct = ((count / ads.length) * 100).toFixed(0);
    console.log(`    ${format.padEnd(12)} ${count} ads (${pct}%)`);
  }

  console.log(`\n  Top CTAs:`);
  const sortedCtas = Object.entries(ctaTypes).sort(([,a], [,b]) => b - a);
  sortedCtas.slice(0, 5).forEach(([cta, count]) => {
    console.log(`    ${cta.padEnd(20)} ${count}`);
  });

  // Common headline words
  const words = headlines.join(' ').toLowerCase().split(/\s+/);
  const wordFreq = {};
  const stopWords = new Set(['the', 'a', 'an', 'in', 'on', 'for', 'to', 'and', 'or', 'your', 'with', 'our', 'is', 'of']);
  for (const w of words) {
    const clean = w.replace(/[^a-z]/g, '');
    if (clean.length > 2 && !stopWords.has(clean)) {
      wordFreq[clean] = (wordFreq[clean] || 0) + 1;
    }
  }

  console.log(`\n  Top Headline Words:`);
  Object.entries(wordFreq).sort(([,a], [,b]) => b - a).slice(0, 10).forEach(([word, count]) => {
    console.log(`    ${word.padEnd(15)} ${count}`);
  });

  return { formats, ctaTypes, headlines, total: ads.length };
}

analyzeAdCreatives('hubspot');

Google's ad transparency center lets you see ads from any advertiser across Search, YouTube, and Display:

async function searchGoogleAds(query) {
  const res = await fetch(
    `${BASE}/google-ad-library?query=${encodeURIComponent(query)}`,
    { headers }
  );
  const ads = (await res.json()).data || [];

  console.log(`\nGoogle Ad Library: "${query}" — ${ads.length} ads found\n`);

  ads.slice(0, 10).forEach((ad, i) => {
    console.log(`${i + 1}. ${ad.advertiser_name || ad.advertiser || 'Unknown'}`);
    console.log(`   Format: ${ad.format || ad.type || 'N/A'}`);
    console.log(`   Region: ${(ad.region || ad.geo_targeting || []).join?.(', ') || 'N/A'}`);
    console.log(`   Last shown: ${ad.last_shown || ad.last_seen || 'N/A'}`);
    console.log(`   Text: ${(ad.text || ad.description || ad.creative_text || '').substring(0, 150)}`);
    console.log('');
  });

  return ads;
}

searchGoogleAds('semrush');

LinkedIn Ad Library

LinkedIn's ad library is the newest. See what B2B competitors are running:

async function searchLinkedInAds(query) {
  const res = await fetch(
    `${BASE}/linkedin-ad-library?query=${encodeURIComponent(query)}`,
    { headers }
  );
  const ads = (await res.json()).data || [];

  console.log(`\nLinkedIn Ad Library: "${query}" — ${ads.length} ads found\n`);

  ads.slice(0, 10).forEach((ad, i) => {
    console.log(`${i + 1}. ${ad.advertiser_name || ad.advertiser || 'Unknown'}`);
    console.log(`   Headline: ${ad.headline || ad.title || 'N/A'}`);
    console.log(`   Body: ${(ad.body || ad.text || '').substring(0, 150)}`);
    console.log(`   CTA: ${ad.cta || ad.call_to_action || 'N/A'}`);
    console.log(`   Started: ${ad.start_date || ad.created_at || 'N/A'}`);
    console.log('');
  });

  return ads;
}

searchLinkedInAds('salesforce');

Cross-Platform Ad Comparison

Compare a competitor's ad strategy across all three platforms:

async function crossPlatformAdAnalysis(brand) {
  console.log(`\nCross-Platform Ad Analysis: "${brand}"`);
  console.log('═'.repeat(60));

  // Facebook
  const fbRes = await fetch(
    `${BASE}/facebook-ad-library?query=${encodeURIComponent(brand)}`,
    { headers }
  );
  const fbAds = (await fbRes.json()).data || [];

  // Google
  const gRes = await fetch(
    `${BASE}/google-ad-library?query=${encodeURIComponent(brand)}`,
    { headers }
  );
  const gAds = (await gRes.json()).data || [];

  // LinkedIn
  const liRes = await fetch(
    `${BASE}/linkedin-ad-library?query=${encodeURIComponent(brand)}`,
    { headers }
  );
  const liAds = (await liRes.json()).data || [];

  console.log(`\n  Platform        Active Ads`);
  console.log(`  ${'─'.repeat(35)}`);
  console.log(`  Facebook/IG     ${fbAds.length}`);
  console.log(`  Google          ${gAds.length}`);
  console.log(`  LinkedIn        ${liAds.length}`);
  console.log(`  ${'─'.repeat(35)}`);
  console.log(`  Total           ${fbAds.length + gAds.length + liAds.length}`);

  // Which platform they invest most in
  const biggest = [
    { platform: 'Facebook/IG', count: fbAds.length },
    { platform: 'Google', count: gAds.length },
    { platform: 'LinkedIn', count: liAds.length }
  ].sort((a, b) => b.count - a.count)[0];

  console.log(`\n  Primary ad platform: ${biggest.platform} (${biggest.count} active ads)`);

  return {
    brand,
    facebook: fbAds.length,
    google: gAds.length,
    linkedin: liAds.length,
    total: fbAds.length + gAds.length + liAds.length
  };
}

crossPlatformAdAnalysis('hubspot');

Ad Tracking Over Time

Monitor when competitors launch and kill ads:

import os
import json
import time
import requests
from datetime import date

API_KEY = os.environ["SOCIAVAULT_API_KEY"]
BASE = "https://api.sociavault.com/v1/scrape"
HEADERS = {"X-API-Key": API_KEY}

def track_ad_changes(brands, filename="ad-tracker.json"):
    """Track competitor ad changes over time"""
    
    # Load previous snapshot
    try:
        with open(filename) as f:
            history = json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        history = {"snapshots": []}

    today = str(date.today())
    snapshot = {"date": today, "brands": {}}

    for brand in brands:
        print(f"Scanning ads for: {brand}")
        
        # Facebook ads
        r = requests.get(f"{BASE}/facebook-ad-library", headers=HEADERS, params={"query": brand})
        fb_ads = r.json().get("data", [])
        time.sleep(1)

        # Google ads
        r = requests.get(f"{BASE}/google-ad-library", headers=HEADERS, params={"query": brand})
        g_ads = r.json().get("data", [])
        time.sleep(1)

        snapshot["brands"][brand] = {
            "facebook_count": len(fb_ads),
            "google_count": len(g_ads),
            "total": len(fb_ads) + len(g_ads),
            "facebook_headlines": [
                (ad.get("ad_creative_link_title") or ad.get("title", ""))[:100]
                for ad in fb_ads[:5]
            ]
        }

    history["snapshots"].append(snapshot)

    # Compare with previous
    if len(history["snapshots"]) >= 2:
        prev = history["snapshots"][-2]
        print(f"\nChanges since {prev['date']}:")
        print("-" * 50)
        
        for brand in brands:
            curr = snapshot["brands"].get(brand, {})
            old = prev["brands"].get(brand, {})
            
            fb_diff = curr.get("facebook_count", 0) - old.get("facebook_count", 0)
            g_diff = curr.get("google_count", 0) - old.get("google_count", 0)
            
            print(f"\n  {brand}:")
            print(f"    Facebook: {curr.get('facebook_count', 0)} ({fb_diff:+d})")
            print(f"    Google:   {curr.get('google_count', 0)} ({g_diff:+d})")

            if fb_diff > 5:
                print(f"    ⚠️  Major Facebook ad ramp — possible new campaign")
            if fb_diff < -5:
                print(f"    📉 Significant ad reduction — campaign may have ended")

    with open(filename, "w") as f:
        json.dump(history, f, indent=2)

    print(f"\nSnapshot saved: {today}")

# Run weekly
track_ad_changes(["hubspot", "salesforce", "mailchimp", "semrush"])

What to Look for in Competitor Ads

SignalWhat It Means
Many ads with same offerThey found a winner, testing variations
Short-lived ads (1-3 days)Testing phase, or ads that didn't perform
Long-running ads (30+ days)Proven winner, cost-effective creative
Sudden ad count spikeNew campaign launch or seasonal push
Video-heavy creative mixInvesting in brand awareness
Direct response CTAs ("Try free")Performance marketing focused
Comparison ads mentioning youThey see you as a threat
Job posting adsScaling up, possible product launch coming

All three ad libraries (Facebook, Google, LinkedIn) are publicly accessible by design. The platforms created these for transparency purposes. You're accessing the same data any person could see by visiting the ad library websites — just doing it more efficiently via API.


Get Started

Sign up free — start tracking competitor ads across all major platforms today.


Found this helpful?

Share it with others who might benefit

Ready to Try SociaVault?

Start extracting social media data with our powerful API. No credit card required.