LinkedIn Jobs Scraper by URL — Listings, Salaries (No Login) avatar

LinkedIn Jobs Scraper by URL — Listings, Salaries (No Login)

Pricing

from $1.00 / 1,000 result scrapeds

Go to Apify Store
LinkedIn Jobs Scraper by URL — Listings, Salaries (No Login)

LinkedIn Jobs Scraper by URL — Listings, Salaries (No Login)

Scrape LinkedIn job listings by search URL — extract job title, company, salary, location, skills and apply links. No login, no API key, no rate limits. Bypass anti-bot detection. CSV/JSON output. Pay per result. Proxycurl alternative for job board scraping and recruiter lead generation.

Pricing

from $1.00 / 1,000 result scrapeds

Rating

5.0

(3)

Developer

Web Data Labs

Web Data Labs

Maintained by Community

Actor stats

10

Bookmarked

781

Total users

29

Monthly active users

5.3 hours

Issues response

2 days ago

Last modified

Share

LinkedIn Jobs Scraper — Public Job Listings (No Login Required)

Free plan? Apify gives every user free monthly platform credits — enough to scrape plenty of job listings at this actor's per-result price. No credit card needed.

Scrape LinkedIn job listings at scale without login credentials or authentication. This actor uses LinkedIn's public guest API to search and extract job postings, returning structured data including job title, company, location, description, apply type, and apply links when LinkedIn exposes them.

No login required | Full job details in search mode | Pay per result


Why Use This Actor?

LinkedIn is the world's largest professional job board with millions of active listings. But LinkedIn's interface makes it difficult to:

  • Export job data in bulk
  • Compare salaries across roles and locations
  • Track job market trends over time
  • Build pipelines of job leads for recruiting
  • Feed job data into analytics dashboards

This actor solves all of that. You get clean, structured JSON data from LinkedIn's public job listings — no login, no cookies, no accounts to manage.


Key Use Cases

🔍 Job Market Research

Track hiring trends across industries. Monitor which skills are in demand, which companies are hiring, and how salaries change by region. Perfect for HR consultants, workforce analysts, and career coaches who need data-driven insights.

📊 Recruitment Analytics

Build dashboards showing competitor hiring activity. Know when a competitor posts 50 new engineering roles — that's a signal. Recruiting firms use this to identify clients who are actively scaling.

💼 Lead Generation

Every job posting is a buying signal. A company posting for a "DevOps Engineer" probably needs DevOps tools. A company hiring "SDRs" is scaling sales. Feed job listings into your CRM to identify warm leads based on hiring activity.

🤖 Automated Job Alerts

Set up scheduled runs to monitor specific keywords and locations. Get notified when new roles matching your criteria appear. Build custom job boards or feeds for niche audiences.

📈 Salary Benchmarking

Collect salary data across roles, locations, and experience levels. Build compensation reports for clients or internal planning. Compare your offers against market rates.

🎓 Academic Research

Study labor market dynamics, remote work trends, skill demand evolution, or geographic hiring patterns. Export to CSV/Excel for use in research papers and reports.


How It Works

The actor operates in two modes:

Mode 1: Search Jobs

Search LinkedIn's public job listings by keyword and location. Returns a list of matching jobs — by default with the same full detail fields as job mode (description, applicants, seniority, employment type, job function, industries, apply info). Detail fetching is time-budgeted: rows still waiting for their detail page when the run's remaining time runs out come back with the 6 summary fields at the same per-row price — a longer run timeout yields more detailed rows. Set fetchDetails: false for the lighter 6-field summary rows.

Mode 2: Get Job Details

Fetch the full details of a specific job posting by URL or job ID. Returns the complete job description, requirements, and metadata.


Input Parameters

