LinkedIn Jobs Scraper with Applicant Counts (No Cookies)
Pricing
Pay per event
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
13 hours ago
Last modified
Categories
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 MotorsAug 20 21 applicants postedAug 21 58 applicantsAug 22 97 applicantsAug 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.
| field | meaning |
|---|---|
applicants | how many have applied right now |
applicantsPerDay | the rate, measured across your own observations |
applicantsPerDayReliable | false when the figure cannot be trusted — see below |
applicantsAddedSinceLastRun | new applicants since you last looked |
postedOn | the exact date from LinkedIn's own datetime attribute, not "3 days ago" |
daysOpen | how long it has been live |
closed / closedOn | it stopped accepting applications, and when you first saw that |
isRepost, timesPosted, daysSincePreviousPosting | the same company advertising the same role again |
observations | how many times you have sampled this job |
title, company, companySlug, location, seniority, employmentType, jobFunction, industries, description, salaryMentioned | the 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
Cookieand noAuthorizationfield, 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,authorpassword. - 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:
| event | when |
|---|---|
| Actor start | once per run |
| Job listed | one job from the listing |
| Job detail | one 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.
daysOpenuses the posting date when LinkedIn gives one, and falls back to the date you first saw the job.daysOpenBasistells 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 installnpm test # 34 unit tests, no network, including the no-login guarantees