LinkedIn Jobs Scraper avatar

LinkedIn Jobs Scraper

Pricing

from $0.60 / 1,000 jobs

Go to Apify Store
LinkedIn Jobs Scraper

LinkedIn Jobs Scraper

🏷️ From $0.60 / 1K | Export LinkedIn jobs with company enrichment, CV keyword matching, applicant counts and annualised salary. No login, no cookie, no proxy needed.

Pricing

from $0.60 / 1,000 jobs

Rating

0.0

(0)

Developer

丂卩ㄖㄖҜㄚ

丂卩ㄖㄖҜㄚ

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

7 days ago

Last modified

Share

Export LinkedIn job listings with company enrichment, CV keyword matching, applicant counts and salary parsing. No login, no session cookie, no proxy required.

🔍 What does LinkedIn Jobs Scraper do?

It runs a job search on LinkedIn and returns every listing it finds as a clean row of data, ready for a spreadsheet, a database or your own product.

No login, no session cookie, no proxy required. This reads LinkedIn's public guest endpoints, the ones that serve logged out job browsing, so there is no session to expire and no account of yours at risk.

On top of the listing itself you can add company details, filter on applicant counts and score every job against your own CV keywords.

🧭 Is there an official LinkedIn jobs API?

Not one you can sign up for. LinkedIn's job APIs live inside its Talent Solutions partner programme, which is limited to approved, incorporated partner companies, and LinkedIn has stopped accepting new partners for its Job Posting API. There is no public, self-serve API that returns LinkedIn job search results.

This Actor reads the public guest job pages instead, the ones LinkedIn serves to logged out visitors, and returns them as structured rows. You get the search as data, with salary parsed and annualised, plus the filtering and CV matching described below, without a partner agreement, a login or a cookie.

📊 What data can I extract from LinkedIn?

One row per job:

FieldWhat it holds
🆔jobIdStable id of the listing on LinkedIn
🏷️titleThe job title as advertised
🏢companyThe hiring company shown on the listing
🔗companyUrlLinkedIn page of the hiring company
📍locationThe location as LinkedIn displays it
📅postedAtPosting date as an ISO timestamp
💰listingSalarySalary exactly as written on the listing
🔗urlDirect link to the listing
💰salaryFromLower bound of the advertised salary
💰salaryToUpper bound of the advertised salary
💰salaryCurrencyCurrency of the salary figures
💰salaryPeriodPeriod the figures refer to, such as Annual or Hourly
💰salaryAnnualisedFromLower bound converted to an annual figure
💰salaryAnnualisedToUpper bound converted to an annual figure
💰salaryRawThe unparsed salary text, kept for auditing
hasSalaryWhether the listing states any pay at all
💰payBandNHS style pay band, kept as a fact rather than converted
💰paySchemeThe pay scheme the band belongs to
📥searchKeywordsThe keywords this search ran with
📥searchLocationThe location this search ran with
📅retrievedAtWhen the row was collected

Optional features add their own columns on top: applicant counts and job categorisation from the detail pages, keywordMatchScore with its matched and unmatched keyword lists when resumeKeywords is set, and the company fields added by enrichCompanyData.

💡 Why scrape LinkedIn jobs?

Beating the applicant pile. A job seeker pastes their CV keywords in, filters to under ten applicants at direct employers, and applies to the roles that scored highest before the crowd arrives.

Hiring signals. A recruiter watching which companies are hiring for the same role in the same city runs the search weekly and diffs the company column.

Salary benchmarking. An HR manager pricing a nursing or engineering opening pulls annualised pay by title and region, with NHS bands reported as bands rather than dropped.

Market research. An analyst tracking whether fintech firms in Leeds are still adding sales roles runs the same query monthly and charts the counts by company size.

Agency filtering. Someone tired of the same role appearing five times through different agencies switches on excludeRecruitingAgencies and sees only direct employers.

Watching one employer. A candidate interviewing at a company pulls everything it advertises, enriched with headcount and industry, to see which teams are growing.

🚀 How do I use LinkedIn Jobs Scraper?

  1. Click Try for free.
  2. Enter what you are searching for and where, for example software engineer in London.
  3. Add any filters you want, such as date posted, workplace type or company size.
  4. Cap the run with maxResults, then click Start.
  5. Download the results as JSON, CSV or Excel, or pull them from the API.

⬇️ Input

{
"keywords": "software engineer",
"location": "London",
"maxResults": 100
}

Set at least one of keywords or location.

FieldTypeDefaultWhat it does
keywordsstringJob title or keywords
locationstringCity, region or country
datePostedstringanyAny time, past 24 hours, past week or past month
workplaceTypestringanyOn site, remote or hybrid
jobTypestringanyFull time, part time, contract, temporary, internship or volunteer
experienceLevelstringanyInternship through executive
sortBystringrelevanceRelevance or most recent
requireSalaryInfobooleanfalseOnly jobs that state pay or an NHS band
maxResultsinteger100Hard cap on billable results

Many more filters are available including company size, industry, applicant count and CV keyword matching, see the input schema in the Console.

