Back to Blog
Guide

LinkedIn Profile Scraper: Extract Company & People Data for Lead Generation

October 24, 2025
16 min read
By SociaVault Team
LinkedIn ScraperB2B Lead GenerationLinkedIn Data ExtractionSales IntelligenceRecruiting Tools

LinkedIn Profile Scraper: Extract Company & People Data for Lead Generation

You find the perfect prospect on LinkedIn. VP of Sales. 500+ connections. Active poster. Perfect fit.

You click "Connect." LinkedIn limits you to 100 invites per week.

You want to enrich their profile data. LinkedIn's API gives you... their name. That's it. No email. No company details. No posts. Nothing useful.

You try their "official" Sales Navigator. $100/month. Still limited. Still manual. Still slow.

Meanwhile, your competitor is pulling 10,000 LinkedIn profiles per day, enriching them with company data, finding emails, and closing deals.

How? They're not using LinkedIn's terrible API.

This guide shows you how to extract LinkedIn data at scale for lead generation, recruiting, and market research. Real code. Real use cases. No limitations.

Why LinkedIn Data is the B2B Goldmine

LinkedIn isn't social media. It's a database of 900 million professionals who list:

  • Their job title (targeting decision makers)
  • Their company (finding enterprise accounts)
  • Their experience (vetting candidates)
  • Their posts (gauging engagement)
  • Their connections (mapping networks)

For sales teams: Find and qualify leads For recruiters: Source passive candidates For marketers: Build target account lists For researchers: Understand market landscapes

The problem? LinkedIn doesn't want you to have this data at scale.

LinkedIn's API Is Intentionally Useless

Let's be brutally honest about LinkedIn's official API:

What you get:

  • Basic profile info (name, headline, profile URL)
  • Limited company info
  • No contact details
  • No posts
  • No connections
  • Heavy rate limits

What you need but can't get:

  • Email addresses
  • Phone numbers
  • Full work history
  • Skills and endorsements
  • Posts and engagement
  • Connection networks
  • Company employee lists

Cost: Free tier gives you almost nothing. Premium API costs thousands per month and still limits you.

LinkedIn's strategy: Force you to use Sales Navigator ($100/month) or Recruiter ($1,000+/month) manually. One profile at a time. Forever.

What You Can Actually Extract

Here's what's publicly available on LinkedIn (and perfectly legal to extract):

Profile Data

  • Full name, headline, location
  • Current company and position
  • Work history (past roles)
  • Education history
  • Skills (listed publicly)
  • Profile URL and custom URL
  • Follower count
  • Connection count (range)
  • Profile photo
  • Banner image
  • About section / bio
  • Featured section

Company Data

  • Company name and size
  • Industry and specialties
  • Location and headquarters
  • Website URL
  • Follower count
  • Company description
  • Employee count (range)
  • Founding year

Post Data

  • Text content
  • Images and videos
  • Engagement (likes, comments, shares)
  • Post timestamp
  • Hashtags used
  • Comments (public)

What you CAN'T extract:

  • Private messages
  • Email addresses (not publicly visible)
  • Phone numbers (not publicly visible)
  • Connection lists (behind login)
  • Private profiles

Method 1: Extract LinkedIn Profile Data

Let's extract a LinkedIn profile with all publicly available data.

JavaScript Example

const axios = require('axios');

const SOCIAVAULT_API_KEY = process.env.SOCIAVAULT_API_KEY;
const BASE_URL = 'https://api.sociavault.com';

async function getLinkedInProfile(profileUrl) {
  try {
    const response = await axios.get(`${BASE_URL}/api/scrape/linkedin/profile`, {
      params: {
        url: profileUrl
      },
      headers: {
        'X-API-Key': SOCIAVAULT_API_KEY
      }
    });

    return response.data;
  } catch (error) {
    console.error('Error fetching LinkedIn profile:', error.message);
    throw error;
  }
}

// Usage
const profileUrl = 'https://www.linkedin.com/in/example-profile/';
const profile = await getLinkedInProfile(profileUrl);

console.log('Profile Data:');
console.log(`Name: ${profile.fullName}`);
console.log(`Headline: ${profile.headline}`);
console.log(`Location: ${profile.location}`);
console.log(`Company: ${profile.currentCompany}`);
console.log(`Position: ${profile.currentPosition}`);
console.log(`Connections: ${profile.connectionCount}`);
console.log(`\nWork History:`);
profile.workHistory.forEach((job, i) => {
  console.log(`${i + 1}. ${job.title} at ${job.company} (${job.duration})`);
});

