LinkedIn Jobs Scraper
Pricing
Pay per event
LinkedIn Jobs Scraper
Scrape LinkedIn job listings without a login or cookie: job titles, companies, company pages and logos, locations, posting dates, salary ranges, seniority, employment type and full descriptions for any keyword and place.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Muhammad Ahmed
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
6 days ago
Last modified
Share
Scrape LinkedIn job listings without a login, a cookie or a browser. Give it a few search terms and locations and it returns structured job rows: title, company, company page, logo, location, posting date, and — optionally — salary range, seniority, employment type, industries, applicant count and the full job description.
It reads LinkedIn's own logged-out job endpoints over plain HTTP. No account is used, nothing is authenticated, and no personal profile data is touched.
What you get
Every row is one job. Fill rates below are measured on a real Apify platform run
(100 jobs, queries software engineer and registered nurse, United States, residential proxy).
Always present — from the search results
| Field | Type | Fill | Notes |
|---|---|---|---|
jobId | string | 100% | LinkedIn's numeric job posting id |
url | string | 100% | Canonical https://www.linkedin.com/jobs/view/<jobId> |
title | string | 100% | |
company | string | 100% | Falls back to the plain-text name when the company has no LinkedIn page |
companyUrl | string | null | 100% | null when the company has no LinkedIn page — not fabricated |
companyLogo | string | null | 100% | media.licdn.com URL. Grey placeholder images are never emitted |
location | string | 100% | As LinkedIn writes it: New York, NY, Georgia, United States, Greater Kolkata Area |
postedTimeText | string | 100% | Relative, as shown: 3 days ago, 15 hours ago |
postedAt | string | null | 100% | Date only, YYYY-MM-DD. See limitations |
isRemote | boolean | 100% | Inferred from the job title. See limitations |
isNewBadge | boolean | 100% | LinkedIn's "new posting" styling on the date |
benefitBadge | string | null | 70-77% | Raw badge text: Actively Hiring, Be an early applicant, Medical insurance +3 benefits |
searchQuery | string | 100% | The query that produced this row |
searchLocation | string | 100% | The location that produced this row |
scrapedAt | string | 100% | ISO timestamp |
detailFetched | boolean | 100% | Whether the fields below were actually read |
Only with Fetch job details enabled
One extra request per job. Fill rates from the same platform run.
| Field | Type | Fill | Notes |
|---|---|---|---|
seniorityLevel | string | null | 100% | About 90% of real jobs say Not Applicable |
employmentType | string | null | 100% | Full-time, Part-time, Internship, Contract, Volunteer, Other |
jobFunction | string | null | 100% | Single joined string, e.g. Engineering and Information Technology |
industries | string | null | 100% | Single joined string, e.g. Financial Services. See limitations |
applicantCountText | string | null | 72% | A bucket, not a number. See limitations |
salaryText | string | null | 20% | Raw as displayed, e.g. $123,500.00/yr - $150,000.00/yr. Most jobs publish none |
easyApply | boolean | null | 100% | null when the page carries no apply marker at all |
description | string | null | 100% | Full text, ~3,900 characters on average |
Without Fetch job details these nine fields are null and detailFetched is false.
Example row
{"jobId": "4453687841","url": "https://www.linkedin.com/jobs/view/4453687841","title": "Software Engineer II (Java)","company": "JPMorganChase","companyUrl": "https://www.linkedin.com/company/jpmorganchase","companyLogo": "https://media.licdn.com/dms/image/v2/D4E0BAQGxpntCyRgsuA/company-logo_100_100/...","location": "New York, NY","postedTimeText": "3 days ago","postedAt": "2026-08-13","isNewBadge": false,"benefitBadge": "Actively Hiring","isRemote": false,"seniorityLevel": "Not Applicable","employmentType": "Full-time","jobFunction": "Engineering and Information Technology","industries": "Financial Services","applicantCountText": "Over 200 applicants","salaryText": "$123,500.00/yr - $150,000.00/yr","easyApply": false,"description": "Job Description You're ready to gain the skills and experience needed to grow …","detailFetched": true,"searchQuery": "software engineer","searchLocation": "United States","scrapedAt": "2026-08-17T04:49:15.076Z"}
Input
| Input | Type | Default | What it does |
|---|---|---|---|
searchQueries | string[] | required | Job titles or keywords. Each is run against every location |
locations | string[] | ["United States"] | Free-text places LinkedIn geocodes, e.g. Texas, United States, London, England, United Kingdom |
maxResultsPerQuery | integer | 100 | Jobs kept per query/location pair. Capped at 1000 (LinkedIn's own ceiling) |
remoteOnly | boolean | false | Keep only remote jobs. Title-based — see limitations |
maxAgeDays | integer | 0 | Only jobs posted within N days. 0 = any date. Genuine server-side filter |
easyApplyOnly | boolean | false | Only jobs you apply to without leaving LinkedIn. Genuine server-side filter |
fetchJobDetails | boolean | false | Fetch each job's page for the nine extra fields. One extra request per job, billed separately |
experienceLevel | select | any | Seniority filter. Requires fetchJobDetails |
jobType | select | any | Employment type filter. Requires fetchJobDetails |
maxItems | integer | 0 | Hard cap across the whole run. 0 = no cap |
proxyConfiguration | object | Apify residential | LinkedIn blocks datacenter IPs. Leave as is |
Minimal input
{"searchQueries": ["software engineer"],"locations": ["United States"],"maxResultsPerQuery": 100}
Everything on
{"searchQueries": ["product manager", "data analyst"],"locations": ["United States", "Canada"],"maxResultsPerQuery": 200,"maxAgeDays": 7,"easyApplyOnly": true,"fetchJobDetails": true,"maxItems": 500}
Use cases
- Job boards and aggregators — pull fresh listings for a niche daily with
maxAgeDays: 1. - Recruiting intelligence — track which companies are hiring for which roles, and where.
- Salary research —
salaryTextgives real posted ranges for the ~20% of jobs that publish one. - Lead generation — a company hiring 15 sales reps is a company with a budget.
- Job-seeker automation —
easyApplyOnlyplusmaxAgeDays: 1produces a short, actionable list. - Labour-market analysis — posting volume and
postedAtover time, by title and geography.
Limitations — read this before you rely on a field
This Actor reads LinkedIn's public, logged-out surface. That surface is narrower than the logged-in site, and several things people expect simply are not published there.
isRemote is inferred from the job title, not reported by LinkedIn.
LinkedIn's logged-out pages contain no workplace-type field anywhere — not on the search card, not
on the job page. Its f_WT remote parameter is accepted and then silently ignored (a made-up
parameter returns byte-identical results). Matching the description text was tested and rejected:
every match in a 40-job sample was a false positive, such as "Fridays are remote-friendly ... we
prioritize in-person and hybrid work". So remoteOnly does two things instead: it appends
remote to the keywords sent to LinkedIn, which genuinely changes what is returned, and then keeps
only jobs whose title advertises remote work. On a platform test this produced 25 rows all
titled remote (Backend Engineer (Remote Position), Software Engineer (Fully Remote)) against
0 remote-titled rows out of 50 in the unfiltered control. It is precise but it is not exhaustive —
a remote job whose title does not say so will be missed.
postedAt is a date, never a time. LinkedIn publishes 2026-08-13 and nothing finer anywhere
in the markup. If you need "15 hours ago" granularity, that lives in postedTimeText. postedAt
comes from the search card only, so it is present on every row regardless of fetchJobDetails.
applicantCountText is a bucket, not a count. LinkedIn only ever renders
Be among the first 25 applicants or Over 200 applicants. It is kept as the raw display string
on purpose. Do not parse it into a number — those are thresholds, not measurements. About 28% of
job pages show no applicant element at all.
industries and jobFunction are single strings, not arrays. LinkedIn joins multiple values
with " and ", and the individual values themselves contain " and "
(Hospitals and Health Care, Computer and Network Security and Software Development). Splitting
would be lossy and ambiguous, so the raw string is emitted unchanged.
seniorityLevel is mostly uninformative. About 90% of real listings report Not Applicable.
Filtering on experienceLevel therefore discards most results.
experienceLevel and jobType are client-side filters and can be slow and expensive.
LinkedIn's f_E and f_JT parameters are accepted by its public search and then ignored, so this
Actor reads the real values from each job page instead. That means every scanned card costs a
request whether or not it matches, and the run keeps scanning until it fills your quota or reaches
LinkedIn's 1000-result ceiling. A measured worst case: jobType: "part-time" on
software engineer scanned all 1000 reachable results, found 1 match, and took 11 minutes. Both
inputs refuse to run without fetchJobDetails rather than silently returning unfiltered results.
easyApply is derived from the apply button's tracking attribute, cross-checked against
LinkedIn's own Easy Apply filter (24 of 25 rows returned by that filter were detected as Easy
Apply, against 4 of 50 in the unfiltered control). The visible button text is "Apply" in both
cases, so it cannot be read from the label. A job page carrying neither marker yields null
rather than a misleading false.
1000 results per query and location, hard. LinkedIn refuses any result offset at or beyond 1000. To collect more, split the search into narrower queries or more specific locations.
Results are deduplicated by jobId across the whole run. LinkedIn's result window drifts
between requests, so consecutive pages genuinely overlap. A job matching two of your queries is
emitted once, attributed to whichever query reached it first.
benefitBadge is a multi-purpose badge. It is not an Easy Apply flag and not an applicant
count. Only the raw string is emitted; no booleans are derived from it.
Residential proxy is required. LinkedIn blocks Apify's datacenter IPs. The default configuration is correct — changing it will produce empty runs.
Pricing
Pay per event:
| Event | Price | When |
|---|---|---|
| Actor start | $0.0005 | Once per run |
| Job scraped | $0.0004 | Per job that reaches the dataset |
| Job details | $0.0008 | Per job whose detail page was successfully read |
Blocked requests, empty pages and jobs discarded by a filter are never charged — you pay for rows you actually receive. 1,000 jobs without details is $0.40; with full details it is $1.20.