⚠️ 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.
LinkedIn's detection system runs four layers:
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));
}
// 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);
}
LinkedIn shows limited profile data to non-logged-in users, but it's often enough for lead generation:
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.
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),
};
iPhone fingerprint + Romania or US residential IP. Passes LinkedIn's DataDome detection out of the box.
Get Started — from $13.99/mo →