LinkedIn Jobs Scraper
Pricing
Pay per event
LinkedIn Jobs Scraper
LinkedIn job postings by keyword, location or company - no login, no cookies. Filter by date, experience, job type and remote. Incremental mode returns only NEW or CLOSED postings per run. Full description, seniority, employment type and industries included. No browser.
Pricing
Pay per event
Rating
5.0
(1)
Developer
Hriday Rungta
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
7 hours ago
Last modified
Categories
Share
LinkedIn Jobs Scraper — search, company postings and change alerts
Point it at a keyword search or a company, and get one row per job posting — no login, no
li_at cookie, no session of any kind. Turn on incremental mode and every run after the first
returns only postings that are new or have gone closed since the previous run, tracked per
search and per company in a key-value store on your own Apify account.
From $0.50 per 1,000 rows in mode "all", $2.00 per 1,000 in mode "changes". No start fee, no platform usage passed through.
What you get
One row per job posting:
jobId, title, permalink | the posting and a direct link to it |
companyName, companyUrl, location | who's hiring and where |
employmentType, seniorityLevel, jobFunction, industries | LinkedIn's own posting metadata |
descriptionText | the full job description, not a preview |
postedIso, postedText, applicantsText, activelyHiring | when it went up, how many have applied, and LinkedIn's own "actively hiring" badge |
sourceType, sourceValue | which search or company found it |
In mode "changes", every row also carries changeType (new or closed), firstSeen, lastSeen
and daysLive — so a schedule builds a clean history of when a posting appeared and when it came
down, with no duplicate rows for postings that are just still open.
Search by keyword, by company, or both
searches takes any number of { keywords, location } pairs — leave either blank for "any".
companyUrls takes LinkedIn company page URLs or bare slugs (stripe, or the full
https://www.linkedin.com/company/stripe/); every open posting from that company comes back, with
zero overlap against a keyword search — company postings and keyword-search postings are fetched
through separate endpoints, not filtered out of the same result set. Mix both in one run: 2 searches
and 3 companies is 5 independent sources, each with its own incremental tracking.
Narrowing a search
datePosted (any time / past 24 hours / past week / past month), experienceLevel, jobType and
remote are sent straight to LinkedIn's own search filters, so they narrow the result set
server-side rather than discarding rows you already paid to fetch. titleInclude / titleExclude
filter by keyword afterwards — exclude always wins over include.
salaryMin is best-effort, and deliberately conservative: LinkedIn does not expose a
structured salary field on every posting — most say nothing about pay at all — so a posting is
excluded only when a specific figure was found in its description text and that figure is below
your minimum. A posting that mentions no salary is always kept, never guessed away.
Incremental "changes" mode
| Input | What it does |
|---|---|
mode | "all" (default): every matching posting, every run. "changes": only postings NEW or CLOSED since this source's last run. |
firstRunSince | how far back a source's first run in changes mode reaches before treating a posting as "new". Default 30 days. Set "all" to baseline nothing and report every currently-open posting as new on run one. |
stateStoreName | the key-value store on your account holding the known-postings set per source. Default is created for you. |
stateNamespace | optional extra prefix, for several independent tracking sets inside one store. |
Why this isn't just a high-water mark
A job posting isn't append-only the way a forum post is — it can close, and LinkedIn's search gives no "this posting is now closed" signal of its own. So instead of a newest-first watermark, this Actor keeps the full set of posting ids it has seen for each search or company, and diffs a fresh fetch against it: an id missing from the fresh fetch is reported closed, an id present in both is left alone (no charge, no row), and an id that's new to the set is reported new. That's what makes "changes" mode able to tell you a posting came down, not just that nothing new appeared.
The first run, spelled out
A source with no stored set yet takes every currently open posting matching firstRunSince as
its baseline "new". By default that's the last 30 days — so a first run on a broad search doesn't
bill you for every posting a company has ever had open. Every later run compares against the stored
set instead, and only reports genuine changes.
Safety rails
- A run cut short never reports a false "closed". If a page walk is truncated by a budget or a page cap before it finishes a source, the run cannot tell "this posting is gone" apart from "this posting is just further down a page we didn't reach" — so closed-posting detection is skipped entirely for that source on that run, and the log says so plainly.
- Your own filters never affect what counts as known.
titleInclude,titleExcludeandsalaryMinonly decide what gets emitted as a row; the full fetched set is still tracked, so a posting your filters hide this run is never wrongly reported "closed" the moment it stops matching a filter. - State is namespaced to you. Keys are prefixed from your own Apify user id, so two buyers' tracked sets can never cross, even if they happen to track the identical search.
What it needs, and what it never does
A plain Apify Proxy is enough — this Actor deliberately does not request the UNBLOCKER group.
Measured directly: 20 out of 20 job-detail page requests answered HTTP 200 on a bare datacenter
connection with zero cookies, no 999 anti-bot blocks. Job postings are public listings, and this
Actor is priced accordingly — about a tenth of what an unblocked request costs.
It never touches a personal profile page (/in/<slug>). Personal profiles were checked separately
during this Actor's development and found to be genuinely gated — blocked cold, and truncated to a
sign-in-wall teaser even through a paid unblocking proxy — which is exactly why this Actor scrapes
only job postings: a public listing, not a personal record, and a surface that tolerates a plain
proxy because LinkedIn treats it differently from a profile page. No login, no li_at cookie, no
session of any kind, ever.
Input at a glance
| Input | Default | Notes |
|---|---|---|
searches | one example search | { keywords, location } pairs; either can be blank |
companyUrls | [] | a full company URL or a bare slug |
mode | all | all or changes |
datePosted | any | any, past24Hours, pastWeek, pastMonth |
experienceLevel | any | internship / entry / associate / mid-senior / director / executive |
jobType | any | full-time / part-time / contract / temporary / volunteer / internship / other |
remote | any | on-site / remote / hybrid |
salaryMin | 0 | best-effort, see above — never excludes a posting that mentions no salary |
titleInclude / titleExclude | [] | keyword filters on the title; exclude wins |
maxResults | 100 | cost cap — you're billed once per row saved |
detailConcurrency | 5 | job-detail pages fetched in parallel |
firstRunSince | 30 days | mode "changes" only; or all for no baseline suppression |
stateStoreName / stateNamespace | default / auto | mode "changes" only |
Billing
Pay per event, charged as each row is saved, with no start fee and no charge for platform usage:
| Event | Price | When |
|---|---|---|
job-row | $0.0005 | one posting row, mode "all" |
change-row | $0.002 | one NEW or CLOSED posting row, mode "changes" |
Set maxResults as your cost cap. When a charge limit is reached the run stops cleanly, keeps
everything already saved, and leaves the tracked state where it was so nothing is re-billed on the
next run.