Company filters

FieldDescription
companyIncludeOnly these employers. Suffixes like Ltd and PLC are ignored when matching
companyExcludeDrop these employers
excludeRecruitingAgenciesDrop agency listings and keep direct employers
enrichCompanyDataAdd employee count, followers, industry, website and location
companySizeMin / companySizeMaxFilter by employee count
companyFollowersMin / companyFollowersMaxFilter by LinkedIn following

Company pages are fetched once per company and cached within a run, so a hundred jobs at one employer costs one extra request, not a hundred.

Job filters

FieldDescription
excludeJobTitlesDrop titles containing any of these words
underTenApplicantsLinkedIn's own low competition filter, applied server side
maxApplicantsOnly jobs with an exact count at or below this
easyApplyOnlyApply without leaving LinkedIn, applied server side
requireSalaryInfoOnly jobs that state pay or an NHS band

CV matching

Paste your skills into resumeKeywords and every job is scored on how many appear in the title and description. You get keywordMatchScore as a percentage plus matchedKeywords and unmatchedKeywords, so you can see exactly why a job scored what it did. Set minKeywordScore to drop anything below a threshold.

⬆️ Output

Table view

Results arrive as a table you can sort and filter in the Console. There is a second view, Salary detail, that lines up the parsed and annualised pay figures for comparing listings against each other.

Every run also writes a RUN_SUMMARY to the key value store showing how many jobs were scanned, how many were kept, and exactly which filter rejected the rest. If a run returns nothing you can see why rather than guessing.

JSON

A typical row:

{
"title": "Product Manager",
"company": "Example Group",
"location": "Reading, England, United Kingdom",
"postedAt": "2026-08-07T00:00:00.000Z",
"url": "https://uk.linkedin.com/jobs/view/product-manager-4448545238",
"jobId": "4448545238",
"companyUrl": "https://uk.linkedin.com/company/examplegroup"
}

Download it from the run as JSON, CSV or Excel, or read it straight from the API.

Two things this does that others do not

Salary you can actually compare. Pay is published as hourly, daily or annual with no consistency. Every job gets salaryPeriod plus salaryAnnualisedFrom and salaryAnnualisedTo, so one column sorts correctly across the whole result set.

UK healthcare pay bands. NHS roles state pay as "Band 5" rather than a number, so generic scrapers report no salary at all. On a sample of 20 UK nursing jobs, numeric salary extraction found pay on 1. Adding band detection took that to 9. You get payBand and payScheme as facts. The actor deliberately does not convert bands into salary figures, because published Agenda for Change tables disagree with each other materially and inventing a number would be worse than leaving it out.

Honest limits

1,000 results per search. LinkedIn's own ceiling, not a bug. Verified: offset 975 returns jobs, offset 1000 returns an error. Narrow the search with filters or date ranges to get past it.

Applicant counts are bucketed below 25. LinkedIn will filter to low competition jobs server side via underTenApplicants, but its guest view still reports "be among the first 25 applicants" rather than the exact figure. So you can select those jobs, you just cannot see the true number. The applicantsBasis field marks every count as exact, upper-bound or minimum so you always know which you are looking at.

Salary is often absent. LinkedIn does not require employers to publish pay, and most do not. requireSalaryInfo filters to those that do.

Empty results are not a failure

If nothing passes your filters the run finishes successfully with an empty dataset, and RUN_SUMMARY holds the rejection breakdown, so you can see which filter ate everything.

An earlier version threw an error here. That recorded a failed run against the actor's success rate for what was a filter choice, and it discarded the diagnostic you needed.

When a filter cannot be evaluated

If a detail page fails to load, the job's function, industry and applicant count are unknown. By default those jobs are kept, so a network problem is never mistaken for a filter decision. detailFailures in RUN_SUMMARY tells you how often it happened.

Set strictFilters to reject them instead.

Speed

concurrency controls how many detail and company pages are read at once, default 6, maximum 10. It does not change how many requests are made, only how long you wait. Ten detail pages take under a second at the default, against roughly eight seconds one at a time.

Set the run timeout to suit the size of the ask. This Actor's default is 3600 seconds, which is comfortably more than the largest run in the table above (150 jobs with details on takes about 2 minutes). You are charged per delivered result rather than per minute, so a generous timeout costs you nothing and a tight one risks losing the run's work. Lower it only if you want a hard ceiling on how long a scheduled run may sit.

Salary

Salary is read from the listing's own pay field, or from a labelled pay section on the detail page. It is never extracted from the body of a job description: a project budget or a revenue figure in the copy would otherwise become the salary and then feed salaryMin filtering. Where there is no pay information, hasSalary is false rather than a number being guessed.

⏱️ How long does a run take?

Measured on real runs, so you know what normal looks like and can tell it apart from a run that has stalled.

Jobs returnedTypical run time
10about 5 seconds
60about 11 seconds
10018 to 25 seconds
400about 55 seconds