ParameterTypeRequiredDefaultDescription
actionstringYessearch"search" to find jobs by keywords, "job" to get full details of a specific job
keywordsstringNo"python developer"Search terms (e.g., "data engineer", "marketing manager remote")
locationstringNo""Location filter (e.g., "United States", "London", "Berlin")
maxItemsintegerNo5Upper bound on results — batch accepts up to 1,000, the HTTP API interface caps at 100 per request. A ceiling, not a guarantee: LinkedIn's public search stops paginating well before 1,000 for most queries
fetchDetailsbooleanNotrueFetch the full posting for every search result — slower (one extra page per job), the price per row is the same. Time-budgeted: rows that miss the run's remaining time fall back to the 6 summary fields at the same price. false returns only the 6-field summary rows
datePostedstringNo"any"Posting-age filter for search: "any", "past24h", "pastWeek", "pastMonth"
companyIdsarrayNo—Restrict results to these LinkedIn company IDs — numeric strings like "1068" (JPMorganChase), up to 10. Server-side filter: it rides the search URL, rows keep the 6 summary fields
easyApplybooleanNofalseLinkedIn's own Easy Apply filter — jobs you apply to on LinkedIn itself. Server-side, and when details are on each row is also checked: clearly-external applies are dropped, undetectable rows are kept
under10ApplicantsbooleanNofalseLinkedIn's own Under 10 applicants filter — early postings only. It is LinkedIn's filter, not a recount: the guest page can show at most "first 25 applicants", so rows are only dropped when a numeric count over 10 is visible; unknown counts are kept
experienceLevelsarrayNo—Keep only these seniority levels: Internship, Entry level, Associate, Mid-Senior level, Director, Executive. Client-side filter — forces detail fetching and scans extra listings until maxItems match (bounded — see Filtering)
jobTypesarrayNo—Keep only these employment types: Full-time, Part-time, Contract, Temporary, Volunteer, Internship, Other. Client-side filter — same scanning behavior as experienceLevels; combined, both must match
urlstringNo""LinkedIn job URL or job ID, used with action: "job"

Input Examples

Search for remote Python jobs in the US (25 results):

{
"action": "search",
"keywords": "python developer remote",
"location": "United States",
"maxItems": 25
}

Search for marketing roles in London:

{
"action": "search",
"keywords": "marketing manager",
"location": "London",
"maxItems": 50
}

Get full details of a specific job:

{
"action": "job",
"url": "https://www.linkedin.com/jobs/view/1234567890"
}

Broad search for data engineering jobs (highest allowed ceiling):

{
"action": "search",
"keywords": "data engineer",
"maxItems": 1000
}

maxItems is an upper bound, not a quota — LinkedIn's public job search stops paginating well before 1,000 for most queries, so a large request usually returns fewer rows. When that happens the run's status message says so ("LinkedIn search ended after N results").

Jobs posted in the last 24 hours, summary rows only:

{
"action": "search",
"keywords": "registered nurse",
"location": "Chicago",
"datePosted": "past24h",
"fetchDetails": false
}

Full-time mid-or-senior engineering roles at specific companies, Easy Apply only:

{
"action": "search",
"keywords": "software engineer",
"location": "New York",
"maxItems": 20,
"companyIds": ["1068"],
"easyApply": true,
"experienceLevels": ["Mid-Senior level", "Associate"],
"jobTypes": ["Full-time"]
}

Filtering

Two kinds of filters, and they cost differently:

  • Server-side — companyIds, easyApply, under10Applicants, datePosted, location, keywords. These ride the search URL, so LinkedIn does the filtering and the scan costs nothing extra.
  • Client-side — experienceLevels, jobTypes, plus easyApply/under10Applicants verification when details are on. LinkedIn only exposes seniority and employment type on each job's detail page, so these filters fetch and check each listing until maxItems rows match — they force detail fetching on and scan extra listings.

What that means in practice:

  • Recall is bounded, not exhaustive. A client-side filter keeps scanning until maxItems match, the scan cap is hit (up to 10× maxItems in batch, 3× maxItems capped at 50 on the HTTP interface), or the listings stop matching fast enough to keep going — whichever comes first. Most LinkedIn jobs mark seniority Not Applicable, so a narrow experienceLevels choice can scan many jobs before matching — and may still return fewer rows than asked, or the paid zero-match notice.
  • A zero-match filtered scan delivers one notice row — "0 of N scanned jobs matched your filters" — charged as 1 result, exactly like a clean empty search. If every detail page failed instead, the run fails and nothing is charged.
  • Only matching rows are delivered and charged. Rows the filter drops are never pushed and never billed.
  • Degraded rows don't count as matches. A listing whose detail page couldn't be fetched has unknown seniority/type and is skipped by client-side filters — it is neither delivered nor charged.

Sample Output

Each job listing returns a structured JSON object. Here's what a typical result looks like:

