LinkedIn Jobs Scraper avatar

LinkedIn Jobs Scraper

Pricing

from $1.00 / 1,000 job scrapeds

Go to Apify Store
LinkedIn Jobs Scraper

LinkedIn Jobs Scraper

Scrape LinkedIn job postings without an account or cookies: title, company, location, posting date, full description, seniority, employment type, industry and applicant count. Search by keyword and location, or paste LinkedIn search URLs, job URLs or job IDs. $2 per 1,000 jobs.

Pricing

from $1.00 / 1,000 job scrapeds

Rating

5.0

(1)

Developer

Badr Bouargan

Badr Bouargan

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

5 hours ago

Last modified

Features

Limited permissions

Share

LinkedIn Jobs Scraper – Job Listings, Descriptions, Salaries & Companies

LinkedIn Jobs Scraper: visual report of a real run, key figures and charts

What it does. It exports LinkedIn job postings, found by keyword and location or from any LinkedIn jobs search URL, to a spreadsheet: job title, company, location, posting date, salary range when published, full description, seniority, employment type, industry, applicant count and job link.

The problem it solves. LinkedIn has no job export, its search stops at about 1,000 results, and its official job APIs are reserved for partners. This Actor reads the public job pages LinkedIn shows signed-out visitors, with no account, no cookies and no API key, and can split one search city by city to go past the 1,000 ceiling.

Who it is for. Recruiters and staffing agencies, B2B sales teams using hiring as a buying signal, job boards and newsletters, and labour-market or salary researchers.

What you get. One row per job: title, companyName, location, postedAt, link, salaryInfo, applicantsCount, seniorityLevel, employmentType, industries, descriptionText, workplaceTypes, companyLinkedinUrl. Sample (real run, shortened):

titlecompanyNamelocationpostedAtseniorityLevelemploymentTypeapplicantsCount
Data analyst H/FPKF ArsilonCasablanca-Settat, Morocco2026-09-02Entry levelFull-timeOver 200 applicants

Start in 3 steps

  1. Open the Actor on the Store: apify.com/badr_xba/linkedin-jobs-scraper and click Try for free, or start from the ready-made task (developer jobs posted this week) listed on the same page.
  2. Type Keywords (data analyst) and a Location (New York, United States), or paste LinkedIn search URLs, job URLs or job IDs. Set Maximum jobs (50 is a good first run) and switch on Open each job for its full details if you need descriptions.
  3. Click Start, then download the table as CSV, Excel or JSON, or send it to Google Sheets, Make, Zapier or n8n.

Why choose this one

  • Past LinkedIn's 1,000-job ceiling: Split the search by city runs the same search in each city of a country (199 countries built in) and removes duplicates on the job ID; duplicates are not billed.
  • No login, no cookies: nothing of yours is exposed to LinkedIn.
  • Pay only for jobs saved. Refused pages are listed in FAILED_INPUTS, never billed; Maximum jobs and Maximum jobs per search are exact, and Maximum cost per run stops the run cleanly.
  • Search filters LinkedIn still honours: date posted (past 24 hours / week / month), company IDs, Easy Apply, under 10 applicants, geoId, distance.
  • Stable, flat output: same columns on every row, ISO dates, numbers as numbers (applicantsCountNumber, postedAtTimestamp).
  • Handles rate limits by itself: on a 429 it halves its concurrency, pauses, retries on a fresh session, then speeds up again.

How much it costs

Pay per event: a tiny start fee plus one charge per job saved in the dataset. Failed pages, duplicates and filtered jobs are never charged. Platform costs are included.

Apify planPrice per jobPer 1,000 jobs
Free$0.002$2.00
Starter$0.0016$1.60 (−20 %)
Scale$0.0013$1.30 (−35 %)
Business$0.001$1.00 (−50 %)

Actor start: $0.00005 per GB of memory (default 512 MB = one event).

Worked examples (Free plan): 1,000 jobs ≈ $2.00. The ready-made task (up to 100 jobs) ≈ $0.20. A country-wide city split capped at 2,000 jobs ≈ $4.00. Opening each job for details does not change the price per job.

What you can paste

The first field accepts any mix of the following, one per line (several on one line also work); duplicates and stray spaces are cleaned up for you:

You pasteWhat happens
https://www.linkedin.com/jobs/search/?keywords=nurse&location=Dubai&f_TPR=r86400Runs that search with its filters
https://www.linkedin.com/jobs/view/4413987826/ (any xx.linkedin.com variant)Opens that one job directly, full details included
4413987826Same, from the bare job ID
data analystRuns a keyword search with the Location and filters of the form
https://lnkd.in/…, a non-LinkedIn link, a LinkedIn profileSkipped with a warning, listed in FAILED_INPUTS, never billed

Empty input? The Actor runs a small demo search (developer) and says so in the log instead of failing.

