LinkedIn Jobs Scraper
Pricing
Pay per usage
LinkedIn Jobs Scraper
Scrape LinkedIn jobs by keyword, location, company & filters — no login or cookies needed. Returns title, company, logo, location, salary, seniority, full description, applicants count and more. Fast, reliable, built for scale.
Pricing
Pay per usage
Rating
0.0
(0)
Developer
Osamih
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
What does LinkedIn Jobs Scraper do?
Scrape LinkedIn job postings at scale — by keyword, location, company, industry, salary, seniority and more — without a LinkedIn account, login, or cookies. Every run returns clean, structured JSON: job title, company, logo, location, salary, seniority, employment type, applicant count, the full job description, and the direct job URL.
This Actor reads LinkedIn's public guest job pages, the same pages any logged-out visitor sees on linkedin.com/jobs. Nothing is scraped from behind a login, so there is no account to get restricted and no cookies to keep alive. Run it from the Apify Console, call it from your own backend through the Apify API, schedule it, hook it into Make, Zapier, Google Sheets, or Slack, and export results as JSON, CSV, Excel, HTML, or XML.
Why use LinkedIn Jobs Scraper?
- Job boards and aggregators — keep a fresh index of openings in your niche, refreshed hourly or daily on a schedule.
- Recruiting and talent intelligence — track which companies are hiring, for which roles, in which markets, and how fast postings accumulate applicants.
- Market and salary research — collect posted salary ranges, seniority mixes, and remote-vs-onsite splits across an industry.
- Lead generation — a company that is hiring five sales engineers is a company with budget; surface those signals early.
- Job-seeker tools — power alerts for new postings that match a candidate's filters, including LinkedIn's "be an early applicant" (under 10 applicants) filter.
It is built on pure HTTP requests (Crawlee's CheerioCrawler, no headless browser), which makes it fast and inexpensive — a 60-job run finishes in about 35 seconds.
How to use LinkedIn Jobs Scraper
- Click Try for free / Start to open the Actor.
- Type a job title or keywords (e.g.
Software Engineer) and a location (e.g.United States,London,Remote). - Optionally narrow the search — date posted, remote/hybrid/on-site, employment type, experience level, company, industry, or minimum salary.
- Set Number of results to how many jobs you want.
- Click Save & Start, then watch live progress in the run's status message (
Scraped 40/100 jobs). - When the run finishes, open the Output tab and export as JSON, CSV, Excel, HTML, or XML.
Keep the default Apify residential proxy — LinkedIn blocks datacenter IPs aggressively, so residential is what makes runs reliable.
Input
The only thing you really need is a job title or a location. Everything else is an optional filter.
| Field | Type | Default | Notes |
|---|---|---|---|
title | string | "" | Job title / keywords. Quoted phrases supported. |
location | string | "United States" | Free-text city, state, or country. |
companyName | string[] | [] | Resolved to LinkedIn company IDs automatically; unresolvable names fall back to quoted keyword search. |
companyId | string[] | [] | Numeric LinkedIn company IDs (f_C, e.g. 1441 for Google) for exact targeting when a name is ambiguous. OR-combined and merged with any resolved companyName. |
publishedAt | enum | "" | "" any time, r2592000 month, r604800 week, r86400 24 h. |
workType | enum | "" | 1 on-site, 2 remote, 3 hybrid. |
contractType | enum | "" | F/P/C/T/I/V/O = full-time/part-time/contract/temporary/internship/volunteer/other. |
experienceLevel | enum | "" | 1 internship … 5 director, 6 executive. |
jobFunctions | string[] | [] | LinkedIn job-function codes (e.g. eng engineering, sale sales, it IT, mrkt marketing). OR-combined. |
industryIds | string[] | [] | Numeric LinkedIn industry codes (e.g. 4 software development, 43 financial services). OR-combined. |
titleIds | string[] | [] | Numeric LinkedIn title IDs for exact-title filtering (f_T). OR-combined. |
minSalary | enum | "" | 1–9 = $40k+ … $200k+ (LinkedIn salary filter; mostly US). |
easyApply | boolean | false | Only Easy Apply jobs (apply directly on LinkedIn). |
under10Applicants | boolean | false | Only jobs with fewer than 10 applicants so far ("Be an early applicant"). |
distance | integer | 0 | Search radius in miles around the location; 0 = LinkedIn default. |
sortBy | enum | "" | R relevance (default), DD most recent first. Changes which jobs LinkedIn returns; not a guarantee about row order. |
postedWithinSeconds | integer | 0 | Custom freshness window (e.g. 3600 = past hour). Overrides publishedAt. |
proxy | object | Apify RESIDENTIAL | Keep residential — LinkedIn blocks datacenter IPs. |
rows | integer | 50 | Target number of jobs (max 2000). LinkedIn caps one search at ~1,000; above that the Actor splits the search across facets automatically. |
At least one of
title,location,companyName, orcompanyIdmust be non-empty, otherwise the run stops immediately withInput error: provide at least a job title, location, or company.
Example input
{"title": "Software Engineer","location": "United States","rows": 50,"publishedAt": "r604800","workType": "2","proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }}
Output
One dataset item per job. You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.
{"id": "4430579227","title": "Data Engineer","jobUrl": "https://www.linkedin.com/jobs/view/data-engineer-at-arlo-4430579227","companyName": "Arlo","companyUrl": "https://www.linkedin.com/company/arlo-health","companyId": "65615916","companyLogo": "https://media.licdn.com/dms/image/v2/.../company-logo_100_100/...","location": "New York, NY","postedTime": "8 hours ago","publishedAt": "2026-07-01","applicationsCount": "Over 200 applicants","description": "…full plain-text job description…","descriptionHtml": "<strong>About Arlo</strong><br>…raw HTML of the description…","contractType": "Full-time","experienceLevel": "Mid-Senior level","workType": "Information Technology","sector": "Insurance","salary": "$150,000.00/yr - $220,000.00/yr","applyUrl": null,"applyType": "EXTERNAL","benefits": ["Medical insurance", "401(k)"],"posterFullName": "Grace Boyle","posterProfileUrl": "https://www.linkedin.com/in/gracekboyle"}
Data fields
| Field | Description |
|---|---|
id | LinkedIn job posting ID |
title | Job title |
jobUrl | Canonical link to the posting |
companyName, companyUrl, companyId, companyLogo | Hiring company details |
location | Job location as shown by LinkedIn |
postedTime, publishedAt | Relative age (8 hours ago) and absolute date (YYYY-MM-DD) |
applicationsCount | Applicants so far, e.g. Over 200 applicants |
description, descriptionHtml | Full job description as plain text and as HTML |
contractType, experienceLevel, workType, sector | LinkedIn's job criteria |
salary | Posted compensation range, where LinkedIn shows one |
applyType | EASY_APPLY (apply on LinkedIn) or EXTERNAL (apply on the company site) |
benefits | Listed benefits, where shown |
posterFullName, posterProfileUrl | The person who posted the job, where shown |
Field availability
LinkedIn does not publish every field for every posting, so some are null depending on the job. Measured on live runs: id, title, jobUrl, companyName, companyId, location, publishedAt, description and applyType come back ~100% of the time; salary roughly half of US searches; posterFullName / posterProfileUrl roughly a third; benefits mostly on US postings.
applyUrl is always null — the external application link is not present on LinkedIn's public pages, and reaching it would require a logged-in session, which this Actor deliberately avoids so that no account is ever put at risk. applyType still tells you reliably whether a job is Easy Apply or external, and jobUrl always takes you to the posting.
Row order is not guaranteed. Job detail pages are fetched concurrently and written as they complete, so sort on publishedAt client-side if you need ordering.
How much does it cost to scrape LinkedIn jobs?
Measured on a real 60-job run: $0.021 total, which works out to roughly $0.35 per 1,000 jobs (about 3.5 cents per 100). Residential proxy bandwidth is the dominant cost at ~68% of the total; the Actor itself uses about 0.01 compute units per 60 jobs and peaks at ~120 MB RAM.
Apify's free plan includes $5 of monthly usage, which is enough for roughly 10,000+ jobs per month at no cost. Your exact rate depends on your plan's residential proxy pricing and on how long the job descriptions are.
Using the Actor via API
Small jobs (rows ≤ ~100) — synchronous, returns items directly:
POST https://api.apify.com/v2/acts/osamih~linkedin-jobs-scraper/run-sync-get-dataset-items?token=<API_TOKEN>Content-Type: application/json{ "title": "Software Engineer", "location": "United States", "rows": 25 }
Large or bursty jobs — asynchronous run plus a webhook:
POST https://api.apify.com/v2/acts/osamih~linkedin-jobs-scraper/runs?token=<API_TOKEN>{"title": "Software Engineer", "location": "United States", "rows": 500,"webhooks": [{ "eventTypes": ["ACTOR.RUN.SUCCEEDED", "ACTOR.RUN.FAILED"], "requestUrl": "https://your-app.example.com/apify-callback" }]}
Then fetch results from GET https://api.apify.com/v2/datasets/{defaultDatasetId}/items. Keep your API token server-side only.
Run semantics for integrations:
- Success with 0 items — the search legitimately matched nothing (
Finished: no jobs found for this search). - Success with fewer than
rowsitems — LinkedIn had fewer results. Not an error. - Non-zero exit — input or system error.
Tips and advanced options
- Go faster and cheaper by setting
rowsto what you actually need; the run stops the moment it has them. - Monitor new postings with
postedWithinSeconds(e.g.3600for the past hour) on a schedule, rather than re-scraping everything. - Target a specific employer with
companyName. If the name is ambiguous, usecompanyId— open the company's LinkedIn page and copy the number from the URL. - Scrape more than 1,000 jobs by simply asking for them: LinkedIn caps a single search at ~1,000 results, so the Actor automatically splits the query across experience levels, work types, or employment types to get past that ceiling.
- 1 GB of memory is plenty — peak usage is ~120 MB. Compute cost scales linearly with allocated memory, so raising it just multiplies your bill.
FAQ
Do I need a LinkedIn account or cookies? No. The Actor only reads publicly accessible job pages. There is no login, so there is no account to get restricted.
Is it legal to scrape LinkedIn jobs? This Actor collects only publicly available job-posting information — the same content any logged-out visitor can see. Scraping public data is generally lawful in many jurisdictions, but how you use the data is your responsibility, particularly under GDPR/CCPA where personal data (such as a job poster's name) is involved. Review LinkedIn's Terms of Service and consult your own legal counsel before commercial use. Do not use this Actor to collect personal data without a lawful basis.
Why did I get fewer jobs than I asked for? LinkedIn simply had fewer matching results. The run still succeeds and its status message says so. Broadening the location or removing filters usually helps.
Why is applyUrl empty? See Field availability above — that link is not exposed on LinkedIn's public pages.
Can it scrape LinkedIn profiles, companies, or posts? No. This Actor is scoped to job postings.
Support
Found a bug or need a field that isn't here? Open an issue on the Actor's Issues tab and it will be looked at. Feature requests and custom-scraper enquiries are welcome.
Development
npm installnpm test # unit tests (parsers, query planning)apify run --purge # local run, reads storage/key_value_stores/default/INPUT.jsonapify push # build & deploy
Architecture (see src/): main.ts wires input → proxy → crawler; routes.ts holds the two handlers (SEARCH pages enqueue DETAIL pages with a rolling 5-page lookahead; DETAIL pushes exactly rows items and stops the crawl); parsers.ts extracts data (schema.org ld+json first, CSS selectors as fallback); company.ts resolves company names via the guest typeahead endpoint; queryPlan.ts splits >1,000-row requests across search facets.
apify pushdoes not updatedefaultRunOptionson an Actor that already exists — it only applies them at creation. If the Console shows a different memory than.actor/actor.jsondeclares, every run silently costs the wrong amount. Check withapify api acts/<actorId>and correct it with:$apify api PUT acts/<actorId> -d '{"defaultRunOptions":{"build":"latest","timeoutSecs":3600,"memoryMbytes":1024}}'