LinkedIn Jobs Scraper avatar

LinkedIn Jobs Scraper

Pricing

Pay per event

Go to Apify Store
LinkedIn Jobs Scraper

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

Hriday Rungta

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

7 hours ago

Last modified

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, permalinkthe posting and a direct link to it
companyName, companyUrl, locationwho's hiring and where
employmentType, seniorityLevel, jobFunction, industriesLinkedIn's own posting metadata
descriptionTextthe full job description, not a preview
postedIso, postedText, applicantsText, activelyHiringwhen it went up, how many have applied, and LinkedIn's own "actively hiring" badge
sourceType, sourceValuewhich 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.

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

InputWhat it does
mode"all" (default): every matching posting, every run. "changes": only postings NEW or CLOSED since this source's last run.
firstRunSincehow 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.
stateStoreNamethe key-value store on your account holding the known-postings set per source. Default is created for you.
stateNamespaceoptional 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, titleExclude and salaryMin only 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

InputDefaultNotes
searchesone example search{ keywords, location } pairs; either can be blank
companyUrls[]a full company URL or a bare slug
modeallall or changes
datePostedanyany, past24Hours, pastWeek, pastMonth
experienceLevelanyinternship / entry / associate / mid-senior / director / executive
jobTypeanyfull-time / part-time / contract / temporary / volunteer / internship / other
remoteanyon-site / remote / hybrid
salaryMin0best-effort, see above — never excludes a posting that mentions no salary
titleInclude / titleExclude[]keyword filters on the title; exclude wins
maxResults100cost cap — you're billed once per row saved
detailConcurrency5job-detail pages fetched in parallel
firstRunSince30 daysmode "changes" only; or all for no baseline suppression
stateStoreName / stateNamespacedefault / automode "changes" only

Billing

Pay per event, charged as each row is saved, with no start fee and no charge for platform usage:

EventPriceWhen
job-row$0.0005one posting row, mode "all"
change-row$0.002one 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.