LinkedIn Jobs Scraper — pay only for unique, fresh jobs
Pricing
$0.75 / 1,000 unique job delivereds
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
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
jobIdacross 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 forpostedWithin: "week"and stale rows are dropped and counted, never billed. - Honest failure semantics. A blocked or empty run says so, loudly. If
your
maxTotalChargeUsdbudget runs out, the run stops immediately with abudget_exhaustedreceipt — 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).
workTypeDerivedis a best-effort derivation from explicit title/location signals like "(Remote)" — when signals conflict or are ambiguous it staysnullrather than guessing. - Salary appears on only a minority of postings;
salaryMin/Maxparse annual USD ranges. Hourly and non-USD ranges ship raw insalaryRaw. - 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: falseand 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 installnpm 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.