LinkedIn Jobs Scraper - Job Titles, Companies, Salaries · Apify
LinkedIn Jobs Scraper avatar

LinkedIn Jobs Scraper

Pricing

from $0.90 / 1,000 jobs

Go to Apify Store
LinkedIn Jobs Scraper

LinkedIn Jobs Scraper

LinkedIn Jobs Scraper returns public job listings from LinkedIn by keywords, location, and filters. Scrape job titles, companies, salaries, descriptions, and applicant counts from LinkedIn's public guest job search API.

Pricing

from $0.90 / 1,000 jobs

Rating

0.0

(0)

Developer

Murat Uzun

Murat Uzun

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

0

Monthly active users

19 hours ago

Last modified

Features

Limited permissions

Share

What does LinkedIn Jobs Scraper do?

LinkedIn Jobs Scraper extracts job listings from LinkedIn's public job search API. It returns job titles, companies, locations, salaries, application counts, and full job descriptions from LinkedIn's public guest listings without requiring login or cookies.

Built for recruiters tracking hiring, job seekers collecting openings, and analysts studying the job market.

Price: $0.00005 per run started, plus $0.0009–$0.0015 per accepted job, depending on your Apify plan; FREE-plan jobs cost $0.0015 each ($1.50 per 1,000). Pricing snapshot: 13 November 2026, 04:16 UTC. Platform/proxy charges, where applicable, are separate; see Pricing.

Why use LinkedIn Jobs Scraper?

  • No login required — scrapes LinkedIn's public guest job search API
  • Comprehensive data — extract titles, companies, locations, salaries, descriptions, seniority levels, job types
  • Flexible filtering — search by keywords, location, experience level, work type, posting date
  • Bulk operations — scrape hundreds of job listings in a single run
  • API access — integrate job data into your applications, dashboards, and workflows via the Apify API
  • Scheduled runs — automate job monitoring and alert systems

How to use LinkedIn Jobs Scraper

  1. Open the Actor on the Apify platform or via API
  2. Set search parameters:
    • Enter job title(s) or keywords (e.g., "software engineer", "data analyst")
    • Specify location(s) (e.g., "United States", "Germany")
    • Optionally filter by experience level, work type (remote/onsite/hybrid), posting date
  3. Configure options:
    • Set maximum jobs to scrape (1-1000, default 25)
    • Enable/disable job descriptions (impacts run time and cost)
    • Choose proxy mode (auto/always/never)
  4. Run the Actor — results are saved to the dataset
  5. Download or integrate results in JSON, CSV, Excel, or other formats

Input parameters

ParameterTypeRequiredDescription
keywordsstringNoJob title or skills, e.g. "data analyst". Several searches: "python; golang". Aliases accepted: keyword, query.
locationstringNoCity, region or country, e.g. "Austin, TX". Empty = worldwide.
locationsarrayNoExtra locations for the same search, e.g. ["London", "Manchester"].
datePostedstringNoFilter: past24h, pastWeek, pastMonth, or leave empty for all
workTypearrayNoFilter by: onsite, remote, hybrid
experienceLevelarrayNoFilter by level: internship, entry_level, senior, manager, etc.
jobTypearrayNoFilter by: full-time, part-time, contract, temporary, internship, volunteer
titleIncludesarrayNoKeep only jobs whose title contains one of these words or phrases, e.g. ["data analyst", "bi"]. Case-insensitive, whole words.
titleExcludesarrayNoDrop jobs whose title contains one of these words, e.g. ["senior", "staff", "intern"]. Case-insensitive, whole words; Sr. is not senior, so list both if needed.
excludeCompaniesarrayNoDrop jobs from these companies, e.g. ["Deloitte"]. Whole-name match, case-insensitive.
externalApplyOnlybooleanNoReturn only jobs that apply on the company's own site. Fetches the detail page for each candidate.
maxJobsnumberNoMaximum jobs to scrape (default 25, max 1000)
includeDescriptionbooleanNoFetch full job descriptions (default true; costs more, takes longer)
searchUrlsarrayNoPaste LinkedIn job search URLs; parameters are extracted and applied
proxyModestringNoauto (direct first), always (residential), or never (default: auto)

