Initial release. Pay-per-event LinkedIn Jobs scraper, a drop-in for automation-lab/linkedin-jobs-scraper at identical pricing:
- Input (drop-in field names):
searchQuery (required), location, maxJobs (1–1000), jobType (all/F/P/C/T/I), experienceLevel (all/1–6), workplaceType (all/1/2/3), datePosted (all/r86400/r604800/r2592000), sortBy (R/DD), scrapeJobDetails, maxRequestRetries, customMapFunction, proxyConfiguration. searchQuery/location also accept a list (query × location cross-product) when called via API.
- Output (drop-in field names):
id, title, url, companyName, companyLinkedinUrl, companyLogo, location, postedAt, salary, applicantsCount, workplaceType, descriptionHtml, descriptionText, seniorityLevel, employmentType, jobFunction, industries, applyUrl, benefits, scrapedAt. Additive enrichments: postedDate (machine-readable YYYY-MM-DD), searchQuery, searchLocation.
- HTTP-only, no login: scrapes LinkedIn's public guest API —
…/jobs-guest/jobs/api/seeMoreJobPostings/search for listings and …/jobs-guest/jobs/api/jobPosting/{id} for details — with got-scraping (HTTP/1.1 + generated Chrome headers to avoid the blocked HTTP/2 fingerprint) and cheerio parsing. No headless browser; runs on 512 MB with datacenter proxy.
- Filters are server-side:
jobType, experienceLevel, workplaceType, datePosted and sortBy map 1:1 to LinkedIn guest-search query params (f_JT, f_E, f_WT, f_TPR, sortBy).
- Pricing: matches the reference —
actor_start $0.005 + job_scraped $0.00058 ($0.58/1,000, ~$0.585 per 1,000 jobs). job_scraped is charged only after pushData succeeds; actor_start fires at most once per logical run (migration-safe). Higher Apify tiers get a lower per-job rate automatically.
- Run-level deduplication by job ID across seeds — overlapping searches are billed at most once per job. Pagination advances
start by the raw served-card count (not the deduped count) and stops on a short/empty page or when a full page yields no new IDs.
- Migration-safe state: dedup set, per-seed emitted counts, and per-seed search offsets are persisted via
Actor.useState, so an Apify migration resumes where it left off instead of re-scraping and re-billing already-written rows. A job id reserved for a write that then fails is released so an overlapping seed can retry it.
- Resilience: per-request retry with exponential backoff and fresh-proxy rotation; 404/410 treated as permanent; a failed job-detail fetch still emits the listing row; errors recorded to a sidecar key-value store entry so one bad request never aborts the run.
Actor.on('aborting') flushes state and exits within 1s so user cost ceilings are honored.