What you can do with it

  • Recruiting & sourcing: pull every open role for a skill in a city or country and see how many people already applied.
  • Sales lead generation: companies hiring for a role are companies with a budget and a need — hiring for "Salesforce admin" or "data engineer" is a buying signal for staffing agencies, SaaS vendors and consultants.
  • Job market & salary research: track demand per city, contract type and seniority; collect published pay ranges.
  • Competitor hiring intelligence: follow named employers with LinkedIn company IDs and see which teams they are growing.
  • Job boards & alerts: schedule a daily run on "Past 24 hours" to feed your own job board, newsletter or Slack channel with fresh postings.

How it works

  1. Your keywords/location (or each pasted search URL, or each city of a split) becomes a LinkedIn guest search. One request returns up to 10 job cards.
  2. Optionally, each job page is opened for the full description and criteria, and each company page for its website and size.
  3. Rows are written to the dataset as they arrive; duplicates are dropped on the job ID.
  4. At the end the run writes RUN_SUMMARY (counts, failures, duration, stop reason) and FAILED_INPUTS to the key-value store.

The August 2026 LinkedIn search change

LinkedIn now ignores most URL filters. Still honoured: date posted (f_TPR), company (f_C), Easy Apply (f_AL), under 10 applicants (f_EA), keywords, location, geoId, distance. Experience level, job type and workplace are no longer real filters: with Convert dropped filters into search words on (default), they are appended to your keywords in plain language. That steers the search; it does not guarantee the filter. Filters that are internal IDs (f_I industry, f_F function, f_T title…) cannot be converted and are named in the log.

Step-by-step tutorial

  1. Fill in Keywords (e.g. data analyst) and Location (e.g. New York, United States) — or paste one or more LinkedIn job search URLs.
  2. Set Maximum jobs. 50 is a good first run; 0 means no ceiling.
  3. Switch on Open each job for its full details if you need descriptions, seniority and applicant counts.
  4. Optional: Date posted → Past 24 hours and an Apify Schedule for a daily feed.
  5. Optional: Split the search by city + a country to go past 1,000 jobs.
  6. Press Start, then export as CSV, Excel, JSON or HTML, or send the data to another app (see Integrations).

Input example

{
"keywords": "data analyst",
"location": "New York, United States",
"datePosted": "pastWeek",
"maxItems": 200,
"scrapeJobDetails": true,
"scrapeCompanyDetails": false
}

Sweep a whole country past the 1,000-job limit:

{
"keywords": "developer",
"splitByCity": true,
"splitCountry": "MA",
"maxItems": 2000
}

Or paste searches built in your browser, single jobs, or both:

{
"urls": [
"https://www.linkedin.com/jobs/search/?keywords=nurse&location=Dubai&f_TPR=r86400",
"https://www.linkedin.com/jobs/view/4413987826/"
],
"maxItems": 100
}

A pasted job URL always opens the job page (that is where its title comes from), so its row has the description and criteria filled even with Open each job off. Its postedAt is approximate: the job page only says "4 months ago".

Output example

LinkedIn Jobs Scraper: result cards from a real run

Screenshots from a real platform run.

Each row (with job details on; company fields are null unless Also read each company is on and LinkedIn served the page):

{
"id": "4459931695",
"link": "https://ma.linkedin.com/jobs/view/data-analyst-h-f-at-pkf-arsilon-4459931695",
"title": "Data analyst H/F",
"companyName": "PKF Arsilon",
"companyLinkedinUrl": "https://fr.linkedin.com/company/pkf-arsilon",
"companyLogo": "https://media.licdn.com/dms/image/…",
"location": "Casablanca-Settat, Morocco",
"postedAt": "2026-09-02",
"postedAtTimestamp": 1788307200000,
"salaryInfo": [],
"salaryInsights": null,
"benefits": ["Actively Hiring"],
"applicantsCount": "Over 200 applicants",
"applicantsCountNumber": 200,
"seniorityLevel": "Entry level",
"employmentType": "Full-time",
"jobFunction": "Analyst and Accounting/Auditing",
"industries": "Financial Services",
"workRemoteAllowed": false,
"workplaceTypes": ["On-site"],
"applyMethod": "Apply",
"applyUrl": null,
"descriptionText": "Chez APSCAC Maroc, filiale du groupe PKF Arsilon en France…",
"descriptionHtml": "<p>…</p>",
"jobPosterName": null,
"jobPosterTitle": null,
"jobPosterPhoto": null,
"jobPosterProfileUrl": null,
"companyDescription": null,
"companyWebsite": null,
"companyEmployeesCount": null,
"searchInput": "data analyst",
"scrapedAt": "2026-09-29T14:25:13.000Z"
}