Output

Each job is returned as a single JSON object:

{
"jobId": "4465379983",
"url": "https://www.linkedin.com/jobs/view/4465379983",
"title": "Senior Software Engineer - Python",
"company": "Venmo",
"companyUrl": "https://www.linkedin.com/company/venmo",
"location": "Austin, TX",
"postedAt": "2026-09-10",
"postedText": "2 weeks ago",
"salary": "$120,000 - $150,000",
"applicants": 163,
"seniority": "Senior",
"employmentType": "Full-time",
"jobFunction": "Software Development",
"industries": ["Technology", "Software Development"],
"description": "We are seeking a senior Python engineer...",
"descriptionHtml": "<p>We are seeking a senior Python engineer...</p>",
"easyApply": true,
"keyword": "software engineer",
"searchLocation": "United States",
"scrapedAt": "2026-09-28T10:30:45.123Z"
}

You can download the dataset in JSON, CSV, Excel, HTML, or other formats directly from the platform.

Data fields

FieldDescription
jobIdLinkedIn job posting ID
urlDirect link to the job on LinkedIn
titleJob position title
companyHiring company name
companyUrlLink to company's LinkedIn profile
locationJob location
postedAtJob posting date (ISO format)
postedTextRelative time text (e.g., "2 weeks ago")
salaryPay range written in the posting text (e.g. $143,500.00 - $212,850.00 Annually), copied as written. LinkedIn's public pages have no structured salary field, so this is null for postings that state no pay range.
applicantsNumber of applicants (if shown)
seniorityRequired seniority level
employmentTypeFull-time, Part-time, Contract, etc.
jobFunctionPrimary job category (null when LinkedIn does not show it on the posting)
industriesIndustry categories, as a list (null when not shown)
descriptionFull job description (plain text)
descriptionHtmlJob description with HTML markup
applyTypeexternal (Apply on the company site) or null; the public job page rarely shows a LinkedIn Easy Apply marker, so easyApply is reported only when it does
applyUrlThe company's application link, only when the posting markup carries one that leaves LinkedIn (often null)
easyApplytrue when applyType is easyApply
keywordSearch keyword used to find job
searchLocationLocation parameter in search
scrapedAtTimestamp when job was scraped

Pricing

The recorded Apify pricing snapshot from 13 November 2026 at 04:16 UTC lists a $0.00005 start event per run, plus an event for each accepted job written to the default dataset:

Apify planUSD per accepted jobUSD per 1,000 accepted jobs, excluding start fee
FREE$0.0015$1.50
BRONZE$0.0012$1.20
SILVER$0.00105$1.05
GOLD / PLATINUM / DIAMOND$0.0009$0.90

These are plan-specific event rates, not a claim that collecting more rows automatically changes your rate. Check the Actor Pricing tab for current terms. This documentation update does not change prices.

Rejected / unknown candidates are never written to the default dataset, so they do not create a default-dataset job event. The start event still costs $0.00005 when a started run returns zero accepted jobs. Verifying criteria requires detail requests even if includeDescription is false; fewer returned jobs does not mean fewer requests or guaranteed lower compute/proxy cost.

Verified filters and result quality

LinkedIn may ignore the experience, employment and workplace parameters in its guest search. This Actor checks returned job details before writing results:

  • experienceLevel: the labelled Seniority level. mid_level and senior accept LinkedIn's combined Mid-Senior level; manager requires an explicit Manager label. It does not infer seniority from a job title.
  • jobType: the labelled Employment type.
  • workType: an explicit workplace criterion or location label, such as Berlin (Remote). A city, a mention of remote customers in the description, or a remote-sounding title is not proof. Missing or contradictory labels are unknown and excluded.
  • Multiple values within a criterion mean OR; different criteria mean AND. Unsupported values fail before fetching. The same checks apply to filters parsed from searchUrls.