{
"jobId": "3876543210",
"title": "Senior Python Developer",
"company": "TechCorp Inc.",
"location": "San Francisco, CA (Remote)",
"postedDate": "2 weeks ago",
"applicants": "127 applicants",
"applicantsCount": 127,
"isEarlyApplicant": false,
"description": "We're looking for a Senior Python Developer to join our platform team...",
"seniority": "Mid-Senior level",
"employmentType": "Full-time",
"jobFunction": "",
"industries": "Software Development",
"url": "https://www.linkedin.com/jobs/view/3876543210",
"applyType": "EXTERNAL",
"applyUrl": null,
"salary": "$137,750.00/yr - $185,000.00/yr",
"salaryMin": 137750.0,
"salaryMax": 185000.0,
"salaryCurrency": "USD",
"salaryPeriod": "year",
"salaryProvider": "TechCorp Inc. provided pay range",
"companyUrl": "https://www.linkedin.com/company/techcorp",
"companyLogoUrl": "https://media.licdn.com/dms/image/.../techcorp_logo",
"companyId": "1068"
}

Fields returned (availability depends on what LinkedIn provides for each listing):

Search results (action: "search") return the full field set by default — fetchDetails fetches each posting's detail page, within a time budget derived from the run's timeout. With fetchDetails: false — or whenever one detail page cannot be fetched, or whenever the detail budget runs out before that row's turn — the row falls back to the 6 summary fields at the same price:

  • jobId, title, company, location, postedDate (ISO date), url

Job details (action: "job") and detail-enriched search rows additionally return:

  • applicants / applicantsCount / isEarlyApplicant — applicant caption and parsed count
  • description — full job description text
  • seniority, employmentType, jobFunction, industries — job criteria. jobFunction is no longer sent on LinkedIn's public guest pages — the key is kept for compatibility but arrives empty on current listings.
  • applyType — "EASY_APPLY", "EXTERNAL" (offsite company ATS), or null when the guest page shows no apply marker LinkedIn can be classified from
  • applyUrl — usually null. LinkedIn does not expose the external apply URL on the public guest surface without login; this field is filled only when the guest HTML still includes it (opportunistic). Do not rely on it being present.
  • salary — the raw salary text exactly as LinkedIn shows it, e.g. "$137,750.00/yr - $185,000.00/yr"; null when the employer doesn't disclose pay
  • salaryMin / salaryMax — parsed numeric bounds ("From $X" → min only, "Up to $X" → max only, a single value → both)
  • salaryCurrency — USD, CAD, AUD, NZD, GBP, EUR, INR or the ISO code LinkedIn used
  • salaryPeriod — "year", "month" or "hour". An unrecognised period keeps salary text but leaves the numbers null — bounds are never invented
  • salaryProvider — who published the range, e.g. "Employer-provided"
  • companyUrl — the company's LinkedIn page (tracking parameters stripped); null when LinkedIn doesn't link it
  • companyLogoUrl — the employer's logo image URL; null when absent
  • companyId — the company's numeric LinkedIn ID — feed it straight back into companyIds; null when LinkedIn doesn't expose one

Code Examples

Python — Search for Jobs and Save to CSV

from apify_client import ApifyClient
import csv
client = ApifyClient("YOUR_APIFY_API_TOKEN")
run_input = {
"action": "search",
"keywords": "data scientist",
"location": "New York",
"maxItems": 50,
}
run = client.actor("cryptosignals/linkedin-jobs-scraper").call(run_input=run_input)
# Save results to CSV
items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
with open("linkedin_jobs.csv", "w", newline="", encoding="utf-8") as f:
if items:
writer = csv.DictWriter(f, fieldnames=items[0].keys())
writer.writeheader()
writer.writerows(items)
print(f"Saved {len(items)} jobs to linkedin_jobs.csv")

Python — Monitor New Jobs Daily

from apify_client import ApifyClient
import json
client = ApifyClient("YOUR_APIFY_API_TOKEN")
# Search for recently posted jobs
run_input = {
"action": "search",
"keywords": "machine learning engineer",
"location": "Remote",
"maxItems": 100,
}
run = client.actor("cryptosignals/linkedin-jobs-scraper").call(run_input=run_input)
items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
# Filter for jobs with salary info
jobs_with_salary = [job for job in items if job.get("salary")]
print(f"Found {len(jobs_with_salary)} jobs with salary data out of {len(items)} total")
for job in jobs_with_salary[:5]:
print(f" {job['title']} at {job['company']} — {job['salary']}")

Python — Get Full Details of a Specific Job

from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_API_TOKEN")
run_input = {
"action": "job",
"url": "https://www.linkedin.com/jobs/view/3876543210",
}
run = client.actor("cryptosignals/linkedin-jobs-scraper").call(run_input=run_input)
items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
if items:
job = items[0]
print(f"Title: {job['title']}")
print(f"Company: {job['company']}")
print(f"Description:\n{job.get('description', 'N/A')}")