With job details off, the detail fields (descriptionText, seniorityLevel, applicantsCount…) are null, but the columns are still there.

Also in the run's key-value store:

  • RUN_SUMMARY — status, stopReason (maxItems, budget or null), itemsPushed, searches, jobUrls, invalidInputs, failedCount, failuresByType, durationSeconds, pages, duplicatesSkipped, requests, throttled…
  • FAILED_INPUTS — one entry per skipped input, refused search page or job page: type (input, searchPage, jobDetail, jobUrl), input, reason, jobId/searchUrl, failedAt. Example:
[{ "type": "input", "input": "https://lnkd.in/abc", "reason": "Short lnkd.in link: open it in a browser and paste the full LinkedIn address", "failedAt": "2026-10-09T16:39:34.000Z" }]

What gets filled, and how often

Measured by the author on two runs (80 data-analyst jobs in New York, 60 developer jobs in Morocco): id, link, title, location, postedAt, description, seniority, employment type, industries and applicant count were filled on 100 % of jobs; company name and job function on 97 %; benefits on about half; salaryInfo on 14 % (only when the employer publishes a range); job-poster fields on 3–15 %. In a local check on 2026-11-07 (15 data-analyst jobs in Morocco), jobFunction was empty on all 15 job pages: LinkedIn no longer shows that criterion on many postings, so expect it to be often empty. Company details come back mostly empty: LinkedIn refuses most company pages (999), from datacenter and residential addresses alike.

Visual report

Every run also saves a visual report: one HTML page that shows your jobs as job cards instead of rows.

  • Job cards: company logo (initials when there is none), job title linked to LinkedIn, company (linked to its LinkedIn page), location, posted date, employment type, seniority, workplace (on-site / hybrid / remote), "Actively hiring" / "Early applicant" marks, salary when LinkedIn shows one, applicants, industry and function. With job details on, the recruiter who posted the job and the full job description (click to expand) are shown too.
  • Key figures and charts: jobs, companies, locations, share with salary and remote, median applicants; top companies, top locations and a posting-date timeline. They follow your filters; click a bar to filter.
  • Search, filters and sorting: search titles, companies, locations and descriptions; filter by company, location, employment type, seniority, workplace, salary shown, remote or search; sort by newest, oldest, most applicants or company. Card or table view.
  • Export: download the filtered jobs as JSON or CSV, or the full dataset as JSON, CSV or Excel.
  • Inputs that failed are listed at the top, grouped by error code (BLOCKED, JOB_GONE, NO_TITLE, INVALID_INPUT, UNEXPECTED_ERROR), next to the run warnings (they are not billed). The same code is in the errorCode field of each FAILED_INPUTS entry.
  • Live progress: while the run works, the page shows searches and job URLs done / planned and refreshes itself every 30 s (at most one update a minute). If a run stops on an unexpected error, the report is still saved with the status Failed.
  • Data-quality panel: how complete the fields a buyer checks are (title, company, location, date posted, employment type, seniority, salary, applicants, description, apply link). It measures completeness, not accuracy.

How to open it: in the run, go to the Output tab and choose Visual report (or open the REPORT record of the key-value store).

Logos and recruiter photos are shown straight from LinkedIn in your browser; LinkedIn may change or expire these links, and initials are shown then. Nothing is re-hosted. The report holds the first 2,000 jobs of a run; the full dataset is one click away. Saving the report is never billed and can never make a run fail.

FAQ

Is it legal to scrape LinkedIn jobs? The Actor scrapes only publicly available job postings that LinkedIn shows without login. You are responsible for complying with LinkedIn's terms and with data-protection law (such as the GDPR), in particular for the job-poster fields. See Legal below.

Do I need a LinkedIn account, cookies or an API key? No. The Actor reads the pages LinkedIn serves to signed-out visitors.

Why did I get fewer jobs than maxItems? The search had fewer unique jobs, LinkedIn stopped at its 1,000-per-search ceiling, some jobs were duplicates, or some job pages were refused (see FAILED_INPUTS). Check RUN_SUMMARY for the exact counts.

Is "Maximum jobs per search" exact? Yes. Each search stops at that number of saved jobs, and the Actor only asks LinkedIn for as many result pages as the remaining quota can use.

Am I charged for jobs that failed? No. Only rows written to the dataset are billed, and a job whose page could not be read is not written.

How do I collect more than 1,000 jobs? Switch on Split the search by city and pick a country; each city runs as its own search and duplicates are removed.

Why is salaryInfo empty on most jobs? Most employers do not publish a range. The Actor reads only LinkedIn's own pay block; it never guesses figures from the description.

Why are the company fields empty? LinkedIn refuses most company pages. After 15 refusals in a row the Actor stops asking, so you are not waiting for requests that cannot succeed. A residential proxy did not help in our tests.

