# LinkedIn Jobs Scraper (`dataji/apify-linkdin-jobs`) Actor

Scrape LinkedIn jobs without login or cookies. Get title, company, location, salary, full description, and company details. Search by keyword or paste a LinkedIn search URL, filter by title, company, or salary, and export to JSON, CSV, or Excel.

- **URL**: https://apify.com/dataji/apify-linkdin-jobs.md
- **Developed by:** [Dataji](https://apify.com/dataji) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 241 total users, 59 monthly users, 96.0% runs succeeded, 2 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.80 / 1,000 job results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## LinkedIn Jobs Scraper

**Extract LinkedIn job postings at scale — no login, no cookies, no account-ban risk.**

Collect job data from LinkedIn's public job search — title, company, location,
salary, the full description, and rich company details — and export it to JSON,
CSV, or Excel. Search by keyword and filters, or just paste a LinkedIn search
URL. Because it never logs in, there's no account to get banned.

### ✨ Features

- 🔍 **Search by keyword + filters**, or paste **LinkedIn search URLs** (every filter preserved)
- 🏢 **Company details** — size, industry, website, HQ, founded year, employee & follower counts
- 💰 **Structured salary** — parsed into min / max / currency / period
- 🎯 **Precise filtering** — by title keywords, company, and minimum salary
- 📈 **Beyond the 1,000-result cap** — split by experience/job/workplace type, or by city/country
- 🔁 **Only new jobs** — incremental mode for scheduled daily runs
- 🛡️ **Reliable** — proxy rotation, adaptive throttling, auto-retry, resume on interruption
- 📄 **No login required** — reads only public, logged-out data

### 💡 Use cases

- **Job boards & aggregators** — a fresh feed of postings for a niche or region
- **Recruiting & sourcing** — see who's hiring for a role, company, or location
- **Market & salary research** — analyze demand, pay, and hiring trends
- **Lead generation** — companies actively hiring = a buying signal

### 🚀 How to use

1. Enter **keywords** and a **location** — or paste one or more **LinkedIn job-search URLs** into `searchUrls`.
2. (Optional) turn on **company details**, add **filters**, or **split** the search to collect more than 1,000 results.
3. Click **Start**. Results stream to the dataset, ready to export as JSON, CSV, Excel, or via the API.

### Input parameters

#### Search

| Field | Type | Description |
| --- | --- | --- |
| `searchUrls` | array | Paste LinkedIn job-search URLs (from `linkedin.com/jobs/search`). Overrides the fields below and keeps **every** filter in the URL. Multiple allowed. |
| `keywords` | string | Search terms — job title, skill, or company. Required unless `searchUrls` is set. |
| `location` | string | City, region, or country. Empty = worldwide. |
| `datePosted` | enum | `any`, `past24Hours`, `pastWeek`, `pastMonth`. |
| `jobTypes` | array | `fullTime`, `partTime`, `contract`, `temporary`, `internship`, `volunteer`. |
| `workplaceTypes` | array | `onSite`, `remote`, `hybrid`. |
| `experienceLevels` | array | `internship`, `entryLevel`, `associate`, `midSenior`, `director`, `executive`. |
| `sortBy` | enum | `recent` or `relevance` ordering. |
| `companyIds` | array | Restrict to specific companies by their LinkedIn company ID (the number in a company page URL). |
| `geoId` | string | Exact LinkedIn geo ID (advanced) — overrides `location`. |

#### How much to collect

| Field | Type | Description |
| --- | --- | --- |
| `maxResults` | integer | Stop after this many jobs (default 100). |
| `scrapeDetails` | boolean | Fetch each job's detail page for the full description and metadata (default `true`). |
| `scrapeCompany` | boolean | Also fetch each company's page for size, industry, website, HQ, etc. (default `false`). |
| `splitBy` | array | Split the query by `experienceLevel` / `jobType` / `workplaceType` to exceed the ~1,000-result cap (see below). |
| `splitByCountry` | string | Split across a country's major cities (e.g. `United States`). |
| `splitLocations` | array | Split across your own list of locations (overrides `splitByCountry`). |

#### Filter the results

| Field | Type | Description |
| --- | --- | --- |
| `titleIncludeKeywords` | array | Keep only jobs whose title contains any of these. |
| `titleExcludeKeywords` | array | Drop jobs whose title contains any of these (e.g. `senior`, `manager`). |
| `excludeCompanies` | array | Drop jobs from these companies. |
| `minSalary` | integer | Drop jobs whose salary max is below this (jobs with no salary shown are kept). |
| `onlyWithSalary` | boolean | Keep only jobs that show a salary. |

#### Incremental & advanced

| Field | Type | Description |
| --- | --- | --- |
| `onlyNewJobs` | boolean | Skip jobs seen in previous runs of this Actor (tracked automatically) — ideal for schedules. |
| `skipJobIds` | array | Job IDs to exclude (pass a previous run's IDs to get only new postings). |
| `requestDelaySecs` | integer | Delay between requests (default 1). Raise if you hit rate limits. |
| `detailConcurrency` | integer | Parallel detail-page fetches (default 3). |
| `proxyConfiguration` | object | **Residential proxy by default** (most reliable — LinkedIn heavily blocks datacenter IPs); switch to **Datacenter** to save cost on small runs. |

#### Example input

```json
{
    "keywords": "data engineer",
    "location": "Berlin, Germany",
    "datePosted": "pastWeek",
    "jobTypes": ["fullTime"],
    "workplaceTypes": ["remote", "hybrid"],
    "maxResults": 200,
    "scrapeCompany": true
}
```

### Output

One item per job:

```json
{
    "jobId": "3801234567",
    "title": "Senior Data Engineer",
    "companyName": "Acme Corp",
    "companyUrl": "https://www.linkedin.com/company/acme",
    "location": "Berlin, Germany",
    "postedAt": "2026-06-25",
    "jobAgeDays": 6,
    "jobUrl": "https://www.linkedin.com/jobs/view/3801234567",
    "description": "We are looking for...",
    "descriptionHtml": "<p>We are looking for…</p>",
    "seniorityLevel": "Mid-Senior level",
    "employmentType": "Full-time",
    "jobFunction": "Engineering and Information Technology",
    "industries": "Software Development",
    "applicants": 47,
    "salary": "€70,000 - €90,000",
    "salaryMin": 70000,
    "salaryMax": 90000,
    "salaryCurrency": "EUR",
    "salaryPeriod": "yearly",
    "applyType": "offsite",
    "easyApply": false,
    "isActive": true,
    "companyLogo": "https://media.licdn.com/...",
    "scrapedAt": "2026-07-01T12:00:00+00:00",

    "companySize": "501-1,000 employees",
    "companyIndustry": "Software Development",
    "companyWebsite": "https://acme.com",
    "companyHeadquarters": "Berlin, Germany",
    "companyFounded": "2014",
    "companyEmployeesOnLinkedIn": 3506,
    "companyFollowers": 561227
}
```

The `company*` fields appear only when `scrapeCompany` is on. Fields LinkedIn
doesn't show (e.g. `salary`) come back as `null`. See **What guest mode can't
see** below for the few fields that require a login.

### Collecting more than 1,000 results

LinkedIn caps every search at **1,000 results**. To collect more, the Actor runs
narrower sub-queries — each with its own 1,000 ceiling — and merges and
de-duplicates them:

- **By filter dimension** — `splitBy: ["experienceLevel", "jobType"]` runs 6 × 6 = 36 sub-queries (~36,000 jobs).
- **By location** — `splitByCountry: "United States"` runs the search across major US cities; or pass your own `splitLocations`. This stacks with `splitBy`.

Each job is tagged with the sub-query (`searchVariant`) it came from.

### Export & integrations

- **Download** results as JSON, CSV, Excel, HTML, or RSS.
- **Send to** Google Sheets, Slack, Google Drive, GitHub, Zapier, Make, or a webhook via the **Integrations** tab — no code.
- **Schedule** it (e.g. daily) and pair with `onlyNewJobs` to get only fresh postings each run.
- **API** — trigger runs and fetch results from any language via the Apify API or Python/JS clients (see the **API** tab).

Every record carries `scrapedAt` and `jobAgeDays` so you can track freshness.

### What guest mode can't see

The Actor reads only public, logged-out data, so a few things LinkedIn hides
behind login are **not** available (and we don't fabricate them):

- **External apply URL** — LinkedIn shows guests a sign-up wall instead of the real link. We report `applyType` (`offsite` / `easyApply`), but `applyUrl` is usually `null`.
- **Exact applicant count** — guest pages cap it ("Over 200 applicants"), so `applicants` is a floor.
- **Recruiter / job-poster details and required-skill lists** — login-only.
- **Exact workplace type** per job — only set when your query makes it unambiguous.

Unlocking these would require an authenticated session, which violates
LinkedIn's Terms of Service — so this Actor deliberately stays login-free.

### FAQ

**Do I need a LinkedIn account or cookies?**
No. It reads only public data — nothing to log in with, and no account-ban risk.

**Which proxy should I use?**
Residential (the default) is the most reliable — LinkedIn heavily rate-limits datacenter IPs. Switch to Datacenter to save cost on small, low-volume runs.

**How many jobs can I get from one search?**
LinkedIn caps each query at ~1,000. Use `splitBy` and `splitByCountry` / `splitLocations` to collect far more.

**Can I get the external apply link or recruiter details?**
No — LinkedIn hides those behind login (see *What guest mode can't see*).

**How do I get only new jobs each day?**
Turn on `onlyNewJobs` and schedule the Actor — it skips jobs seen in previous runs.

**What can I export?**
JSON, CSV, Excel, HTML, or RSS — via the Console or API.

**Is this legal?**
It scrapes only public data, but scraping LinkedIn is against their Terms of
Service. Use it responsibly, keep volume reasonable, and check the laws that
apply to you.

# Actor input Schema

## `searchUrls` (type: `array`):

Paste one or more LinkedIn job-search URLs (from linkedin.com/jobs/search). Overrides the keyword/filter fields below and preserves every filter in the URL. Leave empty to use the structured fields.

## `keywords` (type: `string`):

Search terms, e.g. job title, skill, or company. Ignored if 'searchUrls' is set.

## `location` (type: `string`):

City, region, or country to search in. Leave empty for worldwide.

## `geoId` (type: `string`):

Exact LinkedIn geo identifier for precise location filtering. Overrides free-text matching of 'location'. Find it in the geoId= query param of a linkedin.com/jobs/search URL.

## `companyIds` (type: `array`):

Restrict the search to these LinkedIn company IDs (the number in a company page URL / the f\_C= URL param). Structured mode only.

## `skipJobIds` (type: `array`):

Job IDs to treat as already seen and exclude. Feed the IDs from a previous run here to collect only NEW postings (ideal for scheduled daily runs).

## `sortBy` (type: `string`):

Order of the LinkedIn search results.

## `datePosted` (type: `string`):

Only return jobs posted within this time window.

## `jobTypes` (type: `array`):

Employment types to include. Empty = all.

## `workplaceTypes` (type: `array`):

On-site / remote / hybrid. Empty = all.

## `experienceLevels` (type: `array`):

Seniority filter. Empty = all.

## `maxResults` (type: `integer`):

Stop after collecting this many jobs. Caps run time and cost.

## `splitByCountry` (type: `string`):

Run the search once per major city of the chosen country, then de-duplicate. A curated starter list of metros per country. Ignored if 'splitLocations' is set.

## `splitLocations` (type: `array`):

Run the search once per location listed here (cities/regions), then de-duplicate. Overrides 'splitByCountry'. Use to exceed the ~1000-result cap with your own location list.

## `splitBy` (type: `array`):

LinkedIn caps each query near 1000 results. Split the search into sub-queries by these dimensions (the cartesian product is run, then de-duplicated). Pick more than one to multiply coverage, e.g. experience level × job type = 36 sub-queries.

## `scrapeDetails` (type: `boolean`):

Fetch each job's detail page for full description, seniority, function, industry, and applicant count. One extra request per job.

## `titleIncludeKeywords` (type: `array`):

Keep only jobs whose title contains at least one of these keywords (case-insensitive).

## `titleExcludeKeywords` (type: `array`):

Drop jobs whose title contains any of these keywords (e.g. 'senior', 'manager').

## `excludeCompanies` (type: `array`):

Drop jobs from these companies (case-insensitive substring match on company name).

## `minSalary` (type: `integer`):

Drop jobs whose parsed salary maximum is below this. Jobs with no salary shown are kept.

## `onlyWithSalary` (type: `boolean`):

Keep only jobs that display a salary.

## `onlyNewJobs` (type: `boolean`):

Skip jobs seen in previous runs of this Actor (tracked automatically). Ideal for scheduled daily runs.

## `scrapeCompany` (type: `boolean`):

Fetch each unique company's page for size, industry, website, headquarters, founded year, follower/employee counts. One extra request per unique company.

## `requestDelaySecs` (type: `integer`):

Base delay between requests (jittered). The default of 1 suits residential proxies; raise it if you use datacenter or hit rate limits.

## `detailConcurrency` (type: `integer`):

How many job detail pages to fetch in parallel. Lower = gentler on rate limits.

## `proxyConfiguration` (type: `object`):

Residential proxy (the default) is the most reliable — LinkedIn heavily rate-limits datacenter IPs. For small, low-volume runs you can switch to Datacenter to save cost.

## Actor input object example

```json
{
  "searchUrls": [],
  "keywords": "data engineer",
  "location": "Berlin, Germany",
  "companyIds": [],
  "skipJobIds": [],
  "sortBy": "",
  "datePosted": "any",
  "jobTypes": [],
  "workplaceTypes": [],
  "experienceLevels": [],
  "maxResults": 100,
  "splitByCountry": "",
  "splitLocations": [],
  "splitBy": [],
  "scrapeDetails": true,
  "titleIncludeKeywords": [],
  "titleExcludeKeywords": [],
  "excludeCompanies": [],
  "minSalary": 0,
  "onlyWithSalary": false,
  "onlyNewJobs": false,
  "scrapeCompany": false,
  "requestDelaySecs": 1,
  "detailConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `jobs` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchUrls": [],
    "keywords": "python developer",
    "location": "United States"
};

// Run the Actor and wait for it to finish
const run = await client.actor("dataji/apify-linkdin-jobs").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchUrls": [],
    "keywords": "python developer",
    "location": "United States",
}

# Run the Actor and wait for it to finish
run = client.actor("dataji/apify-linkdin-jobs").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchUrls": [],
  "keywords": "python developer",
  "location": "United States"
}' |
apify call dataji/apify-linkdin-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=dataji/apify-linkdin-jobs",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/d1gs0RHIwEnsan7XX/builds/SYfZ5ESXFD2d6gF4v/openapi.json