Between an eighth and a quarter of a second a job on the listing pass, which is what the table above is measuring. Two options change that completely, because both add a request per job on top of the listing. fetchDetails opens each job's own page for the full description and the applicant count: 150 jobs with it on took about 105 seconds against roughly 25 seconds without. enrichCompanyData looks up each distinct employer, and 80 jobs with both switched on took about 128 seconds. Turn them on when you need those fields and expect a run measured in minutes rather than seconds. The first few seconds of any run are the container starting rather than the work.

A run is never silently stuck. Jobs are logged as each listing page is read, and written to the dataset as they are parsed rather than held back to the end, so a run that hits its time limit still leaves everything it had already collected. A search that matches nothing ends successfully with an empty dataset and the reason in its status message.

💰 How much does it cost?

Three events, because the cost of a run is not proportional to how many jobs survive your filters.

EventCharged when
job-detail-fetchedA job detail page is read
company-enrichedA company page is read, once per company
job-resultA job passes every filter and is delivered

Deep filtering asks this actor to read a thousand jobs to find fourteen. Under a single per-result charge, the better your filters the worse the economics, which is the wrong incentive on the one feature that sets this apart. Splitting the events means you pay for the reading you asked for and the results you got, and both are visible before you run.

Cheap filters, company name, title exclusions and agency exclusion, are applied before any detail request, so they never cost you a fetch.

What a run actually costs

The listing shows the result price. That is the cheap end, and deep filtering costs more, so here are both plainly:

RunCost
100 jobs, no detail filtersabout $0.10
1,000 scanned, 14 delivered after salary, applicant and keyword filtersabout $2.01

The second run reads a thousand detail pages to find fourteen jobs. That reading is the work you asked for and it is charged at job-detail-fetched, not hidden in the result price. Filters that need no detail page, company name, title exclusions and agency exclusion, are applied first and cost nothing.

If you want the cheap end, leave the detail filters off.

🔌 Integrations

Send results straight to Google Sheets, Slack, Airtable, Zapier, Make or your own webhook using Apify integrations. You can also trigger a run whenever something happens in another tool.

AI agents can run this Actor too, through the Apify MCP server. An agent connected to mcp.apify.com can discover it, read its input schema and start a run under the identifier spookyweb/linkedin-jobs, then read the finished dataset. That means an assistant asked to shortlist low competition product manager roles in London can call this Actor with the right filters and answer from live listings.

🔗 Using LinkedIn Jobs Scraper with the Apify API

curl -X POST "https://api.apify.com/v2/acts/spookyweb~linkedin-jobs/run-sync-get-dataset-items?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"keywords": "software engineer", "location": "London", "maxResults": 100}'

Or with the Apify client:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('spookyweb/linkedin-jobs').call({
keywords: 'software engineer',
location: 'London',
maxResults: 100,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Full detail is in the Apify API reference, and every run is also callable from the Python and JavaScript clients.

❓ FAQ

No, and that is the point. This reads the public guest job pages, so there is no login step, no cookie to paste and no session of yours to expire or get flagged. You also do not need a proxy.

Up to 1,000, which is LinkedIn's own ceiling rather than a limit set here. Past that the guest endpoint returns a 400, which the run treats as a normal end and finishes successfully with what it has. To cover more of a market, split the search by location, by job title or by date range.

What is CV keyword matching?

Put your skills into resumeKeywords and every job is scored on how many of them appear in its title and description. The row carries keywordMatchScore as a percentage plus matchedKeywords and unmatchedKeywords, so the score is auditable. minKeywordScore drops anything below a threshold.

Does it get applicant counts?

Yes, in applicants, with applicantsBasis saying whether the figure is exact, an upper-bound or a minimum. Below 25 applicants LinkedIn's guest view reports a bucket rather than a number, so you can still select those jobs with underTenApplicants, you just cannot see the true count.

What does enrichCompanyData add?

Employee count, followers, industry, website, description and location for the hiring company. Company pages are read once per company and cached within the run, so a hundred jobs at one employer costs one extra request rather than a hundred.

Why is a job kept when a lookup fails?

Because a network problem is not a filter decision. If a detail page will not load, the job's function, industry and applicant count are unknown, and dropping it would quietly turn an outage into a filtered result. Those jobs are kept by default and counted in detailFailures. Set strictFilters to reject them instead.

This reads LinkedIn's public guest job pages, the ones anyone can see without an account. It never logs in, it never uses a cookie, and it never touches anything behind authentication. Job adverts are business information published by employers to be seen.

Scraping public data is lawful in the UK, the EU and the US, and the hiQ v LinkedIn line of cases went to public data specifically. If you go on to combine this data with personal data, that is on you to handle under GDPR. Apify's ethical scraping guide covers the wider picture.

👍 Your feedback

Found a bug, or want a field that is not here yet? Open an issue on the Actor's Issues tab. Requests that make the data more useful get built, and problems get fixed quickly.

🔎 You might also like

ActorWhat it does
Indeed Jobs ScraperIndeed listings with parsed salary figures, past the 15 job sign in wall
Reed Jobs ScraperUK jobs from Reed with applicant counts and annualised salaries
Totaljobs ScraperUK jobs from Totaljobs with duplicate agency repostings removed