# LinkedIn Job Scraper (`datascrapers/linkedin-job-scraper`) Actor

LinkedIn job dataset from keywords or search URLs, including title, company, location, employment type, and optional company-page fields.

- **URL**: https://apify.com/datascrapers/linkedin-job-scraper.md
- **Developed by:** [Farhan Ali](https://apify.com/datascrapers) (community)
- **Categories:**
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 jobs

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#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 Job Scraper** creates a structured dataset of job postings collected from [LinkedIn](https://www.linkedin.com). Each dataset item represents one LinkedIn job and can include identifiers, title, description, posting dates, employment type, company name and URL, location fields, coordinates, industry, skills, experience, and education. Query the source with keyword searches (`searchQueries`) plus locations and filters, or with LinkedIn job search URLs in `startUrls` (these override `searchQueries` when provided). Control the result limit with `maxItems`, and retrieve records through the Apify Dataset API or export them as JSON, CSV, Excel, XML, or another supported format.

### Dataset at a glance

| Property | Value |
|---|---|
| Source | linkedin.com (public job listings) |
| Record unit | One LinkedIn job |
| Input methods | Keyword search (`searchQueries`) plus `locations` and filters, or LinkedIn job search URLs (`startUrls`) |
| Main identifiers | `jobId`, `url` |
| Delivery | Apify Dataset and API |
| Export formats | JSON, CSV, Excel, XML, HTML (Apify dataset exports) |
| Update model | Fresh records per Actor run |
| Pricing | $0.00005 per Actor start; $0.001 per job ($1 per 1,000 jobs) |

### Coverage and available records

The Actor collects public LinkedIn job listings using one of two entry points:

- **Search-based**: Set `searchQueries` to job titles or keywords (for example `Python Developer`). Each query is a separate search. `locations` applies to every query. Filters (`timeRange`, `jobType`, `experienceLevel`, `remote`) narrow the search.
- **URL-based**: Pass LinkedIn job search URLs in `startUrls`. When `startUrls` is provided, `searchQueries` is ignored.

Record types and limits:

- **Job records** are always collected: `jobId`, title, description, dates, employment type, company name and URL, location fields, coordinates, industry, skills, experience, education, and the job URL.
- **Company page fields** are conditional: they attach onto the same job record only when `extractCompanyDetails` is enabled. They are not a separate dataset item.
- **Result cap**: `maxItems` limits the number of jobs collected (`0` means unlimited, the default; Console prefill is `10`).

Known exclusions: content LinkedIn only shows behind login is not collected; each run captures listing state at run time (no historical snapshots); company page fields are omitted when `extractCompanyDetails` is off or the company page is unavailable.

### Data dictionary

Field names below match dataset record JSON properties exactly. `jobId` is the recommended deduplication key; `url` is a reasonable secondary key. Dates are ISO 8601 timestamps as returned on the listing (for example `2026-05-08T13:38:46.000Z`). `country` is a country code. `latitude` and `longitude` are decimal degrees when present.

| Field | Type | Nullable | Description | Example |
|---|---|---:|---|---|
| `jobId` | string | No | LinkedIn job identifier; best stable deduplication key | `4412111373` |
| `title` | string | No | Job title | `Python Developer` |
| `description` | string | Yes | Full job description text | See example record |
| `datePosted` | string | Yes | Posting timestamp (ISO 8601) | `2026-05-08T13:38:46.000Z` |
| `validThrough` | string | Yes | Listing end timestamp (ISO 8601) | `2026-11-04T13:38:46.000Z` |
| `employmentType` | string | Yes | Employment type as shown on the listing | `FULL_TIME` |
| `companyName` | string | Yes | Hiring company name | `DATAGROUP` |
| `companyUrl` | string | Yes | LinkedIn company page URL | `https://de.linkedin.com/company/datagroup-se` |
| `companyLogo` | string | Yes | Company logo image URL | See example record |
| `country` | string | Yes | Country code | `DE` |
| `city` | string | Yes | City as shown on the listing | `Krakau` |
| `latitude` | number | Yes | Location latitude in decimal degrees | `51.36391` |
| `longitude` | number | Yes | Location longitude in decimal degrees | `11.857489` |
| `industry` | string | Yes | Industry text as shown on the listing | `IT-Dienstleistungen und IT-Beratung` |
| `skills` | string | Yes | Skills text; empty when the listing does not provide it | `""` |
| `monthsOfExperience` | string | Yes | Experience requirement as returned on the listing; empty when not provided | `""` |
| `educationLevel` | string | Yes | Education requirement as shown on the listing | `bachelor degree` |
| `url` | string | No | Job posting URL; secondary deduplication key | `https://de.linkedin.com/jobs/view/python-developer-at-datagroup-4412111373` |
| `location` | string | Yes | Full location string (city, region, country) | `Krakau, Saxony-Anhalt, Germany` |

When `extractCompanyDetails` is true, company page fields attach onto this same job record. They are omitted when the toggle is off.

### Example dataset record

Real record produced with the test input below (`searchQueries`: `Python Developer`, `locations`: `Germany`, `maxItems`: `10`). The `description` text is shortened for readability; the live record contains the full listing copy.

```json
{
  "jobId": "4412111373",
  "title": "Python Developer",
  "description": "Job Description DATAGROUP Polska. Design and implement robust, scalable, and efficient JAVA-based frontend as well as Python components for the ETL process.",
  "datePosted": "2026-05-08T13:38:46.000Z",
  "validThrough": "2026-11-04T13:38:46.000Z",
  "employmentType": "FULL_TIME",
  "companyName": "DATAGROUP",
  "companyUrl": "https://de.linkedin.com/company/datagroup-se",
  "companyLogo": "https://media.licdn.com/dms/image/v2/C4D0BAQFCZT1emBoUsw/company-logo_200_200/company-logo_200_200/0/1630477221838/datagroup_ag_logo?e=2147483647&v=beta&t=kHRk6yObQhtWun4zVyZKfRI6iNwfH5xZ2aTfFnDTOjQ",
  "country": "DE",
  "city": "Krakau",
  "latitude": 51.36391,
  "longitude": 11.857489,
  "industry": "IT-Dienstleistungen und IT-Beratung",
  "skills": "",
  "monthsOfExperience": "",
  "educationLevel": "bachelor degree",
  "url": "https://de.linkedin.com/jobs/view/python-developer-at-datagroup-4412111373",
  "location": "Krakau, Saxony-Anhalt, Germany"
}
```

The record above was produced with this input:

```json
{
  "searchQueries": ["Python Developer"],
  "locations": ["Germany"],
  "timeRange": "any",
  "maxItems": 10,
  "extractCompanyDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"]
  }
}
```

### Query and input reference

| Input | Type | Required | Default | Accepted values | Description |
|---|---|---|---|---|---|
| `searchQueries` | array | No\* | `[]` | Free-text keywords (prefill `Python Developer`) | Job titles or keywords. Each value is a separate search. Ignored when `startUrls` is provided |
| `startUrls` | array (requestListSources) | No\* | `[]` | LinkedIn job search URLs | URL-based entry point. Overrides `searchQueries` when provided |
| `locations` | array | No | `[]` | Free-text locations (e.g. `Germany`, `London`) | Location filter applied to every search query |
| `timeRange` | string | No | `any` | `any`, `past24Hours`, `pastWeek`, `pastMonth` | Filter jobs by posting date |
| `jobType` | array | No | `[]` | `fullTime`, `partTime`, `contract`, `temporary`, `internship`, `volunteer` | Employment-type filter; empty means all types |
| `experienceLevel` | array | No | `[]` | `internship`, `entryLevel`, `associate`, `midSenior`, `director`, `executive` | Experience-level filter; empty means all levels |
| `remote` | array | No | `[]` | `onSite`, `remote`, `hybrid` | Work-arrangement filter; empty means all types |
| `maxItems` | integer | No | `0` | `0` or any positive integer | Maximum jobs to collect; `0` = unlimited (Console prefill `10`) |
| `extractCompanyDetails` | boolean | No | `false` | `true` / `false` | Attach company page fields onto each job record |
| `maxConcurrency` | integer | No | `5` | `1`–`20` | Maximum parallel detail-page fetches |
| `proxyConfiguration` | object | No | Apify proxy, `RESIDENTIAL` group | Apify proxy groups or custom proxies | Residential proxies are recommended |

\* Provide `searchQueries` or `startUrls` — at least one source is required to collect records.

Minimal request:

```json
{ "searchQueries": ["Python Developer"], "locations": ["Germany"] }
```

Advanced request (filters plus company page fields):

```json
{
  "searchQueries": ["Python Developer"],
  "locations": ["Germany"],
  "timeRange": "pastWeek",
  "jobType": ["fullTime"],
  "experienceLevel": ["midSenior"],
  "remote": ["remote", "hybrid"],
  "maxItems": 500,
  "extractCompanyDetails": true,
  "maxConcurrency": 5,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

### Retrieve the data through the API

The Actor runs on the Apify platform, so there is no server to host and no crawling infrastructure to maintain.

1. Start the Actor with a JSON input (console or API).
2. Wait for the run to finish, or use a synchronous endpoint if you want the response inline.
3. Retrieve items from the run's default dataset.
4. Paginate or export the dataset.

Python example:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR-APIFY-TOKEN")

run_input = {
    "searchQueries": ["Python Developer"],
    "locations": ["Germany"],
    "maxItems": 10,
}

run = client.actor("datascrapers/linkedin-job-scraper").call(run_input=run_input)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["jobId"], item["title"], item["companyName"])
```

Apify generates ready-to-run Python, JavaScript, and cURL examples on the Actor's [API tab](https://apify.com/datascrapers/linkedin-job-scraper/api). Do not put a real API token in shared code or URLs.

### Data quality and record handling

- **Conditional fields**: company page fields are present only when `extractCompanyDetails` is enabled. Listings alone return the job fields in the data dictionary.
- **Source changes**: LinkedIn page structure and values can change; fields the listing does not provide are returned as empty values rather than fabricated.
- **Deduplication**: within a run, repeated `jobId` values are skipped. Across runs, the Actor does not deduplicate. Use `jobId` as the stable key, or `url` as a secondary key, and filter repeated runs against previously stored identifiers.
- **Empty source values**: `skills` and `monthsOfExperience` are often empty strings when the listing does not publish them. `latitude` and `longitude` are omitted or empty when coordinates are not present.
- **Normalization**: dates are stored as ISO 8601 strings; `country` is a country code; `employmentType` uses the listing value (for example `FULL_TIME`). Values are read from the listing and mapped to the field types in the data dictionary.

### Export and pipeline examples

| Destination | Recommended method | Typical use |
|---|---|---|
| PostgreSQL / Supabase | Dataset API poll or webhook consumer | Store job rows for labor-market or recruiting analysis |
| Google Sheets | Apify Google Sheets integration | Share shortlists with recruiting or research teams |
| ATS / CRM | Webhook on run completion | Push new jobs into sourcing pipelines |
| S3 / cloud storage | Scheduled export via Apify scheduler + integration | Archival of hiring-market snapshots |

### Pricing and cost examples

The Actor uses pay-per-event pricing. Live charges are:

| Event | Trigger | Rate |
|---|---|---|
| Actor start | Once per run | $0.00005 |
| Job result (`apify-default-dataset-item`) | Every job record written to the default dataset | $0.001 ($1 per 1,000 jobs) |

`extractCompanyDetails` attaches company page fields onto the same job record. It is not a separate billed event.

Example costs:

| Records | Configuration | Estimated base cost |
|---:|---|---:|
| 1,000 | Jobs only | $1.00005 |
| 10,000 | Jobs only | $10.00005 |
| 1,000 | Jobs + company page fields | $1.00005 |
| 10,000 | Jobs + company page fields | $10.00005 |

Compute units and proxy traffic consumed by the run are billed by your Apify plan. Estimates depend on the verified pricing model and the options selected for the run.

### Limitations and responsible data use

- The Actor collects publicly accessible data from LinkedIn job pages only.
- Field availability depends on what LinkedIn renders at run time; some values can be empty or missing, and site changes can alter fields.
- Company page fields attach onto job records; they are not standalone dataset items.
- The Actor does not provide historical snapshots unless you store them yourself.
- Residential proxies are recommended; coverage may degrade without them.
- You are responsible for compliance with LinkedIn's terms of service, applicable privacy law, and any contractual obligations before using the data.

### Dataset questions

#### What does one dataset item represent?

One LinkedIn job. Company page fields attach onto that same row when `extractCompanyDetails` is enabled. Enabling company details does not create extra dataset items.

#### Which field should I use as a unique identifier?

`jobId` is the LinkedIn job identifier and is the recommended deduplication key. `url` is a reasonable secondary key.

#### Are fields nullable or conditional?

Yes. Company page fields exist only when `extractCompanyDetails` is enabled. Within job records, fields the listing does not publish (commonly `skills` and `monthsOfExperience`) are returned as empty values.

#### Do startUrls override searchQueries?

Yes. When `startUrls` contains LinkedIn job search URLs, those URLs are used and `searchQueries` is ignored. Use `searchQueries` plus `locations` and filters when you want keyword search.

#### Can I retrieve the records as CSV or JSON?

Yes. The dataset can be exported as JSON, CSV, Excel, XML, or HTML from the Apify Console, and queried through the Dataset API.

#### Does the Actor return historical data?

No. Each run captures the state of the listings at run time. To track hiring changes over time, schedule repeated runs and store the outputs yourself.

#### What counts as a billable result?

Two charges apply: $0.00005 once per Actor start, and $0.001 for every job record written to the default dataset ($1 per 1,000 jobs). Company page fields do not add a billed event.

### Related datasets from Data Scrapers

- **[Dice.com Job Scraper](https://apify.com/datascrapers/dice-scraper)** — Technology job listings that can be compared with LinkedIn jobs by title, company, and skills.
- **[Naukri Job Scraper](https://apify.com/datascrapers/naukri-scraper)** — India job listings that can be joined with LinkedIn rows by title and company for cross-board coverage.
- **[Glassdoor Jobs Scraper](https://apify.com/datascrapers/glassdoor-jobs-scraper)** — Job listings with employer ratings that can be joined to LinkedIn jobs by company name and title.
- **[ZipRecruiter Job Scraper](https://apify.com/datascrapers/ziprecruiter-scraper)** — US job listings for labor-market comparison against LinkedIn roles.
- **[LinkedIn Company Scraper](https://apify.com/datascrapers/linkedin-company-scraper)** — Company profiles that can be joined with these job records by company name or `companyUrl`.
- **[LinkedIn Profile Scraper](https://apify.com/datascrapers/linkedin-profile-scraper)** — Public profile records for hiring-team or candidate research alongside job listings.

### Data Scrapers support

Need an additional field, record type, or export workflow? Contact Data Scrapers at stardustspotlight@gmail.com. Include a sample source URL, required fields, expected record volume, and preferred delivery format.

# Actor input Schema

## `searchQueries` (type: `array`):

Keywords or job titles to search for (e.g. 'Python Developer', 'Data Scientist'). Each query produces a separate search.

## `startUrls` (type: `array`):

Direct LinkedIn job search URLs. Overrides searchQueries when provided.

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

Location names to filter jobs (e.g. 'Germany', 'United States', 'London'). Applied to all search queries.

## `timeRange` (type: `string`):

Filter jobs by posting date.

## `jobType` (type: `array`):

Filter by employment type. Leave empty for all types.

## `experienceLevel` (type: `array`):

Filter by required experience level. Leave empty for all levels.

## `remote` (type: `array`):

Filter by work arrangement. Leave empty for all types.

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

Maximum number of job postings to scrape. 0 = unlimited.

## `extractCompanyDetails` (type: `boolean`):

When enabled, also scrapes each company's LinkedIn page for full company details (description, followers, specialties, etc.) and appends them to the output.

## `maxConcurrency` (type: `integer`):

Maximum number of parallel detail page fetches. Higher values increase speed but may trigger rate limiting. Recommended: 3-10.

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

Proxy settings for anti-bot protection.

## Actor input object example

```json
{
  "searchQueries": [
    "Python Developer"
  ],
  "startUrls": [],
  "locations": [],
  "timeRange": "any",
  "jobType": [],
  "experienceLevel": [],
  "remote": [],
  "maxItems": 10,
  "extractCompanyDetails": false,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `dataset` (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 = {
    "searchQueries": [
        "Python Developer"
    ],
    "startUrls": [],
    "locations": [],
    "maxItems": 10,
    "extractCompanyDetails": false,
    "maxConcurrency": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("datascrapers/linkedin-job-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 = {
    "searchQueries": ["Python Developer"],
    "startUrls": [],
    "locations": [],
    "maxItems": 10,
    "extractCompanyDetails": False,
    "maxConcurrency": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("datascrapers/linkedin-job-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "searchQueries": [
    "Python Developer"
  ],
  "startUrls": [],
  "locations": [],
  "maxItems": 10,
  "extractCompanyDetails": false,
  "maxConcurrency": 5
}' |
apify call datascrapers/linkedin-job-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datascrapers/linkedin-job-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/1UqwHluTBCEfxhsnE/builds/VeN5Qnl6NeZfcMHCe/openapi.json
