You can scrape LinkedIn jobs from LinkedIn's public job search, the version anyone sees without logging in. You get titles, companies, locations, posting dates, seniority, full descriptions and company details. The fastest way is to run a ready-made Actor from the Scrape.ly Store with a keyword and a location, then export the results as CSV, JSON or Excel.
This guide covers public job listings only. It doesn't cover member profiles, and nothing here needs a LinkedIn account. You'll see the fields you get, how to run a search with no code, and how to do it from curl, Python, JavaScript and an AI agent. It also covers cost, limits and LinkedIn's terms. The examples use a real run from 22 October 2026.
What data you can get from LinkedIn job listings
Each job is one row in the dataset. A field is present only when the public page has it, so some rows carry fewer fields than others.
| Field | What it holds |
|---|---|
id, link | LinkedIn job ID and the job page URL |
title | Job title as posted |
companyName | Employer name |
companyLinkedinUrl | The employer's LinkedIn company page |
location | Location text, e.g. "Berlin, Berlin, Germany" |
postedAt | Posting date (YYYY-MM-DD) |
employmentType | Full-time, part-time and so on |
seniorityLevel | Seniority from the job's criteria block, including "Not Applicable" |
industries | Industry from the criteria block |
applicantsCount | Applicant count, only when the page says exactly "N applicants" |
salaryInfo, salaryInsights | Pay range amounts and LinkedIn's pay note, when the job lists them |
descriptionText, descriptionHtml | The full public job description |
applyMethod | Offsite or Easy Apply |
companyDescription, companyWebsite, companyEmployeesCount | From the company page, when scrapeCompany is on |
What you don't get: the recruiter's name, title, photo and profile URL aren't on the logged-out job page, so the Actor doesn't return them. The apply link sits behind sign-in, so no apply URL is stored either.
How to scrape LinkedIn jobs without code
- Open the LinkedIn Jobs Scraper in the Scrape.ly Store and sign in.
- Pick the preset Data engineer jobs in Berlin this week, 10 jobs. It fills in the input below.
- Check the input. This is the exact JSON we ran:
{
"keywords": "data engineer",
"location": "Berlin",
"distance": 0,
"datePosted": "pastWeek",
"limitPerSource": 10
}- Click Start. Our run finished in about 46 seconds.
- Open the dataset and export it as JSON, CSV or Excel. The Jobs view shows title, company, location, posting date, employment type, applicant count and link.
The input fields that matter most:
keywordsandlocation: what you'd type into LinkedIn's job search.distancesets the radius in miles around the location; leave it empty for LinkedIn's default.datePosted:anyTime(the default),past24Hours,pastWeekorpastMonth. A daily run withpast24Hourscollects what was posted since yesterday.limitPerSource: the maximum number of jobs per search. The default is 20, and LinkedIn returns at most about 1,000 per search.urls: paste one or more public LinkedIn job search URLs instead. Whenurlsis set, the keyword and filter fields are ignored. Build the search in a private window so you get the public version.scrapeCompany: on by default. It loads each employer's company page for the about text, website and headcount. That means extra requests per job, so runs take longer.
How to scrape LinkedIn jobs with the API
Get your API token from Settings → API & Integrations in the dashboard and export it as SCRAPELY_TOKEN. The Actors API docs list every endpoint.
curl. This search took about 46 seconds, which is longer than the 45-second limit of the synchronous endpoint. So start the run, wait for it, then read the items:
# 1. start the run; the response's data.id is the run ID
curl -X POST "https://api.scrape.ly/v2/actors/openrole~linkedin-jobs-scraper/runs" \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"keywords": "data engineer", "location": "Berlin", "distance": 0, "datePosted": "pastWeek", "limitPerSource": 10}'
# 2. wait (up to 60 s per call; repeat until status is SUCCEEDED)
curl "https://api.scrape.ly/v2/actor-runs/RUN_ID?waitForFinish=60" \
-H "Authorization: Bearer $SCRAPELY_TOKEN"
# 3. read the items (CSV here)
curl "https://api.scrape.ly/v2/actor-runs/RUN_ID/dataset/items?format=csv" \
-H "Authorization: Bearer $SCRAPELY_TOKEN"Python. Install the client with pip install scrapely-client-python. call() waits for the run however long it takes, so the 46 seconds don't matter here. See the Python client docs.
import os
from scrapely_client import ScrapelyClient
client = ScrapelyClient(os.environ["SCRAPELY_TOKEN"], api_url="https://api.scrape.ly")
run = client.actor("openrole/linkedin-jobs-scraper").call(
run_input={"keywords": "data engineer", "location": "Berlin", "distance": 0, "datePosted": "pastWeek", "limitPerSource": 10}
)
print(run.status) # SUCCEEDED, FAILED, ABORTED or TIMED-OUT
for item in client.dataset(run.default_dataset_id).iterate_items():
print(item["title"], item["companyName"], item.get("applicantsCount"))JavaScript. Install with npm install scrapely-client. See the JavaScript client docs.
import { ScrapelyClient } from 'scrapely-client';
const client = new ScrapelyClient({ token: process.env.SCRAPELY_TOKEN, baseUrl: 'https://api.scrape.ly' });
const run = await client.actor('openrole/linkedin-jobs-scraper').call({
keywords: 'data engineer',
location: 'Berlin',
distance: 0,
datePosted: 'pastWeek',
limitPerSource: 10,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]);AI agents (MCP). Scrape.ly runs a hosted MCP server at https://api.scrape.ly/mcp. In Cursor, add it to ~/.cursor/mcp.json:
{
"mcpServers": {
"scrapely": {
"url": "https://api.scrape.ly/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}In Claude Code, run claude mcp add --transport http scrapely https://api.scrape.ly/mcp, then /mcp to sign in. The agent's call-actor tool waits up to 45 seconds, so for this search it starts the run and then polls get-actor-run before it reads the results with get-actor-output. Try a prompt like: "Use scrapely to run openrole/linkedin-jobs-scraper for data engineer jobs in Berlin posted in the past week, 10 jobs, and give me a CSV of title, company, posting date and applicant count." The MCP docs cover OAuth and other clients.
A real run: data engineer jobs in Berlin this week
We ran the preset on 22 October 2026. It saved 10 jobs in 45.961 seconds with 512 MB of memory, and the run was charged $0.0181. Here are five of the rows (the job link column is left out for width):
| title | companyName | location | postedAt | employmentType | seniorityLevel | applicantsCount |
|---|---|---|---|---|---|---|
| Domain Data Engineer - Power Grid & System Operations (w/m/d) | 50Hertz Transmission GmbH | Berlin, Berlin, Germany | 2026-10-22 | Full-time | Mid-Senior level | 56 |
| Data Engineer - Digital Health Excellence Center (w/m/d) | EY | Berlin, Berlin, Germany | 2026-10-19 | Full-time | Mid-Senior level | — |
| Data Engineer (all genders) in Berlin - Jobbird.com | Jobster | Berlin, Berlin, Germany | 2026-10-22 | Full-time | Not Applicable | — |
| Data Engineer (f/m/x) | Netconomy | Berlin, Berlin, Germany | 2026-10-21 | Full-time | Mid-Senior level | 50 |
| Senior Data Engineer - Shop Analytics (all genders) | ABOUT YOU | Berlin, Berlin, Germany | 2026-10-16 | Full-time | Mid-Senior level | 73 |
The rows hold company data only. We shortened long text in our sample, but there were no names to mask.
The console shows where the time went:
2026-10-22T15:22:59.535Z [scrapely.proxy_configuration] INFO Platform proxy access check succeeded.
2026-10-22T15:23:00.765Z [scrapely.actor] INFO Fetching search offset 0 (0 kept)
2026-10-22T15:23:01.551Z [scrapely.actor] INFO Search offset 0 kept 10 new cards (10 total)
2026-10-22T15:23:01.551Z [scrapely.actor] INFO Search list ended with 10 jobs: reached limit
2026-10-22T15:23:42.582Z [scrapely.actor] INFO Stored 10 jobs with descriptions
2026-10-22T15:23:43.183Z [Scrapely] Container state: status=exited exitCode=0 oomKilled=falseThe search itself took under a second. Most of the run, about 41 seconds, went into opening each job page and each company page, because scrapeCompany was on. All posting dates fall between 16 and 22 October, inside the pastWeek window. Two jobs have no applicantsCount, since the Actor only fills it when LinkedIn prints an exact "N applicants" caption.
What it costs
The Actor charges one event: $0.0015 per job saved to the dataset. Company details from scrapeCompany are included at no extra charge. Platform usage (compute, proxy traffic and storage) is billed on top.
For this run, 10 jobs × $0.0015 = $0.015 in job fees. The run was charged $0.0181 in total because platform usage added $0.00153 in compute, $0.00150 in residential proxy traffic and $0.00005 in dataset writes.
At the same price, 1,000 jobs cost $1.50 in event fees, plus platform usage. If you set a maximum cost per run, the run saves as many jobs as that budget pays for, then stops cleanly.
Limits and common problems
- The synchronous endpoint times out. A 10-job search with company pages took 46 seconds.
run-sync-get-dataset-itemswaits only 45 seconds, then answers408while the run keeps going. Don't call it again, because that starts a second run. Use the async flow or a client. - About 1,000 jobs per search. LinkedIn stops there. To go further, turn on
splitByLocationand setsplitCountry. The search then repeats across major cities in that country and drops duplicate job IDs. - Old search URLs lose filters. LinkedIn's AI job search dropped classic filters such as experience, job type, workplace, salary and sort. With
autoConvertToAiSearchon (the default), those codes become keywords. Industry, function and title codes (f_I,f_F,f_T) aren't converted, so put those ideas inkeywordsyourself. - Fields come and go. Salary, applicant count and headcount appear only when the public page shows them. A headcount range such as "10,001+" isn't turned into a number.
- Memory for big runs. The README sizes a single search for 1 GB, though our 10-job run used 512 MB. Raise memory when you pass many URLs or split by city. A URL that isn't a LinkedIn job search is skipped and listed under
failedInputsin the run'sOUTPUTrecord.
Is it legal to scrape LinkedIn jobs?
Job listings on LinkedIn's public job search are visible without an account, and this Actor reads only those logged-out pages. That doesn't make every use fine. LinkedIn's User Agreement lists scraping among its "Don'ts" in section 8.2: you may not use software, scripts, robots or crawlers "to scrape or copy the Services." So scraping LinkedIn conflicts with its terms, whatever the data. Listings are mostly company data, but descriptions sometimes name a contact person. Any personal data you store falls under privacy laws such as the GDPR and CCPA, so keep only what you need and don't build profiles of people. This is general information, not legal advice. If the data feeds a product or a sales process, ask a lawyer about your use case and jurisdiction.
Alternatives
- LinkedIn's official Job Posting API. It lets approved partners, such as applicant tracking systems and job distributors, post jobs to LinkedIn. It isn't for reading listings, and LinkedIn says it isn't accepting new partnerships. It's only the better choice if you already have that partnership.
- JobSpy. An open-source Python library that searches LinkedIn, Indeed, Glassdoor, ZipRecruiter and other boards into one dataframe. Pick it if you want several job boards in one script and can run your own proxies. Its README notes that LinkedIn answers bursts of requests with a 429 error.
- linkedin-jobs-scraper. An MIT-licensed Python package that drives a headless browser with Selenium. The author marks it for personal or educational use.
- Build it yourself. A headless browser or plain HTTP requests against the public job pages work, as tutorials like Scrapfly's show. You'll own the parsing, retries and blocking, and you'll fix it when LinkedIn changes its markup. That's a fair trade for a one-off project.
FAQ
Can you scrape LinkedIn jobs without an account?
Yes. The public job search and job pages open without logging in, and the Actor uses only those. You don't need cookies or a LinkedIn login. Recruiter details and apply links sit behind sign-in, so you won't get them.
How do I scrape LinkedIn job postings to CSV?
Run the Actor, open the dataset and choose CSV in the export menu. From the API, add ?format=csv to the dataset items URL, as in step 3 of the curl example. The datasets API docs list the other formats.
How many LinkedIn jobs can I scrape per search?
LinkedIn returns at most about 1,000 jobs per search. Set limitPerSource up to that. For larger markets, split the search by city with splitByLocation and splitCountry, or pass several search URLs.
How much does LinkedIn job scraping cost?
$0.0015 per job, so $1.50 per 1,000 jobs in event fees, plus platform usage. Our 10-job run was charged $0.0181 in total, company details included.




