LinkedIn Scraping · 2026

How to Scrape LinkedIn Without Getting Banned in 2026

Feb 19, 2026 9 min read by Virix Labs

⚠️ Legal note: This guide is for educational purposes. Always comply with LinkedIn's Terms of Service and applicable law. Scraping publicly available data for research is generally permitted under fair use principles, but commercial use at scale may require LinkedIn's permission.

LinkedIn is one of the hardest sites to scrape in 2026. It runs DataDome on top of its own anti-bot system, checks IP reputation on every request, and uses machine-learning behavioral analysis. Here's what actually works.

How LinkedIn detects scrapers

LinkedIn's detection system runs four layers:

  1. IP reputation check — data center IPs are blocked before any page loads. Score checked against DataDome's threat intelligence database in <1ms.
  2. Browser fingerprint — checks navigator properties, WebGL renderer, audio context, canvas rendering. Vanilla Playwright fails this.
  3. Behavioral analysis — mouse movement patterns, scroll timing, click coordinates. Linear/instant movements are flagged.
  4. Session velocity — more than 80-100 profile views per day from one IP triggers rate limiting. More than 200 triggers permanent IP ban.

The working stack in 2026

Requirements

  • Residential IP from a real ISP (not datacenter, not hosting ASN)
  • Mobile Safari fingerprint (iPhone — lower bot score than desktop Chrome)
  • Sticky session (same IP throughout a scraping session)
  • Human-like delays: 3-8 seconds between page loads, random scroll patterns
  • Session warm-up: start with a non-profile page before scraping target profiles

Working code example

const { launchHuman } = require('human-browser');

async function scrapeLinkedInProfile(profileUrl) {
  const { page, humanScroll, humanRead, sleep } = await launchHuman({
    country: 'us',           // match target audience
    stickySession: true,     // same IP throughout
  });

  // Warm up: start from homepage, not profile directly
  await page.goto('https://linkedin.com', { waitUntil: 'networkidle' });
  await humanRead(page);   // random 2-5s read pause
  await humanScroll(page, 'down');

  // Navigate to search first (more human-like)
  await page.goto('https://linkedin.com/search/results/people/', { waitUntil: 'networkidle' });
  await sleep(2000 + Math.random() * 3000);

  // Then navigate to target profile
  await page.goto(profileUrl, { waitUntil: 'networkidle' });
  await humanRead(page);
  await humanScroll(page, 'down');

  // Extract data
  const name = await page.$eval('h1', el => el.textContent.trim()).catch(() => null);
  const headline = await page.$eval('.text-body-medium', el => el.textContent.trim()).catch(() => null);
  const location = await page.$eval('.text-body-small.inline', el => el.textContent.trim()).catch(() => null);

  await page.close();
  return { name, headline, location, url: profileUrl };
}

// Rate limiting: 50-80 profiles per IP, then rotate
const profiles = ['https://linkedin.com/in/profile-1', 'https://linkedin.com/in/profile-2'];
for (const url of profiles) {
  const data = await scrapeLinkedInProfile(url);
  console.log(data);
  // Random delay between profiles: 45-120 seconds
  await new Promise(r => setTimeout(r, 45000 + Math.random() * 75000));
}

Rate limits and IP rotation strategy

Per-IP limits (2026)

  • Safe zone: 50-70 profile views/day per IP
  • Warning zone: 70-120 views/day — occasional CAPTCHA challenges
  • Ban zone: 120+ views/day — IP gets blocked for 24-48 hours
  • Permanent ban: 300+ views/day — IP added to permanent blocklist

Rotation strategy

// Rotate IPs every 50 profiles
const PROFILES_PER_IP = 50;
let profileCount = 0;
let { page, humanRead, sleep } = await launchHuman({ stickySession: true });

for (const profileUrl of allProfiles) {
  if (profileCount >= PROFILES_PER_IP) {
    await page.close();
    // New session = new residential IP
    ({ page, humanRead, sleep } = await launchHuman({ stickySession: true }));
    profileCount = 0;
    // Longer break between IPs
    await sleep(10000 + Math.random() * 20000);
  }

  const data = await scrapeProfile(page, profileUrl, { humanRead, sleep });
  results.push(data);
  profileCount++;

  // Delay between profiles
  await sleep(45000 + Math.random() * 75000);
}

Scraping LinkedIn without logging in

LinkedIn shows limited profile data to non-logged-in users, but it's often enough for lead generation:

  • Full name ✅
  • Headline/title ✅
  • Location ✅
  • Current company ✅
  • Profile photo ✅
  • Employment history ❌ (logged-in only)
  • Contact info ❌ (connections only)

For more data, you can use a logged-in account — but be careful, LinkedIn accounts used for automated scraping get restricted quickly if not managed carefully.

Scraping LinkedIn company pages

Company pages have lower detection sensitivity than profile pages. You can scrape company details, employee counts, and recent posts at 2-3x the rate of profile scraping:

await page.goto('https://linkedin.com/company/openai', { waitUntil: 'networkidle' });
await humanScroll(page, 'down');

const companyData = {
  name: await page.$eval('h1', e => e.textContent.trim()).catch(() => null),
  size: await page.$eval('[data-test-id="about-us__size"]', e => e.textContent.trim()).catch(() => null),
  industry: await page.$eval('[data-test-id="about-us__industry"]', e => e.textContent.trim()).catch(() => null),
  followers: await page.$eval('.org-top-card-summary__follower-count', e => e.textContent.trim()).catch(() => null),
};

Human Browser — residential proxy for LinkedIn scraping

iPhone fingerprint + Romania or US residential IP. Passes LinkedIn's DataDome detection out of the box.

Get Started — from $13.99/mo →