LinkedIn Jobs Scraper | Multi-Role & Remote Search avatar

LinkedIn Jobs Scraper | Multi-Role & Remote Search

Under maintenance

Pricing

from $3.00 / 1,000 job scrapeds

Go to Apify Store
LinkedIn Jobs Scraper | Multi-Role & Remote Search

LinkedIn Jobs Scraper | Multi-Role & Remote Search

Under maintenance

Search LinkedIn jobs by role, location and date. Verify remote work from job content, merge duplicates, and export descriptions, company details and direct LinkedIn job URLs.

Pricing

from $3.00 / 1,000 job scrapeds

Rating

0.0

(0)

Developer

Furkan Toluç

Furkan Toluç

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

All Jobs Search API

Search Greenhouse, Lever, Ashby and Workday career boards with one Apify Actor. Supply roles, keywords, locations, remote preference and a date window. Company URLs are optional: external search discovers hosted ATS boards, and a small built-in catalog provides fallback coverage.

This repository contains a runnable TypeScript MVP, an Apify input form and dataset view, a Docker build, a local JSONL CLI, provider adapters, persistent monitoring, and automated tests. It is deployed as a private Actor in the owner's account: open All Jobs Search API. It is not publicly listed in Apify Store. See docs/DEPLOYMENT.md for cloud test evidence.

Quick start

Requires Node.js 22 or later.

npm ci
npm run build
npm test
npm run search -- examples/input.json output/search

Results: output/search/jobs.jsonl. Coverage and failures: output/search/run-report.json. An empty dataset can be a legitimate outcome for narrow filters; inspect the report before treating it as “no jobs exist.”

{
"roles": ["AI Automation Specialist", "QA Automation Engineer"],
"locations": ["Turkey", "Europe"],
"remote": true,
"postedWithinDays": 7,
"maxResults": 1000
}

For a broader first trial, use roles: ["Engineer"] and omit the date/location filters. The supplied example intentionally keeps the original narrow search.

Set either BRAVE_SEARCH_API_KEY or SERPAPI_API_KEY in your shell or Actor environment to enable discovery. These are operator settings; end users do not have to provide keys or companies. Optional secret input fields override the operator keys. .env files are not automatically loaded; Node's --env-file or your shell can supply them. Never commit actual credentials.

Without a key, the Actor works with four fallback employers: Stripe, CI&T, Ashby and NVIDIA. This is a starter catalog, not a substitute for broad discovery. Hosted ATS URLs can supplement it through careerUrls.

Architecture

Input validation → provider-independent company discovery
Brave → SerpAPI failover
Optional URLs + discovered ATS boards + catalog
Greenhouse / Lever / Ashby / Workday connectors
Normalization → filters → deduplication → monitoring
Dataset / JSONL + coverage report

src/discovery.ts defines the small SearchProvider interface. A provider implements search(query): Promise<SearchHit[]>; connector code has no search-vendor dependency. Add another adapter and register it in providersFor to support another vendor.

discovery: "auto" tries configured Brave first, then SerpAPI on a failed request or no usable ATS hits. A failing provider is disabled for the remainder of the run. Discovery failure never discards supplied URLs or the catalog. The catalog also supplements successful discovery. Explicit URLs take priority, followed by discovered boards, then the catalog, subject to maxBoards.

Discovery extracts ATS board URLs from indexed job/board search results. This MVP does not crawl arbitrary company domains to find embedded career links. careerUrls accepts hosted board or job URLs; unsupported custom domains are reported and skipped. Search results are used to locate boards; job records come from the ATS feeds, not search snippets.

Search behavior

InputBehavior
rolesOR between roles; all normalized words of a role must occur in the title. No semantic synonym expansion.
keywordsOR between phrases in title/description; combined with roles using AND.
locationsOR between locations, matched against listed primary/secondary locations and country fields.
remote: trueExplicit remote metadata or remote wording in the location is required.
remote: falseExplicit non-remote or hybrid records; unknown status is excluded. Omit for all workplace types.
postedWithinDaysRolling window on the source date, never on firstSeenAt; future dates excluded.
unknownDatePolicyexclude by default when a date filter is active; include opts in to unknown dates.
maxResultsDelivered unique jobs after filtering and monitoring; default 1,000.
maxDiscoveryQueriesDefault 8 logical queries. Failover can call both providers; HTTP retries add requests.
maxBoardsDefault 20 selected boards. Omitted board count appears in the report.
maxJobsPerBoardDefault 250. Lever/Workday: listing scan cap. Greenhouse/Ashby: title-matched processing cap after the full feed download.
enrichAdds a small deterministic skills dictionary and title-based seniority extraction. No LLM requests.

Europe and Turkey matching uses a limited country/city alias dictionary in src/filters.ts. It is not a geocoder or a work-authorization check. City-only locations can be ambiguous. Generic “Remote” or “Worldwide” does not bypass a location constraint. “EMEA” is not treated as synonymous with Europe. Unknown remote status is null, not false.

Workday uses the public career site's CXS endpoints. They are site implementation details, not a guaranteed public developer API contract. Listing scans are bounded; detail requests are made only for title-matching jobs. A changed endpoint, blocked request, partial page, failed detail, or scan cap appears in the report. There is no login, CAPTCHA solver, proxy rotation or application submission.

Output contract

Every row includes the proposed common fields plus provenance and salary units:

{
"title": "Senior QA Automation Engineer",
"company": "Example",
"location": "Berlin, Germany",
"locations": ["Berlin, Germany"],
"countries": ["DE"],
"remote": true,
"workplaceType": "remote",
"employmentType": "Full-time",
"salaryMin": 60000,
"salaryMax": 80000,
"currency": "EUR",
"salaryInterval": "year",
"description": "Build automated quality checks with Python and Playwright.",
"skills": ["Python", "Playwright"],
"experienceLevel": "Senior",
"postedAt": "2026-09-10T10:00:00.000Z",
"postedAtKind": "lastPublished",
"updatedAt": null,
"source": "ashby",
"sourceBoard": "https://jobs.ashbyhq.com/example",
"applyUrl": "https://jobs.ashbyhq.com/example/example-job/application",
"jobId": "example-job",
"requisitionId": null,
"firstSeenAt": "2026-09-12T12:00:00.000Z",
"isNew": true,
"uniqueId": "stable-sha256-identifier",
"sources": [{
"source": "ashby",
"board": "https://jobs.ashbyhq.com/example",
"jobId": "example-job",
"applyUrl": "https://jobs.ashbyhq.com/example/example-job/application"
}]
}

The example is synthetic. Missing salary, seniority or dates remain null; basic-mode skills are []. Salaries are taken from structured Lever/Ashby compensation fields, retain their original interval and currency, and are not inferred from prose or converted to annual pay. Greenhouse/Workday salary extraction and external company enrichment are not implemented. For discovered boards without a supplied organization name, company falls back to the ATS board slug; Greenhouse can supply its actual company name.

Date provenance is explicit: Greenhouse first_published and Workday startDate map to published; Lever createdAt maps to created (not guaranteed publication time); Ashby publishedAt maps to lastPublished. Greenhouse updated_at is only updatedAt. Relative Workday labels such as “30+ days ago” are never turned into invented exact dates.

Deduplication and monitoring

Default deduplication: "strict" merges identical ATS board/job identities or canonical application URLs, stripping known tracking parameters and keeping job query identifiers. Distinct jobs with the same title remain separate. crossSource also merges exact normalized company/title/location/description matches across ATS sources, provided the description is sufficiently long and the match is not ambiguous within one source. It is conservative and cannot guarantee every duplicate is recognized. The sources array retains all merged provenance.

{
"roles": ["Software Engineer"],
"locations": ["Europe"],
"remote": true,
"monitor": true,
"onlyNewJobs": true,
"monitorId": "europe-software-jobs"
}

Schedule this input daily in Apify after deployment. Creating a schedule is separate from running this code; the repository does not create one automatically.

The first monitoring run returns a baseline of matching jobs. Subsequent runs suppress delivered identities and preserve their first delivery timestamp. isNew means not previously delivered in this monitor scope, not “published since yesterday.” Use the date filter separately when a publication/creation window is needed. This distinction handles boards with unknown dates and older jobs discovered for the first time.

History uses the named Apify key-value store all-jobs-search-history-v1, which persists across independent runs. The scope includes monitorId and normalized search filters; changing filters starts a separate history. Limits, discovery vendor, URL order and onlyNewJobs do not reset it. Without monitor, each invocation has no persistent history and all returned rows are new observations for that invocation.

Only successfully delivered records are marked seen. Filtered records, result-limit overflow and budget-rejected records remain eligible. Matching aliases of previously seen duplicates are also remembered. Delivery and history writes are separate operations: a crash between them may redeliver a record. Consumers needing crash-safe idempotency should use uniqueId and source identities. Run one cloud run per monitor scope at a time; Apify KV storage does not provide transactional locking here. The local CLI uses an exclusive scope lock; after a killed process, remove its stale .lock only after confirming the process is stopped. History has no automatic retention expiry; changing monitorId creates a fresh scope.

Apify and billing

Import this repository as an Actor source and use .actor/actor.json. The Dockerfile builds the TypeScript entry point. Configure the search provider key in the Actor environment. Output rows go to the default dataset; RUN_REPORT in the default key-value store contains source errors, truncation, discovery fallback, delivered counts and run status. All boards failing makes the Actor fail; partial successes remain usable.

Once the Actor is deployed, use the Apify run API with your Actor ID. For example, POST /v2/acts/{actorId}/runs with the input JSON, wait for completion, then retrieve its default dataset. There is no separate HTTP server in this MVP; Apify provides the API and scheduling.

Optional PPE hooks use Actor.pushData(job, eventName) after deduplication and monitoring. Configure pricing events in Apify before setting JOB_RESULT_EVENT and ENRICHED_JOB_RESULT_EVENT; they are operator environment settings, not user input. Unregistered events and conflicting automatic dataset events are rejected to avoid accidental free or double charging. Budget-rejected rows are not marked seen. No charges happen in the ordinary local CLI.

The proposed $1/1K basic and $3/1K enriched results are unvalidated pricing ideas, not active prices. Discovery queries, Workday detail requests, persistent KV operations, low-yield searches and platform compute all affect margins. This MVP's enrichment is deterministic; it does not include company research. Benchmark those costs before enabling paid publication. Cloud billing behavior has not been exercised against an actual paid run.

Verification and extension

npm run check
npm test
npm run build
npm run smoke

npm test uses synthetic fixtures for source contracts, discovery failover, filters, pagination, retry behavior, deduplication and monitoring durability. npm run smoke explicitly contacts one live board on each ATS, processes two sample listings, and saves output/smoke-report.json. The low cap intentionally produces partial/truncated reports. It is not a load test or proof of all tenants working.

Add connector implementations in src/connectors.ts and source mappings in src/normalize.ts; extend source types, discovery operators, board parsing, input schemas and fixture tests together. SmartRecruiters, Recruitee and Personio are planned extensions; LinkedIn, Indeed and Google Jobs collectors are not included.

Primary integration references: Greenhouse Job Board API, Lever Postings API, Ashby Job Postings API, Brave Web Search API, SerpAPI Google Search API, Apify Actor SDK.