Accepted jobs include filterEvidence, with the normalized value and source field for each requested criterion. Existing unfiltered rows retain their fields. OUTPUT in the default key-value store reports status (ok, empty, partial, or failed), resultRows, filteredOut, criteriaOut, criteriaUnknown, criteriaReasons and searchErrors. Reason counters can overlap; filteredOut counts each rejected candidate once. Diagnostics are not paid result rows. Counters cover the current process attempt, not an inferred migration-wide total. Repeated search pages stop pagination with a diagnostic; non-empty responses with no recognised job cards are treated as failed/partial, not genuine empty results.

A strict query can legitimately return zero jobs. This means no candidates could be verified within the searched pages; it does not prove that no such jobs exist. Guest pages can be blocked, omit labels, or change layout. No login or cookies are used.

{
"keywords": "data analyst",
"location": "United States",
"experienceLevel": ["entry_level"],
"jobType": ["contract"],
"includeDescription": false,
"maxJobs": 25
}

Local adversarial fixtures exercise the actual parsers and production runner, including ignored server filters, unknown criteria, budget limits and failed writes. A public guest-page check on 12 November 2026 returned the same first 10 job IDs with and without entry-level/contract hints; all three sampled detail pages contradicted the requested criteria. This is evidence for the extra validation, not a benchmark against a competing Actor or a guarantee about every search.

Documented alternatives

This comparison is limited to finding public job postings for the same role/location and reading one posting's details; automated dataset rows and manually viewed postings are distinguished below. Competitor cells describe their developers' Apify pages, checked on 13 November 2026; they are not independently tested output guarantees. Valig was also identified as a relevant alternative in the supplied 11 November 2026, 23:33 UTC scorecard. No comparative runtime, accuracy, savings or 2× result was measured here.

Comparable capabilityWebDataToolsCurious CoderBebityValigLinkedIn Basic — manual alternative
Search inputKeywords/location or LinkedIn search URLs (.actor/input_schema.json)Search URLs or structured filters; URLs take precedence (README)Search URLs or job-title/location filters (README)Keywords and location, with optional filters and URL parameters (input table)Enter a job query and location in the Jobs interface (official search guide)
Core job fieldsJob ID, URL, title, company and location (.actor/dataset_schema.json)Job ID, link, title, company name and location (field table)Job postings with company details and location (documented output)ID, URL, title, company name and location in each dataset item (output example)Read individual postings in the browser; this manual workflow does not produce a structured dataset row (official search guide)
Job descriptionOptional description text and HTML (.actor/input_schema.json, .actor/dataset_schema.json)Description text and HTML listed (field table)Full description advertised (README)Description text and HTML listed (output example)Open the posting to read its description manually (official search guide)

Free alternative terms, checked 13 November 2026: LinkedIn's official Basic/Premium comparison lists a free account for basic job searching and access to job postings: $0 platform fee for this Basic manual-search scenario, without a Premium trial or subscription. Use a Basic account and sign in as required; unrestricted guest access is not guaranteed. Manual reading/copying takes your time, and your device/internet costs remain; this browser-only workflow needs no self-hosted scraper server. It is not a free bulk-export API. Premium features and automated Actor runs are outside this zero-platform-fee scenario.

Error codes and run deadlines

ERRORS is a key-value-store record, never a paid dataset row. Every entry has a stable errorCode and human-readable error. Existing context (keyword, location, start) remains where applicable; the older layout-error code field is retained.

errorCodeMeaningAction
SEARCH_FETCH_FAILEDA search page could not be fetched after the bounded retries.Inspect access/proxy settings and retry a smaller query.
SEARCH_LAYOUT_UNRECOGNISEDA non-empty page contained no recognised job cards.Inspect the source response; do not interpret it as no jobs.
SEARCH_PAGE_REPEATEDPagination returned already-seen job IDs.Use the partial results; narrow the query before retrying.
RUN_TIMEOUTThe run reached its work deadline.Use any partial results or increase the run timeout in platform run options.
RUN_ABORTEDThe platform emitted an abort event.Inspect the run status before restarting.