Python Example

import requests
import os

SOCIAVAULT_API_KEY = os.getenv('SOCIAVAULT_API_KEY')
BASE_URL = 'https://api.sociavault.com'

def get_linkedin_profile(profile_url):
    """Extract LinkedIn profile data"""
    response = requests.get(
        f'{BASE_URL}/api/scrape/linkedin/profile',
        params={'url': profile_url},
        headers={'X-API-Key': SOCIAVAULT_API_KEY}
    )
    response.raise_for_status()
    return response.json()

# Usage
profile_url = 'https://www.linkedin.com/in/example-profile/'
profile = get_linkedin_profile(profile_url)

print(f"Name: {profile['fullName']}")
print(f"Headline: {profile['headline']}")
print(f"Company: {profile['currentCompany']}")
print(f"Position: {profile['currentPosition']}")
print(f"Location: {profile['location']}")
print(f"\nSkills: {', '.join(profile['skills'][:10])}")

Cost: 1 credit per profile

Use Case 1: Sales Lead Enrichment

You have a list of LinkedIn URLs from your CRM. You want to enrich them with current job titles, companies, and verify they're still at the same company.

const { Pool } = require('pg');

const pool = new Pool({
  connectionString: process.env.DATABASE_URL
});

async function enrichLeads() {
  // Get leads that need enrichment
  const { rows: leads } = await pool.query(`
    SELECT id, linkedin_url, last_enriched
    FROM leads
    WHERE linkedin_url IS NOT NULL
      AND (last_enriched IS NULL OR last_enriched < NOW() - INTERVAL '30 days')
    LIMIT 100
  `);

  console.log(`Enriching ${leads.length} leads...`);

  for (const lead of leads) {
    try {
      const profile = await getLinkedInProfile(lead.linkedin_url);

      // Update lead with fresh data
      await pool.query(`
        UPDATE leads
        SET
          full_name = $1,
          job_title = $2,
          company = $3,
          location = $4,
          headline = $5,
          connection_count = $6,
          skills = $7,
          last_enriched = NOW()
        WHERE id = $8
      `, [
        profile.fullName,
        profile.currentPosition,
        profile.currentCompany,
        profile.location,
        profile.headline,
        profile.connectionCount,
        profile.skills,
        lead.id
      ]);

      console.log(`✅ Enriched: ${profile.fullName} - ${profile.currentPosition} at ${profile.currentCompany}`);

      // Rate limiting
      await new Promise(resolve => setTimeout(resolve, 2000));

    } catch (error) {
      console.error(`❌ Error enriching lead ${lead.id}:`, error.message);
    }
  }

  console.log('Enrichment complete!');
}

enrichLeads();

What this gives you:

  • Current job title (for targeting decision makers)
  • Company name (for account-based selling)
  • Location (for territory assignment)
  • Connection count (gauge their network)
  • Skills (understand their expertise)
  • Headline (see how they position themselves)

Sales use case:

  • Verify leads are still at the same company before calling
  • Prioritize leads with "VP" or "Director" titles
  • Filter by company size (connection count is a proxy)
  • Personalize outreach using their headline/skills

Use Case 2: Recruiter Candidate Sourcing

You're recruiting for a "Senior React Developer" role. You need to find candidates quickly.

import requests
import pandas as pd

def find_react_developers(count=100):
    """
    Find React developers on LinkedIn
    Note: You'd typically get LinkedIn URLs from:
    - LinkedIn search (manual)
    - Sales Navigator export
    - Recruiter search
    - Company employee lists
    - GitHub profiles with LinkedIn links
    """
    
    # Example: You have a list of LinkedIn URLs from a search
    linkedin_urls = [
        'https://www.linkedin.com/in/developer1/',
        'https://www.linkedin.com/in/developer2/',
        # ... more URLs from your search
    ]
    
    candidates = []
    
    for url in linkedin_urls:
        try:
            profile = get_linkedin_profile(url)
            
            # Filter for React developers
            skills = [s.lower() for s in profile.get('skills', [])]
            headline = profile.get('headline', '').lower()
            
            if 'react' in skills or 'react' in headline:
                # Calculate seniority score
                years_experience = calculate_experience(profile['workHistory'])
                
                candidate = {
                    'name': profile['fullName'],
                    'headline': profile['headline'],
                    'company': profile['currentCompany'],
                    'position': profile['currentPosition'],
                    'location': profile['location'],
                    'years_experience': years_experience,
                    'skills': profile['skills'],
                    'linkedin_url': url,
                    'profile_url': profile['profileUrl']
                }
                
                candidates.append(candidate)
                print(f"✅ Found: {candidate['name']} - {years_experience} years exp")
        
        except Exception as e:
            print(f"❌ Error: {e}")
        
        time.sleep(2)  # Rate limiting
    
    # Convert to DataFrame for analysis
    df = pd.DataFrame(candidates)
    
    # Filter by experience level
    senior_devs = df[df['years_experience'] >= 5]
    
    # Sort by experience
    senior_devs = senior_devs.sort_values('years_experience', ascending=False)
    
    # Export to CSV
    senior_devs.to_csv('senior_react_developers.csv', index=False)
    
    print(f"\nFound {len(senior_devs)} senior React developers")
    
    return senior_devs

