LinkedIn Jobs Scraper avatar

LinkedIn Jobs Scraper

Pricing

Pay per event

Go to Apify Store
LinkedIn Jobs Scraper

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

Muhammad Ahmed

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

6 days ago

Last modified

Categories

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

FieldTypeFillNotes
jobIdstring100%LinkedIn's numeric job posting id
urlstring100%Canonical https://www.linkedin.com/jobs/view/<jobId>
titlestring100%
companystring100%Falls back to the plain-text name when the company has no LinkedIn page
companyUrlstring | null100%null when the company has no LinkedIn page — not fabricated
companyLogostring | null100%media.licdn.com URL. Grey placeholder images are never emitted
locationstring100%As LinkedIn writes it: New York, NY, Georgia, United States, Greater Kolkata Area
postedTimeTextstring100%Relative, as shown: 3 days ago, 15 hours ago
postedAtstring | null100%Date only, YYYY-MM-DD. See limitations
isRemoteboolean100%Inferred from the job title. See limitations
isNewBadgeboolean100%LinkedIn's "new posting" styling on the date
benefitBadgestring | null70-77%Raw badge text: Actively Hiring, Be an early applicant, Medical insurance +3 benefits
searchQuerystring100%The query that produced this row
searchLocationstring100%The location that produced this row
scrapedAtstring100%ISO timestamp
detailFetchedboolean100%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.

FieldTypeFillNotes
seniorityLevelstring | null100%About 90% of real jobs say Not Applicable
employmentTypestring | null100%Full-time, Part-time, Internship, Contract, Volunteer, Other
jobFunctionstring | null100%Single joined string, e.g. Engineering and Information Technology
industriesstring | null100%Single joined string, e.g. Financial Services. See limitations
applicantCountTextstring | null72%A bucket, not a number. See limitations
salaryTextstring | null20%Raw as displayed, e.g. $123,500.00/yr - $150,000.00/yr. Most jobs publish none
easyApplyboolean | null100%null when the page carries no apply marker at all
descriptionstring | null100%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

InputTypeDefaultWhat it does
searchQueriesstring[]requiredJob titles or keywords. Each is run against every location
locationsstring[]["United States"]Free-text places LinkedIn geocodes, e.g. Texas, United States, London, England, United Kingdom
maxResultsPerQueryinteger100Jobs kept per query/location pair. Capped at 1000 (LinkedIn's own ceiling)
remoteOnlybooleanfalseKeep only remote jobs. Title-based — see limitations
maxAgeDaysinteger0Only jobs posted within N days. 0 = any date. Genuine server-side filter
easyApplyOnlybooleanfalseOnly jobs you apply to without leaving LinkedIn. Genuine server-side filter
fetchJobDetailsbooleanfalseFetch each job's page for the nine extra fields. One extra request per job, billed separately
experienceLevelselectanySeniority filter. Requires fetchJobDetails
jobTypeselectanyEmployment type filter. Requires fetchJobDetails
maxItemsinteger0Hard cap across the whole run. 0 = no cap
proxyConfigurationobjectApify residentialLinkedIn 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 researchsalaryText gives 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 automationeasyApplyOnly plus maxAgeDays: 1 produces a short, actionable list.
  • Labour-market analysis — posting volume and postedAt over 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:

EventPriceWhen
Actor start$0.0005Once per run
Job scraped$0.0004Per job that reaches the dataset
Job details$0.0008Per 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.