The runner logs timeoutAt, its source, and a five-second closing reserve. It first uses the SDK's ACTOR_TIMEOUT_AT value; if unavailable, it derives the deadline from the current run's startedAt and options.timeoutSecs. If these cannot provide a finite limit (including an unlimited timeout), the packaged defaultRunOptions.timeoutSecs (3600 seconds) supplies a finite safety limit. Reading current run options is itself limited to three seconds. No Actor input parameter is added.

Work stops five seconds before that deadline. Requests, response reads, waits and result writes are bounded; aborting cancels requests and prevents subsequent result writes. Diagnostics and SDK shutdown share a five-second allowance. A stuck closing operation rejects rather than reporting a completed run. A result write already sent to storage may have an uncertain outcome if interrupted; it is never retried by this logic.

OUTPUT retains status, reason, resultRows, filteredOut, per-filter counters and searchErrors. reason explains a zero-result outcome. searchErrors counts ERRORS entries, including run-level interruption. No new dataset fields or pricing changes are introduced. Empty searches and fully filtered searches finish with a terminal explanation; inaccessible searches with zero accepted rows fail. Interruption after accepted rows produces partial diagnostics. An external platform abort can still leave the platform run status ABORTED. Invalid input remains FAILED. Only accepted rows are sent to the default dataset; diagnostics do not create job events. Existing start fees and platform/proxy costs can still apply.

JavaScript, Python and CLI examples

These examples each execute a small cloud run and can incur the existing charges. They are alternative invocations, not a sequence to run together. Supply your own APIFY_TOKEN through your environment; never put it in source control. Check run diagnostics as well as returned rows. The synchronous endpoint can time out while the cloud run continues; inspect the existing run in Console before retrying.

JavaScript (Node.js with built-in fetch):

const response = await fetch(
'https://api.apify.com/v2/acts/webdatatools~linkedin-jobs-scraper/run-sync-get-dataset-items',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.APIFY_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ keywords: 'data analyst', location: 'United States', maxJobs: 2 }),
},
);
if (!response.ok) throw new Error(`Actor request failed: ${response.status}`);
console.log(await response.json());

Python (standard library):

import json
import os
import urllib.request
request = urllib.request.Request(
'https://api.apify.com/v2/acts/webdatatools~linkedin-jobs-scraper/run-sync-get-dataset-items',
data=json.dumps({'keywords': 'data analyst', 'location': 'United States', 'maxJobs': 2}).encode(),
headers={
'Authorization': 'Bearer ' + os.environ['APIFY_TOKEN'],
'Content-Type': 'application/json',
},
method='POST',
)
with urllib.request.urlopen(request, timeout=310) as response:
print(json.load(response))

CLI (curl; HTTP failures return a non-zero exit code):

curl --fail-with-body --silent --show-error \
'https://api.apify.com/v2/acts/webdatatools~linkedin-jobs-scraper/run-sync-get-dataset-items' \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"keywords":"data analyst","location":"United States","maxJobs":2}'

MCP usage

Connect an MCP client to the official Apify server at https://mcp.apify.com using its supported authentication flow. Tool availability depends on the connection and Actor permissions. Use this concrete prompt in that connected client:

Use Apify MCP to fetch the details of webdatatools/linkedin-jobs-scraper.
Using the execution tool exposed by this connection, run that exact Actor once with:
{"keywords":"data analyst","location":"United States","maxJobs":2,"includeDescription":true}
Read the returned run's default dataset and OUTPUT/ERRORS records.
Report the job titles and URLs, plus whether the result is ok, empty, partial or failed.
If execution is unavailable, report that limitation; do not substitute another Actor.
Do not repeat a timed-out call without checking the existing run.

This is a cloud execution request, not a free local preview or evidence that this Actor has passed live MCP acceptance.