Node.js — Search and Process Results

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_API_TOKEN' });
const input = {
action: 'search',
keywords: 'frontend developer react',
location: 'United States',
maxItems: 50,
};
const run = await client.actor('cryptosignals/linkedin-jobs-scraper').call(input);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(`Found ${items.length} jobs`);
// Group by company
const byCompany = {};
for (const job of items) {
const company = job.company || 'Unknown';
byCompany[company] = (byCompany[company] || 0) + 1;
}
console.log('\nTop hiring companies:');
Object.entries(byCompany)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.forEach(([company, count]) => {
console.log(` ${company}: ${count} open positions`);
});

Node.js — Get Job Details

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_API_TOKEN' });
const input = {
action: 'job',
url: 'https://www.linkedin.com/jobs/view/3876543210',
};
const run = await client.actor('cryptosignals/linkedin-jobs-scraper').call(input);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
if (items.length > 0) {
const job = items[0];
console.log(`${job.title} at ${job.company}`);
console.log(`Location: ${job.location}`);
console.log(`Salary: ${job.salary || 'Not disclosed'}`);
console.log(`Apply: ${job.applyUrl}`);
}

Using the Apify API Directly (cURL)

# Start a run
curl -X POST \
"https://api.apify.com/v2/acts/cryptosignals~linkedin-jobs-scraper/runs?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": "search",
"keywords": "product manager",
"location": "Berlin",
"maxItems": 25
}'
# Fetch results (use the defaultDatasetId from the run response)
curl "https://api.apify.com/v2/datasets/DATASET_ID/items?format=json&token=YOUR_TOKEN"

Call as an API (instant HTTP interface)

This actor also exposes a request/response HTTP interface — results come back in the response body, no run polling and no dataset step. Same input parameters, same per-result pricing.

GET/POST https://cryptosignals--linkedin-jobs-scraper.apify.actor
# Search jobs — GET with query params
curl "https://cryptosignals--linkedin-jobs-scraper.apify.actor/?action=search&keywords=devops&location=Remote&maxItems=5" \
-H "Authorization: Bearer YOUR_APIFY_TOKEN"
# Or POST with a JSON body
curl -X POST "https://cryptosignals--linkedin-jobs-scraper.apify.actor/" \
-H "Authorization: Bearer YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"search","keywords":"data engineer","maxItems":5}'
# Get one job posting
curl "https://cryptosignals--linkedin-jobs-scraper.apify.actor/?action=job&url=https://www.linkedin.com/jobs/view/1234567890" \
-H "Authorization: Bearer YOUR_APIFY_TOKEN"

Response:

{
"items": [ { "jobId": "…", "title": "…", "company": "…", "location": "…", "url": "…" } ],
"count": 5
}

