LinkedIn
Job Listings
Anti-Bot
How to Scrape LinkedIn Job Listings
Without Getting Blocked (2026)
📅 March 12, 2026
•
⏱ 10 min read
•
By Papalily Team
Developers who want to scrape LinkedIn job listings run into a wall almost immediately.
LinkedIn has some of the most aggressive bot detection on the internet — rate limiting, IP bans, CAPTCHA
walls, and login requirements that kick in after just a few page loads. Yet the data is genuinely valuable:
job boards, HR tools, recruiting platforms, and labor market researchers all want it.
This guide covers why LinkedIn is uniquely difficult, what data you can realistically extract from public
job pages, and working code examples using a real-browser approach that sidesteps most of the blocking.
⚠ Note: This guide covers scraping publicly accessible LinkedIn job listings
only — the same data visible to any anonymous browser. Scraping private profiles, messages, or content
behind a login is a separate matter entirely, both technically and legally. Always review LinkedIn's
Terms of Service and applicable laws before scraping.
Why Scraping LinkedIn Job Listings Is Hard
LinkedIn is a React-based single-page application. That alone disqualifies simple HTTP scrapers —
curl or requests will return a mostly empty HTML shell. But LinkedIn goes much
further than just using JavaScript:
-
Aggressive rate limiting: After 5–10 requests from the same IP, LinkedIn starts serving
CAPTCHAs, then blocks the IP entirely. The threshold is lower for non-logged-in browsers.
-
Browser fingerprinting: LinkedIn detects headless Chromium and Playwright by checking
dozens of browser properties — navigator plugins, WebGL renderer, canvas fingerprint, and more.
Standard headless browsers fail these checks.
-
Dynamic class names: LinkedIn uses compiled CSS with hashed class names that change
between builds. Any CSS-selector-based scraper breaks on every deployment.
-
Lazy-loaded content: Job listings load incrementally as you scroll. The first 25 results
are visible on initial render; subsequent ones require scroll events.
-
Login wall for details: Full job descriptions and some metadata require a LinkedIn account.
The publicly accessible data is limited to title, company, location, and listing age.
What You Can Realistically Extract
From LinkedIn's public job search pages (no login required), you can extract:
- Job title
- Company name
- Location (city/country or "Remote")
- Time since posted ("2 hours ago", "3 days ago")
- Employment type (Full-time, Contract, Part-time) — when shown
- Job listing URL (the
/jobs/view/ permalink)
- Company logo URL
- Number of applicants — when shown ("Over 200 applicants")
Full job descriptions require navigating to each individual listing URL. You can extract those too,
but each listing is a separate request with its own rendering time.
Rate Limiting and Respectful Scraping
Even with a real browser approach, being respectful matters — both ethically and practically.
Aggressive scraping gets you blocked faster and puts undue load on servers.
- Add delays between requests: at least 5–10 seconds between pages
- Don't scrape the same search URL more than once per hour
- Cache results locally and use
no_cache: false to serve cached data when freshness isn't critical
- Target specific search queries rather than broad crawls
Working Code Examples
Node.js — Scrape a LinkedIn Job Search Page
const API_KEY = process.env.PAPALILY_API_KEY;
async function scrapeLinkedInJobs(keywords, location) {
// Build the LinkedIn job search URL
const query = new URLSearchParams({
keywords,
location,
f_TPR: 'r86400', // Last 24 hours
sortBy: 'DD', // Date descending
});
const linkedInUrl = `https://www.linkedin.com/jobs/search/?${query}`;
const res = await fetch('https://api.papalily.com/scrape', {
method: 'POST',
headers: {
'x-api-key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: linkedInUrl,
prompt: 'Extract all job listings visible on the page. For each job, return: title, company, location, time_posted, employment_type, applicant_count, and job_url. Return as a JSON array called "jobs".',
wait_ms: 4000, // LinkedIn needs time to hydrate
}),
});
const result = await res.json();
if (!result.success) {
throw new Error(`Scrape failed: ${result.error}`);
}
return result.data.jobs || [];
}
// Example usage
const jobs = await scrapeLinkedInJobs('software engineer', 'San Francisco');
console.log(`Found ${jobs.length} jobs`);
jobs.forEach(j => console.log(`${j.title} @ ${j.company} — ${j.location}`));
Python — Scrape and Save to CSV
import requests
import csv
import os
from urllib.parse import urlencode
API_KEY = os.environ['PAPALILY_API_KEY']
def scrape_linkedin_jobs(keywords, location, days_back=1):
"""Scrape LinkedIn job listings for given keywords and location."""
params = urlencode({
'keywords': keywords,
'location': location,
'f_TPR': f'r{days_back * 86400}',
'sortBy': 'DD',
})
url = f'https://www.linkedin.com/jobs/search/?{params}'
resp = requests.post(
'https://api.papalily.com/scrape',
headers={'x-api-key': API_KEY},
json={
'url': url,
'prompt': 'Extract all job listings. Return title, company, location, time_posted, and job_url for each.',
'wait_ms': 4000,
},
timeout=60
)
data = resp.json()
return data.get('data', {}).get('jobs', [])
def save_to_csv(jobs, filename):
if not jobs:
print('No jobs to save')
return
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=jobs[0].keys())
writer.writeheader()
writer.writerows(jobs)
print(f'Saved {len(jobs)} jobs to {filename}')
# Run it
jobs = scrape_linkedin_jobs('data engineer', 'Remote')
save_to_csv(jobs, 'linkedin_jobs.csv')
cURL — Quick Test
curl -X POST https://api.papalily.com/scrape \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.linkedin.com/jobs/search/?keywords=python+developer&location=London",
"prompt": "Get all job listings with title, company, location, time posted, and job URL",
"wait_ms": 4000
}'
Use Cases for LinkedIn Job Data
Once you have the data, what can you build?
-
Custom job board — aggregate listings from LinkedIn, Indeed, Glassdoor, and company
career pages into a single searchable interface for a specific niche (e.g., "remote ML engineering jobs")
-
Recruiting tool — monitor specific companies for new openings and alert your team
when a target company is hiring for roles you can fill
-
Labor market research — track hiring trends by role, location, and seniority level
over time to spot emerging skill demands
-
Salary intelligence — when salary ranges are included in listings, aggregate them
by role and location for compensation benchmarking
-
Competitor intelligence — see who your competitors are hiring, which signals their
strategic priorities (e.g., a company suddenly hiring 10 ML engineers)
A Note on LinkedIn's Terms of Service
LinkedIn's Terms of Service prohibit automated data collection without permission. However, the legal
landscape around scraping publicly available data is complex and evolving. The hiQ Labs vs.
LinkedIn case (decided multiple times in the US Ninth Circuit) established that scraping publicly
accessible data may be protected under the Computer Fraud and Abuse Act's limitations. That said,
every situation is different.
Best practices: only scrape public data, respect rate limits, don't circumvent authentication,
and consult legal counsel if you're building a commercial product on this data.
Ready to Scrape LinkedIn Jobs?
Papalily's real-browser rendering handles LinkedIn's JavaScript, anti-bot measures, and dynamic content.
Get your free API key — 100 requests/month, no credit card.
Get Free API Key on RapidAPI →
Full docs at papalily.com/docs