LinkedIn Jobs Scraper with Applicant Counts (No Cookies) avatar

LinkedIn Jobs Scraper with Applicant Counts (No Cookies)

Pricing

Pay per event

Go to Apify Store
LinkedIn Jobs Scraper with Applicant Counts (No Cookies)

LinkedIn Jobs Scraper with Applicant Counts (No Cookies)

Scrape public LinkedIn job posts with their applicant counts — then keep the series, so you also get how fast applicants arrive and how long each job stays open. No login, no cookies.

Pricing

Pay per event

Rating

0.0

(0)

Developer

Northbell

Northbell

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

13 hours ago

Last modified

Share

LinkedIn Jobs Scraper with Applicant Counts

Scrape public LinkedIn job posts — title, company, location, exact posting date, seniority, employment type, description, and how many people have applied.

Then keep running it, and you get the thing a single scrape cannot give you: how fast those applicants are arriving.

The number that goes stale in an hour

A job post showing 141 applicants tells you almost nothing on its own. 141 applicants on a job posted yesterday is a stampede. 141 applicants on a job posted five weeks ago is a role nobody wants.

Same number. Opposite meaning. The difference is time, and LinkedIn does not keep it for you.

Applicant counts cannot be back-filled. There is no endpoint that tells you what a job's count was last Tuesday. If you did not record it that day, that day is gone. This Actor records it and keeps the series:

job 4419969671 Senior Software Engineer — General Motors
Aug 20 21 applicants posted
Aug 21 58 applicants
Aug 22 97 applicants
Aug 24 141 applicants → 30 per day, still open, 4 days

What you get

Every run appends to your dataset. Rows are tagged by type.

job — one row per job.

fieldmeaning
applicantshow many have applied right now
applicantsPerDaythe rate, measured across your own observations
applicantsPerDayReliablefalse when the figure cannot be trusted — see below
applicantsAddedSinceLastRunnew applicants since you last looked
postedOnthe exact date from LinkedIn's own datetime attribute, not "3 days ago"
daysOpenhow long it has been live
closed / closedOnit stopped accepting applications, and when you first saw that
isRepost, timesPosted, daysSincePreviousPostingthe same company advertising the same role again
observationshow many times you have sampled this job
title, company, companySlug, location, seniority, employmentType, jobFunction, industries, description, salaryMentionedthe posting itself

vanished — a job that was in your results last run and is not there now, with its last known applicant count. A job disappearing silently is indistinguishable from a broken scraper, so it gets a row.

error — anything that failed, written where you will actually see it.

Three questions this answers that a snapshot cannot

"Is it worth applying?" — 141 applicants in one day means you are competing with a crowd. 12 applicants in fourteen days means the posting is quiet and your application will actually be read.

"Is this company really hiring?" — a role that closes in four days was filled. A role reposted three times in six months was not. isRepost and timesPosted separate genuine demand from a job ad that has become furniture.

"How fast is this market moving?" — applicants-per-day, aggregated by company, seniority or region, is a hiring-velocity signal you own outright, because you are the one who recorded it.

Numbers this Actor refuses to give you

LinkedIn stops counting precisely at a threshold and starts saying Over 200 applicants. Subtracting one rounded figure from another produces a rate that looks precise and is not.

So: when either end of an interval is capped, applicantsPerDayReliable is false and applicantsPerDayNote says capped-by-linkedin-so-this-is-a-lower-bound. The same applies on a job's first observation (first-observation — one point has no slope), within the first half-day (observed-for-less-than-half-a-day — dividing by a tiny interval explodes), and when a count goes down (count-went-down).

A rate without that flag is a rate that will eventually lie to you.

No login. Not as a policy — as a property of the code.

This Actor never signs in, never asks you for a session cookie, and never sends one. It reads LinkedIn's public guest endpoints, the same pages an anonymous visitor sees.

That is enforced, not promised:

  • The request headers are a frozen object with no Cookie and no Authorization field, and nothing can add one at runtime.
  • A guard rejects any attempt to attach a credential header, and the input schema refuses any field whose name looks like cookie, session, token, auth or password.
  • Unit tests assert all of the above, including that no session identifier ever reaches your dataset.

If a competing tool asks you to paste your li_at cookie, it is operating your LinkedIn account on your behalf. This one cannot, by construction.

Two more things it gets right

A failed fetch becomes a row, not a log line. Nobody reads run logs. If the listing endpoint refuses, you get an error row in the dataset, and the run is marked failed when a whole requested feature came back empty. A green run with an empty dataset is the worst outcome for anything you check once a day.

A closed job is data, not a failure. When a job's page returns 404 the Actor records closed: true with the date — that is the end of the job's life, which is exactly what you were measuring. It does not count as an error and does not fail the run.

Input

{
"keywords": "software engineer",
"location": "United States",
"maxJobs": 50,
"datePosted": "past-24-hours",
"fetchDetails": true,
"onlyTrackedJobs": false,
"maxRequestsPerMinute": 20
}

Sizing a run

Ten jobs per listing request, plus one request per job for the applicant count.

requests = ceil(maxJobs / 10) + maxJobs

50 jobs is 55 requests, about three minutes at the default rate.

Set fetchDetails: false for a listing-only run at a tenth of the requests — you lose the applicant count, which is the point of this Actor, but it is there if you only want the postings.

Daily runs

Set onlyTrackedJobs: true. New jobs are still listed, but only jobs you already have a history for get their detail page fetched — so you pay to keep watching the series you are building, not to re-scrape the whole market every morning.

What you pay for

Pay per event, charged only for results actually delivered:

eventwhen
Actor startonce per run
Job listedone job from the listing
Job detailone job's applicant count, seniority, type and description recorded

A failed fetch is never charged. If the detail page errors, you get the listing row and the listing charge only. You are paying for data, not attempts.

Limits worth knowing

  • Ten jobs per listing page. Deep result sets thin out; LinkedIn does not serve unlimited pages to guests.
  • Applicant counts are capped above a threshold. See above.
  • daysOpen uses the posting date when LinkedIn gives one, and falls back to the date you first saw the job. daysOpenBasis tells you which, every time.
  • The rate-limit budget persists in a key-value store, so overlapping runs of this Actor share one budget rather than stacking up.

On data and privacy

This Actor collects job postings, not people. It does not read, store or return applicant identities, profiles, names or contact details — the applicant figure is a count and nothing else.

Job descriptions pass through to your dataset but are never written to the Actor's own history. The persistent store holds numbers and identifiers only: job ids, applicant counts, dates.

Company logos are not redistributed.

Storage

History lives in a named key-value store, linkedin-jobs-history, so it survives between runs. Deleting it resets the baselines — every job reports as a first observation again, and velocity goes quiet until it has two samples.

Running locally

npm install
npm test # 34 unit tests, no network, including the no-login guarantees