Tips and advanced options

  • Batch searches: Combine multiple keywords and locations in one run to reduce startup costs
  • Disable descriptions: If you only need job titles and companies, turn off descriptions to skip detail requests when no criterion or apply-route filter requires them
  • Use search URLs: Paste a LinkedIn job search URL with complex filters instead of manually entering parameters
  • Proxy mode: Use auto mode (default) for cost efficiency; switch to always if direct requests are blocked
  • Scheduling: Run the Actor daily to monitor new jobs or track hiring trends

Limitations

  • Public data only — scrapes publicly visible guest job listings; does not use login or cookies
  • Search cap — LinkedIn caps search results at approximately 1,000 jobs per query
  • Some fields optional — salary and applicant counts only appear when LinkedIn displays them
  • Refresh delays — job data may lag by a few hours after posting
  • Rate limiting — LinkedIn may throttle requests during high-volume scraping (handled with proxy escalation)
  • Regional restrictions — some job listings may be region-locked and not available via guest API

This tool scrapes publicly available data from LinkedIn's guest job search API. Users are responsible for ensuring compliance with:

Do not:

  • Scrape private profile information
  • Bypass authentication or access restrictions
  • Use data for discriminatory purposes
  • Violate LinkedIn's terms or local laws

The data returned is public information and the user assumes full responsibility for its use.

FAQ

Q: How much will a run cost? A: The listed Actor event subtotal is $0.00005 + accepted job count × your plan's per-job rate. For 100 accepted jobs, FREE is $0.00005 + 100 × $0.0015 = $0.15005; SILVER is $0.00005 + 100 × $0.00105 = $0.10505; GOLD/PLATINUM/DIAMOND is $0.00005 + 100 × $0.0009 = $0.09005. These examples exclude any separately applicable platform/compute and proxy costs, which depend on the run and billing terms. They are not guaranteed all-in totals.

Q: Do zero results or rejected jobs cost anything? A: Zero accepted jobs means zero job-result events, but the $0.00005 run-start event remains. Rejected or unknown candidates do not create accepted-job events. Fetching and checking them can still consume compute and proxy traffic; those costs are separate from the event subtotal. Strict filters or disabled descriptions do not guarantee savings.

Q: Does this require a LinkedIn account? A: No. It uses LinkedIn's public guest job search API, same as anyone can access in a browser without logging in.

Q: Can I scrape millions of jobs? A: LinkedIn's search results are capped at ~1,000 jobs per query. For multiple queries, batch them together in a single run.

Q: How do I get job descriptions? A: Enable the "Include job descriptions" option in the input. This fetches the detail page for each job (additional cost).

Q: What if I get blocked? A: The Actor handles blocks automatically in auto mode by escalating to residential proxies. A proxy can still be blocked; no mode guarantees access.

Q: Can I filter by salary range? A: Not directly in the input schema, but LinkedIn's job listings include salary data when available. Filter results post-scrape.

Q: How often can I run this? A: No built-in limits. Schedule daily runs, hourly checks, or bulk one-time scrapes as needed.

Integrations

  • n8n — Automate job alerts and notifications
  • Make/Zapier — Pipe jobs into your CRM or email
  • Google Sheets — Use the Apify Google Sheets integration to append jobs to a spreadsheet
  • Slack — Send new job alerts to your team

Part of the webdatatools web-intelligence suite — every Actor is pay-per-event, reads public data without a login, and returns one clean row per entity:

Browse the whole suite at webdatatools, or call ten of these Actors straight from Claude, Cursor or Cline with the webdatatools MCP server.

Website & domain intelligence

Content for AI, LLMs and RAG

Search, video and social

Leads, jobs and company data

Developer, app and research data

Support

Found a bug? Have questions? ../../issues or visit the Apify community forum.


Happy scraping! Visit Apify Store for more job scraping tools and data extraction Actors.

Feedback

  • Found a bug or want a feature? Open an issue on the Issues tab.
  • A run that returns no rows or has warnings says why in its status message.

If you are satisfied with this Actor, you can leave a Store review; nothing is offered in return.