Can I filter by experience level or job type? Yes, as search words (see The August 2026 LinkedIn search change). Treat it as steering, not a strict filter.

How do I get only new jobs every day? Set Date posted to Past 24 hours, give Only new jobs: memory name a name (for example daily-data-jobs) and create a daily Schedule with the same input. The IDs of saved jobs are kept in a named key-value store of your account with that name (last 200,000 IDs); the next runs skip those jobs, so a job is saved and billed only once across runs. RUN_SUMMARY.skippedSeenInEarlierRuns says how many were skipped. Leave the name empty to keep every job (the default).

What it can and cannot do

Can: search by keywords, location, geoId, distance, date posted, company IDs, Easy Apply and under-10-applicants; read pasted search URLs, job URLs and job IDs; open each job for its description and criteria; split a search city by city to go past 1,000 jobs; stop exactly at your job count or budget.

Cannot: log in to LinkedIn, read jobs that are not public, apply to jobs, return recruiter e-mails, or guarantee experience level / job type / workplace filters (LinkedIn dropped them from URLs — they are applied as search words). Company details are usually refused by LinkedIn.

Troubleshooting

  • 0 jobs: the search is too narrow — try fewer keywords, a broader location, or open your search URL in a private window to check LinkedIn shows jobs without login.
  • "LinkedIn refused every search": LinkedIn is throttling. Retry a few minutes later, lower Requests at the same time to 4–6, or switch the proxy on. Nothing is billed for refused pages.
  • Warnings about unknown fields or skipped inputs: an input that cannot be used is listed in FAILED_INPUTS with the reason; fix it and run again.
  • Fewer jobs than expected: read RUN_SUMMARY (duplicatesSkipped, filteredByDate, failedCount, stopReason).

Use it via API

Python (apify-client):

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("badr_xba/linkedin-jobs-scraper").call(run_input={
"keywords": "data analyst",
"location": "New York, United States",
"datePosted": "past24h",
"maxItems": 100,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
print(job["title"], "-", job["companyName"], "-", job["link"])

JavaScript (apify-client):

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('badr_xba/linkedin-jobs-scraper').call({
urls: ['https://www.linkedin.com/jobs/search/?keywords=nurse&location=Dubai'],
maxItems: 100,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, 'jobs');

cURL (runs synchronously and returns the jobs as JSON):

curl -X POST "https://api.apify.com/v2/acts/badr_xba~linkedin-jobs-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"keywords": "python developer", "location": "Germany", "maxItems": 50}'

Integrations

  • API: start runs and download results as JSON, CSV or Excel with the Apify API or the Python/JavaScript clients (see above).
  • MCP / AI agents: add the Actor to Claude, Cursor or any MCP client through the Apify MCP server (https://mcp.apify.com) and ask for jobs in plain language.
  • Webhooks: get notified (or trigger your backend) when a run finishes.
  • Make, Zapier, n8n: use the Apify app/node to run the Actor and push each job into your CRM, ATS or spreadsheet.
  • Google Sheets: send the dataset straight to a sheet from the Integrations tab — ideal for a daily job feed.
  • Schedules: run the same search every morning for fresh postings.

The RUN_SUMMARY record makes automations easy: branch on status, itemsPushed or failedCount.

Support

Found a bug, a field that stopped filling, or a country whose city list needs work? Open an issue in the Actor's Issues tab — include the run ID.

Did this Actor save you time? A short review on its Store page helps other recruiters and marketers find it.

Feedback and reviews

If this Actor saved you time, a short review on its Store page helps other users find it. If something did not work as expected, please open an issue on the Issues tab with your run ID: we read every issue.

This Actor only collects publicly available job postings that LinkedIn serves without login. LinkedIn's robots.txt and User Agreement restrict automated access; deciding whether and how to use this tool is your responsibility. Job-poster fields (jobPosterName, jobPosterPhoto, jobPosterProfileUrl) are personal data: under the GDPR and similar laws you are the data controller and need a lawful basis to store or process them. Do not use the data for spam or unsolicited outreach that breaks local law.

En français

Scraper d'offres d'emploi LinkedIn : saisissez des mots-clés et un lieu (ou collez une URL de recherche LinkedIn) et récupérez titre, entreprise, lieu, date, salaire, description complète, séniorité, type de contrat, secteur et nombre de candidats — sans compte ni cookie. Idéal pour le recrutement, la génération de leads (une entreprise qui recrute est un prospect) et l'étude du marché de l'emploi. Le découpage par ville dépasse la limite de 1 000 offres par recherche. Vous ne payez que les offres réellement enregistrées : les échecs sont listés dans FAILED_INPUTS et jamais facturés, et le run s'arrête proprement au plafond maxItems ou au budget maximal. Données publiques uniquement ; le traitement des données personnelles (RGPD) relève de votre responsabilité.