# LinkedIn Jobs Search Scraper (`piotrv1001/linkedin-jobs-search-scraper`) Actor

The LinkedIn Jobs Search Scraper extracts job postings by keyword and location, breaking past LinkedIn’s 1,000-result cap and removing duplicates, capturing titles, companies, locations, posted dates, salaries, seniority, applicant counts, and company profiles — ideal for recruitment analysis.

- **URL**: https://apify.com/piotrv1001/linkedin-jobs-search-scraper.md
- **Developed by:** [FalconScrape](https://apify.com/piotrv1001) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 50.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 1,000 job listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Search Scraper

Search LinkedIn jobs by keyword and location and export every matching posting to JSON, CSV or Excel — including the ones a normal scraper can't reach.

LinkedIn serves at most **1,000 results per search**, no matter how many jobs actually match. A search for "engineer" in the United States matches over 300,000 postings; ask for them and you get 1,000. This scraper detects when a search hits that ceiling, splits it into narrower searches, runs them, and merges everything with duplicates removed — so you get the jobs, not the ceiling.

Every run tells you how many jobs LinkedIn says match versus how many were actually returned. If your results are incomplete, you'll know.

### ✨ Features

- **Goes past the 1,000-result limit.** Automatic search splitting and merging. Measured on "engineer" / United States: **1,500 unique jobs, zero duplicates, in 191 requests** — where the plain search tops out at 1,000.
- **Deduplicated, always.** LinkedIn repeats results across its own result pages, and overlaps between split searches are unavoidable. Every job appears once, keyed on its LinkedIn job ID.
- **Multiple titles and locations in one run.** Every keyword is searched against every location.
- **Three levels of depth**, so you pay for what you need:
  - **Listing** — title, company, location, posted date, logo, links
  - **+ Job detail** — full description, posted salary parsed into min/max/currency/period, seniority, employment type, job function, industries, applicant count, apply type
  - **+ Company** — followers, employee count, industry, website, headquarters, founded, specialties
- **Company data is charged once per company, not once per job.** A 500-job run usually spans far fewer companies, and the work is only done once.
- **Worldwide by default.** Leave the location empty and you get jobs from everywhere, not just the country the server happens to sit in.
- **Only filters that actually work.** Posted-within window, keyword and location all genuinely narrow the search. Workplace type, experience level, job type and salary band are deliberately not offered: LinkedIn accepts them on this endpoint and then ignores them, so exposing them would hand you unfiltered data that looks filtered.
- **Honest truncation reporting.** Every run writes a `RUN_SUMMARY` with `returned`, `estimatedTotal`, `truncated` and the reason.
- **No proxy needed.** Runs clean without one; proxy support is there as a fallback for high volume, not a requirement.

### 🛠️ How It Works

1. Enter your search keywords and locations, plus any filters.
2. Choose how much data you want per job — listing, detail, or detail + company.
3. Run it. Any search that hits LinkedIn's 1,000-result ceiling is automatically split and merged.
4. Export to JSON, CSV, Excel, or pull it from the API.

A capped search is split into narrower posting-date windows, which is the one dimension LinkedIn's guest search actually honours, and the results are merged on job ID. If **you** set a posted-within window, splits stay inside it — you never get jobs older than you asked for.

### 📋 Output Fields

#### Listing (every run)

| Field           | Description                                                |
| --------------- | ---------------------------------------------------------- |
| `jobId`         | LinkedIn job posting ID — the deduplication key            |
| `jobUrl`        | Canonical LinkedIn job URL                                 |
| `title`         | Job title                                                  |
| `companyName`   | Hiring company name                                        |
| `companyUrl`    | Company LinkedIn URL, normalised so it works as a join key |
| `companySlug`   | Company slug                                               |
| `location`      | Job location                                               |
| `postedDate`    | Posting date (YYYY-MM-DD)                                  |
| `postedTimeAgo` | Relative posting age, e.g. "2 weeks ago"                   |
| `companyLogo`   | Company logo image                                         |
| `sourceQuery`   | Which search this job came from                            |
| `scrapedAt`     | ISO 8601 scrape timestamp                                  |

#### Job detail

| Field                             | Description                                           |
| --------------------------------- | ----------------------------------------------------- |
| `description`                     | Full job description text                             |
| `salaryText`                      | Posted salary range as displayed                      |
| `salaryMin` / `salaryMax`         | Parsed salary bounds                                  |
| `salaryCurrency` / `salaryPeriod` | Currency, and `year` / `hour` / `month`               |
| `seniorityLevel`                  | Entry level, Mid-Senior level, ...                    |
| `employmentType`                  | Full-time, Part-time, Contract, ...                   |
| `jobFunction`                     | Job function                                          |
| `industries`                      | Industries                                            |
| `applicantCount`                  | Applicant count as a number                           |
| `numApplicantsCaption`            | Applicant caption as displayed                        |
| `applyType`                       | `easyApply` or `offsite` (external ATS)               |
| `jobTitle` / `jobLocation`        | Title and location from the posting page              |
| `criteria`                        | Original name/value criteria list                     |
| `similarJobs`                     | Similar jobs LinkedIn surfaces alongside this posting |
| `peopleAlsoViewed`                | Jobs that viewers of this posting also viewed         |

#### Company

| Field                  | Description                                         |
| ---------------------- | --------------------------------------------------- |
| `companyId`            | Numeric LinkedIn company ID                         |
| `companyFollowers`     | LinkedIn follower count                             |
| `companySize`          | Employee band as published, e.g. "51-200 employees" |
| `companyEmployeeCount` | Upper bound of that band, as a number               |
| `companyIndustry`      | Company industry                                    |
| `companyWebsite`       | Company website                                     |
| `companyHeadquarters`  | Headquarters location                               |
| `companyDescription`   | Company About text                                  |
| `companyTagline`       | Company tagline                                     |
| `companyFounded`       | Year founded                                        |
| `companySpecialties`   | Company specialties                                 |
| `companyType`          | Public company, Privately Held, ...                 |

Salary is only present where the employer posts it — common in the US, rare elsewhere. Fields with no data are `null`, never invented.

### 📊 Sample Output Data

```json
{
    "jobId": "4442224163",
    "jobUrl": "https://www.linkedin.com/jobs/view/4442224163",
    "title": "Critical Facility Engineer II (Day Shift)",
    "companyName": "Salute",
    "companyUrl": "https://www.linkedin.com/company/inc-salute",
    "companySlug": "inc-salute",
    "location": "Cheyenne, WY",
    "postedDate": "2026-07-20",
    "postedTimeAgo": "2 weeks ago",
    "companyLogo": "https://media.licdn.com/dms/image/v2/D560BAQFxKcXt6-AAgw/company-logo_100_100/0/1709501595672/salute_dcs_logo",
    "sourceQuery": "sortBy=DD&keywords=engineer&location=United States",
    "scrapedAt": "2026-08-05T12:51:05.793Z"
}
```

With `detail + company` depth, each row also carries the description, parsed salary, seniority, applicant count and the hiring company's profile:

```json
{
    "jobId": "4437106135",
    "title": "Registered Nurse - Ambulatory",
    "companyName": "Incredible Health",
    "location": "Quincy, MA",
    "postedDate": "2026-06-10",
    "salaryText": "$76,003.20 - $163,678.32",
    "salaryMin": 76003.2,
    "salaryMax": 163678.32,
    "salaryCurrency": "$",
    "salaryPeriod": "year",
    "seniorityLevel": "Entry level",
    "employmentType": "Full-time",
    "jobFunction": "Health Care Provider",
    "industries": "Health and Human Services",
    "applicantCount": 25,
    "applyType": "offsite",
    "companyFollowers": 26902,
    "companySize": "51-200 employees",
    "companyEmployeeCount": 200,
    "companyIndustry": "Software Development",
    "companyWebsite": "http://www.incrediblehealth.com",
    "companyHeadquarters": "San Francisco, California",
    "companyFounded": "2017"
}
```

### 💰 Pricing

Pay only for what you extract:

| Event                                  | Price (Free plan) | Price (Gold plan) |
| -------------------------------------- | ----------------- | ----------------- |
| Job listing                            | $0.0008           | $0.0004           |
| Job with full details                  | $0.003            | $0.0012           |
| Company profile *(per unique company)* | $0.01             | $0.004            |

A 500-job run with full job details costs about **$1.50** on the Free plan and **$0.60** on Gold.

### ❓ FAQ

**Why don't I get all 319,000 jobs LinkedIn says match?**
LinkedIn serves 1,000 results per search. Splitting a search multiplies what's reachable but doesn't make it unlimited. Narrow your search — one city instead of a whole country, one keyword instead of a broad one — and run several. The `RUN_SUMMARY` tells you exactly when and why a run stopped short.

**Do I need a proxy?**
No. Leave it off unless you're running at high volume and start seeing failures.

**Do I need a LinkedIn account or cookies?**
No. Only publicly visible job data is collected.

**Can I get jobs for a specific company?**
Use the [LinkedIn Company Jobs Scraper](https://apify.com/piotrv1001/linkedin-company-jobs-scraper), which resolves the company properly instead of keyword-matching its name.

# Actor input Schema

## `keywords` (type: `array`):

Job titles or search terms. Each keyword is combined with each location, so 3 keywords and 2 locations run 6 searches.

## `locations` (type: `array`):

Locations to search, as written on LinkedIn — a country, region or city (e.g. "United States", "Greater London", "Berlin, Germany"). Leave empty to search worldwide.

## `postedWithin` (type: `string`):

Only return jobs posted in this window.

## `enrichment` (type: `string`):

Listing returns the search card fields. Detail also opens each posting for the description, salary, seniority, employment type, job function, industries, applicant count and apply link. Detail + company additionally adds the hiring company's profile — charged once per company, not once per job.

## `maxItems` (type: `integer`):

Stop after this many jobs.

## `autoFanOut` (type: `boolean`):

LinkedIn serves at most 1,000 results per search. When a search hits that ceiling this automatically splits it into narrower searches and merges the results, removing duplicates. Turn off to get the plain first 1,000 only.

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

Optional. Not required — leave off unless you are running at high volume and start seeing failures.

## Actor input object example

```json
{
  "keywords": [
    "software engineer"
  ],
  "locations": [
    "United States"
  ],
  "postedWithin": "",
  "enrichment": "listing",
  "maxItems": 500,
  "autoFanOut": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (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 = {
    "keywords": [
        "software engineer"
    ],
    "locations": [
        "United States"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("piotrv1001/linkedin-jobs-search-scraper").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 = {
    "keywords": ["software engineer"],
    "locations": ["United States"],
}

# Run the Actor and wait for it to finish
run = client.actor("piotrv1001/linkedin-jobs-search-scraper").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 '{
  "keywords": [
    "software engineer"
  ],
  "locations": [
    "United States"
  ]
}' |
apify call piotrv1001/linkedin-jobs-search-scraper --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/3TcsyUSTlXma8EV16/builds/dd1CIGkiciIYC07uc/openapi.json