def calculate_experience(work_history):
    """Calculate total years of experience"""
    total_months = 0
    for job in work_history:
        if 'duration' in job:
            # Parse duration like "2 yrs 3 mos"
            duration = job['duration']
            years = 0
            months = 0
            
            if 'yr' in duration:
                years = int(duration.split('yr')[0].strip().split()[-1])
            if 'mo' in duration:
                months = int(duration.split('mo')[0].strip().split()[-1])
            
            total_months += (years * 12) + months
    
    return round(total_months / 12, 1)

# Run the search
candidates = find_react_developers()

Recruiting workflow:

  1. Search LinkedIn manually for "Senior React Developer" in your target location
  2. Export URLs (LinkedIn doesn't let you export data, but you can collect URLs)
  3. Run this script to enrich each profile
  4. Filter by experience, location, skills
  5. Reach out to top candidates

Why this works:

  • LinkedIn Recruiter costs $1,000+/month and limits you to manual searches
  • This approach lets you build your own candidate database
  • You own the data (no vendor lock-in)
  • Can re-check profiles monthly to see job changes

Use Case 3: Company Employee Mapping

You're selling to enterprise companies. You need to map the org chart and find multiple decision makers.

async function mapCompanyEmployees(companyName) {
  /**
   * Note: LinkedIn's employee directory is often accessible via:
   * https://www.linkedin.com/company/{company}/people/
   * 
   * You'd typically:
   * 1. Get employee list from company page
   * 2. Extract each employee's profile
   * 3. Build org chart
   */
  
  // Example: You have a list of employee LinkedIn URLs
  const employeeUrls = [
    'https://www.linkedin.com/in/ceo-profile/',
    'https://www.linkedin.com/in/vp-sales/',
    'https://www.linkedin.com/in/vp-marketing/',
    // ... more employees
  ];
  
  const orgChart = {
    company: companyName,
    executives: [],
    sales: [],
    marketing: [],
    engineering: [],
    other: []
  };
  
  for (const url of employeeUrls) {
    try {
      const profile = await getLinkedInProfile(url);
      
      const employee = {
        name: profile.fullName,
        title: profile.currentPosition,
        headline: profile.headline,
        linkedIn: url,
        connections: profile.connectionCount,
        skills: profile.skills
      };
      
      // Categorize by department
      const title = profile.currentPosition.toLowerCase();
      
      if (title.includes('ceo') || title.includes('cto') || title.includes('cfo')) {
        orgChart.executives.push(employee);
      } else if (title.includes('sales') || title.includes('revenue')) {
        orgChart.sales.push(employee);
      } else if (title.includes('marketing')) {
        orgChart.marketing.push(employee);
      } else if (title.includes('engineer') || title.includes('developer')) {
        orgChart.engineering.push(employee);
      } else {
        orgChart.other.push(employee);
      }
      
      console.log(`✅ Mapped: ${employee.name} - ${employee.title}`);
      
      await new Promise(resolve => setTimeout(resolve, 2000));
      
    } catch (error) {
      console.error(`❌ Error mapping ${url}:`, error.message);
    }
  }
  
  // Save org chart
  const fs = require('fs');
  fs.writeFileSync(
    `${companyName.replace(/\s+/g, '_')}_org_chart.json`,
    JSON.stringify(orgChart, null, 2)
  );
  
  console.log('\n=== Org Chart Summary ===');
  console.log(`Executives: ${orgChart.executives.length}`);
  console.log(`Sales: ${orgChart.sales.length}`);
  console.log(`Marketing: ${orgChart.marketing.length}`);
  console.log(`Engineering: ${orgChart.engineering.length}`);
  
  return orgChart;
}

// Usage
const orgChart = await mapCompanyEmployees('Acme Corp');

Account-based selling strategy:

  1. Map the buying committee (who makes decisions?)
  2. Identify champions (who's active, influential?)
  3. Multi-thread (reach out to multiple stakeholders)
  4. Personalize outreach (reference their posts, skills)

Use Case 4: LinkedIn Post Engagement Analysis

Track what content resonates with your audience.

def analyze_linkedin_posts(profile_url, limit=20):
    """Analyze someone's recent LinkedIn posts"""
    
    # Get their profile first
    profile = get_linkedin_profile(profile_url)
    
    # Get their recent posts
    response = requests.get(
        f'{BASE_URL}/api/scrape/linkedin/posts',
        params={
            'url': profile_url,
            'limit': limit
        },
        headers={'X-API-Key': SOCIAVAULT_API_KEY}
    )
    
    posts = response.json()
    
    analysis = {
        'total_posts': len(posts),
        'total_likes': 0,
        'total_comments': 0,
        'total_shares': 0,
        'avg_engagement': 0,
        'best_post': None,
        'post_frequency': None
    }
    
    for post in posts:
        analysis['total_likes'] += post.get('likeCount', 0)
        analysis['total_comments'] += post.get('commentCount', 0)
        analysis['total_shares'] += post.get('shareCount', 0)
        
        # Find best performing post
        engagement = post.get('likeCount', 0) + post.get('commentCount', 0) * 2
        if analysis['best_post'] is None or engagement > analysis['best_post']['engagement']:
            analysis['best_post'] = {
                'text': post.get('text', '')[:200],
                'engagement': engagement,
                'likes': post.get('likeCount', 0),
                'comments': post.get('commentCount', 0),
                'url': post.get('postUrl', '')
            }
    
    if len(posts) > 0:
        analysis['avg_engagement'] = (
            analysis['total_likes'] + 
            analysis['total_comments'] + 
            analysis['total_shares']
        ) / len(posts)
    
    print(f"\n=== LinkedIn Post Analysis for {profile['fullName']} ===")
    print(f"Total Posts: {analysis['total_posts']}")
    print(f"Total Engagement: {analysis['total_likes'] + analysis['total_comments'] + analysis['total_shares']}")
    print(f"Avg Engagement: {analysis['avg_engagement']:.1f}")
    print(f"\nBest Post ({analysis['best_post']['engagement']} engagement):")
    print(analysis['best_post']['text'])
    
    return analysis

# Usage - analyze a thought leader in your space
analysis = analyze_linkedin_posts('https://www.linkedin.com/in/thought-leader/')

Use cases:

  • Identify influencers to partner with (high engagement = reach)
  • Content strategy (see what topics resonate in your industry)
  • Competitive intelligence (what are competitors posting?)
  • Lead scoring (active posters = engaged professionals)

Use Case 5: LinkedIn Company Data Extraction

Extract company information for market research or competitive analysis.

async function getCompanyData(companyUrl) {
  const response = await axios.get(`${BASE_URL}/api/scrape/linkedin/company`, {
    params: { url: companyUrl },
    headers: { 'X-API-Key': SOCIAVAULT_API_KEY }
  });
  
  return response.data;
}

async function analyzeCompetitors(competitorUrls) {
  const companies = [];
  
  for (const url of competitorUrls) {
    try {
      const company = await getCompanyData(url);
      
      companies.push({
        name: company.name,
        industry: company.industry,
        size: company.employeeCount,
        followers: company.followerCount,
        headquarters: company.location,
        website: company.website,
        description: company.description,
        specialties: company.specialties
      });
      
      console.log(`✅ Analyzed: ${company.name} (${company.employeeCount} employees)`);
      
      await new Promise(resolve => setTimeout(resolve, 2000));
      
    } catch (error) {
      console.error(`❌ Error:`, error.message);
    }
  }
  
  // Generate comparison report
  console.log('\n=== Competitive Analysis ===\n');
  
  companies.sort((a, b) => b.followers - a.followers);
  
  companies.forEach((company, i) => {
    console.log(`${i + 1}. ${company.name}`);
    console.log(`   Size: ${company.size} employees`);
    console.log(`   Followers: ${company.followers.toLocaleString()}`);
    console.log(`   Industry: ${company.industry}`);
    console.log(`   Location: ${company.headquarters}`);
    console.log('');
  });
  
  return companies;
}

// Usage - analyze your competitors
const competitors = [
  'https://www.linkedin.com/company/competitor1/',
  'https://www.linkedin.com/company/competitor2/',
  'https://www.linkedin.com/company/competitor3/'
];

const analysis = await analyzeCompetitors(competitors);

Market research applications:

  • Company sizing (employee count, growth rate)
  • Market positioning (how they describe themselves)
  • Hiring trends (growing teams = growth)
  • Competitive intelligence (who's similar to you?)

Best Practices for LinkedIn Scraping

1. Respect Rate Limits

Don't hammer LinkedIn. Space out your requests.

async function batchEnrich(urls, delayMs = 2000) {
  const results = [];
  
  for (const url of urls) {
    try {
      const data = await getLinkedInProfile(url);
      results.push(data);
      
      // Wait between requests
      await new Promise(resolve => setTimeout(resolve, delayMs));
      
    } catch (error) {
      console.error(`Error with ${url}:`, error.message);
      results.push({ url, error: error.message });
    }
  }
  
  return results;
}

2. Handle Errors Gracefully

Not all profiles are accessible. Some might be private or deleted.

def safe_extract(url):
    """Extract with error handling"""
    try:
        return get_linkedin_profile(url)
    except requests.exceptions.HTTPError as e:
        if e.response.status_code == 404:
            return {'error': 'Profile not found'}
        elif e.response.status_code == 403:
            return {'error': 'Access denied (private profile)'}
        else:
            return {'error': f'HTTP {e.response.status_code}'}
    except Exception as e:
        return {'error': str(e)}

3. Store Data for Historical Analysis

LinkedIn profiles change. Track changes over time.

CREATE TABLE linkedin_profiles (
  id SERIAL PRIMARY KEY,
  linkedin_url VARCHAR(500) UNIQUE,
  full_name VARCHAR(255),
  headline TEXT,
  current_company VARCHAR(255),
  current_position VARCHAR(255),
  location VARCHAR(255),
  connection_count INTEGER,
  skills TEXT[],
  work_history JSONB,
  last_updated TIMESTAMP DEFAULT NOW()
);

CREATE TABLE profile_history (
  id SERIAL PRIMARY KEY,
  profile_id INTEGER REFERENCES linkedin_profiles(id),
  company VARCHAR(255),
  position VARCHAR(255),
  detected_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_linkedin_url ON linkedin_profiles(linkedin_url);
CREATE INDEX idx_company ON linkedin_profiles(current_company);
CREATE INDEX idx_updated ON linkedin_profiles(last_updated);

4. Enrich With Additional Data

Combine LinkedIn data with other sources:

async function fullyEnrichLead(linkedinUrl) {
  // Get LinkedIn data
  const profile = await getLinkedInProfile(linkedinUrl);
  
  // Find email using Hunter.io or similar
  const email = await findEmail(profile.fullName, profile.currentCompany);
  
  // Get company data from Clearbit or similar
  const companyData = await getCompanyInfo(profile.currentCompany);
  
  // Combine everything
  return {
    ...profile,
    email,
    companyRevenue: companyData.revenue,
    companyFunding: companyData.funding,
    technographics: companyData.tech_stack
  };
}

5. Build a Warm Cache

Don't re-scrape the same profiles constantly. Cache for 30 days.

const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 2592000 }); // 30 days

async function getCachedProfile(url) {
  // Check cache first
  const cached = cache.get(url);
  if (cached) {
    console.log('✅ Cache hit');
    return cached;
  }
  
  // Fetch if not cached
  const profile = await getLinkedInProfile(url);
  cache.set(url, profile);
  
  return profile;
}

Is this legal? Yes, for public data.

Key points:

  • ✅ Public profiles are fair game (hiQ Labs vs LinkedIn court case)
  • ✅ You can extract publicly visible data
  • ✅ Commercial use is allowed
  • ❌ Don't access private profiles or data behind login
  • ❌ Don't use data for spam or harassment
  • ❌ Respect robots.txt (SociaVault handles this)

Best practice: Only extract what you need. Don't build a LinkedIn clone.

LinkedIn API vs SociaVault

Let's be honest about the comparison:

LinkedIn Official API

Pros:

  • "Official" (for what it's worth)
  • Stable schema

Cons:

  • Extremely limited data
  • No contact info
  • No posts
  • Heavy rate limits
  • Expensive ($3,000+/month for enterprise)
  • Requires OAuth (complicated setup)
  • Mostly useless for real use cases

SociaVault

Pros:

  • All publicly visible data
  • Simple API (just pass a URL)
  • Affordable ($0.001 per profile = 1 credit)
  • No rate limits (reasonable use)
  • Works immediately (no OAuth dance)
  • Both profiles and company data

Cons:

  • Not "official" (but 100% legal for public data)
  • Schema might change (we handle updates)

Bottom line: If you need actual LinkedIn data at scale, LinkedIn's API isn't an option. SociaVault is.

Pricing Comparison

LinkedIn Sales Navigator: $100/month

  • Manual profile viewing only
  • Limited export
  • 1 user

LinkedIn Recruiter: $1,000+/month

  • Manual searching
  • Limited InMail credits
  • 1 user

SociaVault:

  • $29 for 6,000 profiles (Growth Pack)
  • $79 for 20,000 profiles (Pro Pack)
  • Unlimited users
  • Full automation

Example: Enrich 5,000 leads

  • LinkedIn API: Not possible
  • Manual with Sales Navigator: 5,000 hours
  • SociaVault: $29 + 1 hour of code

Complete Lead Enrichment System

Here's a production-ready system for sales teams:

const cron = require('node-cron');

// Run daily at 6 AM
cron.schedule('0 6 * * *', async () => {
  console.log('Starting daily lead enrichment...');
  
  try {
    // Get leads that need enriching
    const leads = await getLeadsToEnrich();
    
    console.log(`Found ${leads.length} leads to enrich`);
    
    // Enrich in batches of 50
    for (let i = 0; i < leads.length; i += 50) {
      const batch = leads.slice(i, i + 50);
      
      await enrichBatch(batch);
      
      console.log(`Progress: ${Math.min(i + 50, leads.length)}/${leads.length}`);
      
      // Rate limiting between batches
      if (i + 50 < leads.length) {
        await new Promise(resolve => setTimeout(resolve, 60000)); // 1 min break
      }
    }
    
    // Send summary email
    await sendSummaryEmail(leads.length);
    
    console.log('Daily enrichment complete!');
    
  } catch (error) {
    console.error('Enrichment failed:', error);
    await sendErrorAlert(error);
  }
});

async function enrichBatch(leads) {
  const promises = leads.map(async (lead) => {
    try {
      const profile = await getCachedProfile(lead.linkedin_url);
      
      await pool.query(`
        UPDATE leads
        SET
          full_name = $1,
          job_title = $2,
          company = $3,
          location = $4,
          headline = $5,
          last_enriched = NOW()
        WHERE id = $6
      `, [
        profile.fullName,
        profile.currentPosition,
        profile.currentCompany,
        profile.location,
        profile.headline,
        lead.id
      ]);
      
      return { success: true, id: lead.id };
      
    } catch (error) {
      return { success: false, id: lead.id, error: error.message };
    }
  });
  
  const results = await Promise.all(promises);
  
  const successful = results.filter(r => r.success).length;
  const failed = results.filter(r => !r.success).length;
  
  console.log(`Batch complete: ${successful} success, ${failed} failed`);
  
  return results;
}

async function getLeadsToEnrich() {
  const { rows } = await pool.query(`
    SELECT id, linkedin_url
    FROM leads
    WHERE linkedin_url IS NOT NULL
      AND (
        last_enriched IS NULL 
        OR last_enriched < NOW() - INTERVAL '30 days'
      )
    ORDER BY created_at DESC
    LIMIT 500
  `);
  
  return rows;
}

Conclusion

LinkedIn's official API is a joke. It gives you almost nothing useful.

But LinkedIn profiles are public. The data is sitting there. You just need the right tool to extract it.

This guide showed you:

  • What data you can extract (profiles, companies, posts)
  • How to extract it (working code in JavaScript and Python)
  • Real use cases (sales, recruiting, research)
  • Production systems (automated enrichment)
  • Best practices (rate limits, caching, error handling)

For sales teams: Build a lead enrichment system that runs automatically every night. Wake up to fresh, accurate data.

For recruiters: Source passive candidates at scale. Build a talent database without paying $1,000/month for LinkedIn Recruiter.

For researchers: Map industries, track career movements, analyze company trends.

LinkedIn data is B2B gold. Don't let LinkedIn's terrible API hold you back.

Ready to start? Get your SociaVault API key at sociavault.com and enrich your first 1,000 profiles free.

Every day you wait, your competitors are building bigger, better databases. And you're still clicking profiles one by one.

Time to scale.

Found this helpful?

Share it with others who might benefit

Ready to Try SociaVault?

Start extracting social media data with our powerful API