Notes:

  • Authenticate with your Apify token in the Authorization: Bearer header (or ?token= query param), exactly like the Apify API.
  • Cold start: if no server instance is warm, the first request may wait up to ~2 minutes while one starts. Warm requests answer in seconds.
  • proxyConfiguration is not supported on this interface — requests containing it are rejected with HTTP 400.
  • maxItems is capped at 100 per request on this interface — batch runs accept up to 1,000 as an upper bound (LinkedIn's public search ends earlier for most queries).
  • fetchDetails is opt-in here — it defaults to off on this interface, so an unchanged call keeps the fast 6-field rows; pass fetchDetails=true for full postings inline. Detail fetches share the request's time budget — rows that miss it still return the 6 summary fields, at the same per-row price. Client-side filters (experienceLevels, jobTypes) force it on.
  • All five filters work in the query string: companyIds=1068,961661 (comma-separated), easyApply=true, under10Applicants=true, experienceLevels=Director,Executive, jobTypes=Contract,Part-time — or as arrays in a JSON POST body. An out-of-domain enum or a non-numeric company id is rejected with HTTP 400 before any work.
  • A filtered scan on this interface scans at most 3× maxItems listings (hard-capped at 50) — a request can legitimately return fewer rows than asked. Rows already collected when the request deadline hits come back as a partial HTTP 200 with a note field — never thrown away.
  • Common field aliases work too (query→keywords, limit→maxItems, jobId→url).
  • You are charged the same per-result price, only for results actually delivered in the response.

Pricing

This actor uses Pay Per Event (PPE) pricing — you only pay for the results delivered. The current price per result is shown live on the actor's Apify Store page.

EventHow it's billed
ResultOne charge per job row delivered — including the single no-results notice row a clean empty search returns

No platform fees. A search that matches nothing still performs the lookup, so it delivers one notice row explaining the empty result and is charged as 1 result.

Free-plan Apify users get monthly platform credits which can be used alongside PPE actor costs — plenty for testing, small projects, or personal job searches.

Tip: Start with maxItems: 5 to test your search parameters before scaling up — a small run costs only a few results.


Scheduling & Automation

Set up scheduled runs on Apify to automatically scrape new job listings on a daily or weekly basis:

  1. Go to the actor's page on Apify Console
  2. Click "Schedule" in the top menu
  3. Set your desired frequency (daily, every 6 hours, weekly, etc.)
  4. Configure your input parameters
  5. Results are automatically saved to your dataset

Combine with Apify Integrations to push results directly to:

  • Google Sheets
  • Slack notifications
  • Webhooks (your own API)
  • Zapier / Make.com
  • Email alerts

Output Formats

Export your results in any format:

FormatHow
JSONDefault. Download from dataset or use API
CSVClick "Export" → CSV in Apify Console
ExcelClick "Export" → Excel in Apify Console
APIFetch programmatically via dataset API endpoint

Frequently Asked Questions

Do I need a LinkedIn account?

No. This actor uses LinkedIn's public guest API. No login, no cookies, no account required. It only accesses publicly available job listings — the same ones visible to anyone browsing LinkedIn without logging in.

Is this against LinkedIn's terms of service?

This actor accesses only publicly available data through LinkedIn's public endpoints. It does not bypass any authentication or access private data. Public job listings are intentionally made available by employers to reach the widest audience.

How many jobs can I scrape per run?

maxItems sets the ceiling — up to 1,000 per run in batch, 100 per request on the HTTP standby/API interface. In practice the real limit is LinkedIn's public job search itself: it stops paginating well before 1,000 for most queries, so large requests usually return fewer rows — the run's status message reports when the search ended early. For larger volumes, set up multiple runs with different search parameters (different keywords, locations, or datePosted windows).

How fresh is the data?

The data is scraped in real-time from LinkedIn when you run the actor. You always get the latest listings. For monitoring new postings, schedule the actor to run daily or every few hours.

What if my search returns 0 results?

  • Check your keyword spelling
  • Try broader search terms
  • Remove or change the location filter
  • Narrow filters like datePosted: "past24h" or a restrictive experienceLevels/jobTypes choice legitimately hit this more often — fewer postings match within the bounded scan
  • LinkedIn may not have listings matching very niche queries
  • A clean empty search — and a filtered scan that matched nothing — delivers one notice row and is charged as 1 result — the lookup itself is the work

Can I filter by remote/hybrid/on-site?

Yes — include terms like "remote" in your keywords. For example: "software engineer remote".

Can I get salary data?

Salary information is returned when the employer includes it in the listing. Not all job postings disclose salary. The actor returns whatever LinkedIn provides.

How is this different from other LinkedIn scrapers?

  • No login required — many competitors require LinkedIn credentials (which risk account bans)
  • Public API only — we don't scrape HTML or use headless browsers, so it's fast and reliable
  • Simple pricing — pay per result, no hidden fees
  • Full details in bulk — search fetches each posting's detail page by default, bounded by the run's time budget
  • Two modes — search for jobs OR get full details of a specific listing

Can I use this for commercial purposes?

Yes. The data is publicly available. You can use it for analytics, lead generation, research, and building products. Always ensure your use case complies with applicable data protection laws in your jurisdiction.

What happens if LinkedIn changes their API?

We actively maintain this actor. If LinkedIn makes changes, we update the actor to ensure continued functionality.


Rate Limits & Best Practices

  • Start small: Test with maxItems: 5 before scaling up
  • Be specific: Precise keywords return better results than broad ones
  • Use location: Adding a location filter reduces noise and speeds up results
  • Schedule smart: Daily runs catch most new postings; hourly is rarely needed
  • Combine searches: Run multiple searches with different keywords to build comprehensive datasets

Support

Having issues or questions? Use the Issues tab on this actor's Apify page to report bugs or ask questions. We typically respond within 24 hours.


Built and maintained by cryptosignals.


Found this actor useful? Leave a review - it helps a lot. Something broken? Report it on the Issues tab.