LinkedIn Jobs Scraper — pay only for unique, fresh jobs avatar

LinkedIn Jobs Scraper — pay only for unique, fresh jobs

Pricing

$0.75 / 1,000 unique job delivereds

Go to Apify Store
LinkedIn Jobs Scraper — pay only for unique, fresh jobs

LinkedIn Jobs Scraper — pay only for unique, fresh jobs

Scrapes LinkedIn job postings from public guest endpoints. Deduplicates on stable job IDs across pages and queries, enforces freshness against verified posted-at timestamps, and bills only for the unique jobs delivered.

Pricing

$0.75 / 1,000 unique job delivereds

Rating

0.0

(0)

Developer

Slim81

Slim81

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Share

Scrapes LinkedIn job postings from public guest endpoints — no login, no cookies, no personal data — and bills you only for unique jobs. Raw LinkedIn search results repeat heavily across pages and overlapping queries (we measure ~1.8–2.2× duplicate inflation on real runs). This actor deduplicates on LinkedIn's stable job ID before anything is delivered or charged, so the duplicates cost you nothing.

Every run ends with a receipt (RUN_SUMMARY) that shows exactly what happened: rows seen, duplicates suppressed, stale rows dropped, unique jobs delivered, and the count actually charged — numbers you can audit against your dataset, not vibes.

Why this actor

  • Unique-only billing. Dedupe by stable jobId across all pages and all your queries in a run. Delivered == charged, structurally: pushing and charging are the same code path.
  • Verified freshness. Every job carries a real machine timestamp (postedAt) read from LinkedIn's own markup — not a parsed "2 weeks ago" guess. Ask for postedWithin: "week" and stale rows are dropped and counted, never billed.
  • Honest failure semantics. A blocked or empty run says so, loudly. If your maxTotalChargeUsd budget runs out, the run stops immediately with a budget_exhausted receipt — it never silently keeps scraping. Even a crash persists a partial receipt of what was delivered and charged.
  • No account risk, no PII. Guest surface only: job postings, never recruiter or applicant data, no login of any kind.

Input

{
"keywords": ["software engineer", "data engineer"], // 1..N phrases
"locations": ["United States", "New York"], // 1..N locations
"postedWithin": "week", // "any" | "24h" | "week" | "month"
"jobType": ["fulltime", "contract"], // optional; also parttime,
// temporary, internship, volunteer
"experienceLevel": ["entry", "associate"], // optional; also internship,
// mid-senior, director, executive
"easyApply": false, // optional: only Easy Apply postings
"maxUniqueJobs": 500, // hard cap = your billing ceiling
"strictMatch": false, // opt-in: drop titles not matching keywords
"fetchJobDetails": true, // per-job enrichment (default true)
"proxy": { "useApifyProxy": true }
}

jobType, experienceLevel, and easyApply are optional filters applied by LinkedIn's own search — leave them out for unfiltered results. When fetchJobDetails is on, each is corroborated by the matching detail field (employmentType, seniority, easyApply), but unlike freshness they are not independently enforced by the actor.

Tip: LinkedIn caps any single query at ~1,000 results. To scale, split into several keyword × location queries — the deduper neutralizes their overlap, so you never pay twice for the same job.

Output

One dataset row per unique job:

{
"jobId": "3959471234",
"title": "Senior Software Engineer",
"company": "Acme Corp",
"location": "New York, NY",
"url": "https://www.linkedin.com/jobs/view/3959471234",
"postedAt": "2026-07-21", // verified machine timestamp
"postedAtText": "2 days ago", // LinkedIn's own label, for transparency
"isNew": false,
"employmentType": "Full-time", // detail fields null when
"seniority": "Mid-Senior level", // fetchJobDetails=false or fetch failed
"easyApply": true,
"applicants": 47,
"salaryRaw": "$120,000.00/yr - $150,000.00/yr",
"salaryMin": 120000, // parsed when an annual USD range is shown
"salaryMax": 150000,
"workTypeDerived": null, // "remote"|"hybrid"|"onsite" ONLY on a
// high-confidence signal, else null
"matchedQuery": { "keywords": "software engineer", "location": "New York" },
"detailFetched": true,
"scrapedAt": "2026-07-23T13:02:19Z"
}

The receipt (RUN_SUMMARY)

Stored in the run's key-value store and printed at the end of the log: per-query pages fetched, rows seen and stop reason; unique jobs emitted; charged count; duplicates suppressed (with the measured inflation factor); stale and strict-match drops; detail-fetch failures; proxy tier. If the numbers ever look off, this is where you check them.

Honest limitations

  • LinkedIn's guest surface does not expose work type (remote/hybrid/onsite). workTypeDerived is a best-effort derivation from explicit title/location signals like "(Remote)" — when signals conflict or are ambiguous it stays null rather than guessing.
  • Salary appears on only a minority of postings; salaryMin/Max parse annual USD ranges. Hourly and non-USD ranges ship raw in salaryRaw.
  • Dedupe is per-run in this version (cross-run dedupe is planned).
  • A failed detail fetch never loses the job: the row ships with detailFetched: false and null detail fields.

Pricing

Pay-per-event: you are charged per unique job delivered — duplicates, stale rows, and filtered rows are free. Set maxUniqueJobs (and optionally Apify's maxTotalChargeUsd) to cap spend; the run stops the moment either limit is reached.


Development

npm install
npm test # node --test; fixture-pinned, no network

src/ — parser, dedupe, freshness, strict-match, salary, worktype, query planner, run-summary, fetch layer, pipeline, error ledger, thin actor shell. .actor/ — Apify packaging. test/ — 209 unit + mocked-integration tests pinned to live-captured fixtures.