# LinkedIn Jobs Scraper (`briskpebble/linkedin-jobs-scraper`) Actor

Scrape LinkedIn jobs from search URLs or filters. Get title, salary, full description, seniority, applicants, apply link, recruiter and company data (website, size, industry). Split searches by city to get past the 1000-job cap. No login or cookies needed. Export to JSON, CSV or Excel.

- **URL**: https://apify.com/briskpebble/linkedin-jobs-scraper.md
- **Developed by:** [Brisk Pebble](https://apify.com/briskpebble) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 jobs

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## LinkedIn Jobs Scraper

Scrape public LinkedIn job listings, with no login and no cookies. For every job you get the full description, salary, seniority, employment type, applicant count, apply URL, the recruiter who posted it, and a company profile (website, size, industry, headquarters, followers).

Paste LinkedIn job search URLs, or fill in a few filters (keywords, location, date posted, companies). To get past LinkedIn's limit of about 1000 jobs per search, you can split a search across a country's cities.

### What can this LinkedIn Jobs Scraper do?

- **Search by URL or by filters.** Paste any public `linkedin.com/jobs/search` URL, single job URLs (`/jobs/view/...`), or use keywords, location, geoId, distance, date posted, company IDs, Easy Apply and "under 10 applicants".
- **Full job details.** Description (HTML and plain text), seniority level, employment type, job function, industries, applicant count, salary, external apply URL or Easy Apply, and whether the job is closed.
- **Job poster.** Name, headline, photo and profile URL of the recruiter, when LinkedIn shows them.
- **Company enrichment.** Website, employee count, size range, industry, headquarters, postal address, organization type, founding year, specialties, followers, slogan and description. Each company is fetched once per run, however many jobs it has.
- **Works with LinkedIn's AI job search.** LinkedIn's public search now ignores classic filters like job type, experience, workplace type and salary. This Actor rewrites them into keywords (e.g. `f_WT=2` becomes "remote"), so old search URLs still return relevant jobs.
- **Get past the 1000 results cap.** Split a search across the major cities of 30+ countries, or across your own list of locations. Duplicate jobs are removed automatically.
- **Pay only for what you want.** Title and company exclusions are applied before job pages are opened. You can also turn off details for a fast run that returns search card data only.
- **Reliable at scale.** Uses residential proxies, session rotation on blocks (HTTP 429/999, auth walls), automatic retries and persisted state, so migrated runs resume without duplicates. If a job page can't be loaded, the job is still saved with its search card data.

### How to scrape LinkedIn jobs

1. Open [linkedin.com/jobs/search](https://www.linkedin.com/jobs/search) in an **incognito window**. The public search can differ from the logged-in one.
2. Search with the filters you want and copy the URL from the address bar.
3. Paste it into **LinkedIn job search URLs**. Alternatively, leave URLs empty and fill in **Search filters**.
4. Set **Max jobs per search** and click **Start**.
5. Download the results as JSON, CSV, Excel or HTML, or get them through the API.

### Input example

```json
{
    "keywords": "data engineer",
    "location": "United States",
    "datePosted": "pastWeek",
    "workplaceTypes": ["2"],
    "scrapeCompany": true,
    "limitPerSource": 200,
    "titleExcludeKeywords": ["intern"],
    "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

The same search, using a URL:

```json
{
    "urls": ["https://www.linkedin.com/jobs/search/?keywords=data%20engineer&location=United%20States&f_TPR=r604800&f_WT=2"],
    "limitPerSource": 200
}
```

Getting every Python job in Germany, past the 1000 cap:

```json
{
    "keywords": "python",
    "splitByLocation": true,
    "splitCountry": "DE"
}
```

#### Input fields

| Field | Description |
| --- | --- |
| `urls` | LinkedIn job search URLs or job URLs. When set, the search filters are ignored. |
| `keywords`, `location`, `geoId`, `distance` | Search filters used when no URLs are given. |
| `datePosted` | `anyTime`, `pastHour`, `past24Hours`, `pastWeek`, `pastMonth`. |
| `companyIds` | LinkedIn numeric company IDs (`f_C=` in search URLs). |
| `under10Applicants`, `easyApply` | Boolean URL filters supported by the public search. |
| `jobTypes`, `experienceLevels`, `workplaceTypes`, `minSalary`, `sortBy` | Classic filters. Rewritten into keywords when `autoConvertToAiSearch` is on. |
| `autoConvertToAiSearch` | Rewrite unsupported classic filters into keywords (default `true`). |
| `scrapeJobDetails` | Open every job page (default `true`). Turn off for a fast run with search card data only. |
| `scrapeCompany` | Add company details (default `true`). |
| `limitPerSource` | Max jobs per search. Empty means everything LinkedIn returns, up to about 1000. |
| `maxItems` | Max jobs in total across all searches. |
| `splitByLocation`, `splitCountry`, `splitLocations` | Run each search once per city to get past the 1000 cap. |
| `titleExcludeKeywords`, `companyExcludeNames` | Skip unwanted jobs before paying for their details. |
| `proxyConfiguration` | Residential proxy recommended (default). |
| `maxConcurrency`, `maxRequestRetries` | Performance tuning. |

### Output example

```json
{
    "id": "4012345678",
    "trackingId": "TrackAAA==",
    "refId": "RefAAA==",
    "link": "https://www.linkedin.com/jobs/view/senior-python-developer-at-acme-4012345678",
    "title": "Senior Python Developer",
    "companyName": "Acme Corp",
    "companyLinkedinUrl": "https://www.linkedin.com/company/acme",
    "companyLogo": "https://media.licdn.com/dms/image/acme-logo.png",
    "location": "New York, NY",
    "salary": "$140,000.00/yr - $180,000.00/yr",
    "postedAt": "2026-10-18",
    "postedTimeAgo": "1 day ago",
    "benefits": ["Actively Hiring"],
    "applicantsCount": "Over 200 applicants",
    "applyUrl": "https://careers.acme.com/jobs/123",
    "applyType": "EXTERNAL",
    "isClosed": false,
    "descriptionHtml": "<strong>About us</strong><br>…",
    "descriptionText": "About us\n\nAcme builds rockets.\n\n• 5+ years of Python",
    "seniorityLevel": "Mid-Senior level",
    "employmentType": "Full-time",
    "jobFunction": "Engineering and Information Technology",
    "industries": "Software Development",
    "jobPosterName": "Jane Doe",
    "jobPosterTitle": "Technical Recruiter at Acme",
    "jobPosterPhoto": "https://media.licdn.com/dms/image/jane.jpg",
    "jobPosterProfileUrl": "https://www.linkedin.com/in/janedoe",
    "companyDescription": "Acme builds rockets for everyone.",
    "companySlogan": "Building the future",
    "companyWebsite": "https://www.acme.com",
    "companyEmployeesCount": 5230,
    "companySize": "1,001-5,000 employees",
    "companyIndustry": "Software Development",
    "companyHeadquarters": "New York, NY",
    "companyType": "Public Company",
    "companyFounded": "2004",
    "companySpecialties": "Rockets, Anvils",
    "companyFollowers": 120345,
    "companyAddress": { "type": "PostalAddress", "streetAddress": "1 Main St", "addressLocality": "New York", "addressRegion": "NY", "postalCode": "10001", "addressCountry": "US" },
    "inputUrl": "https://www.linkedin.com/jobs/search/?keywords=python",
    "scrapedAt": "2026-10-19T12:00:00.000Z"
}
```

Fields LinkedIn doesn't show for a job are `null`. A `detailError` field appears only when the job page couldn't be loaded and the item holds search card data only.

The dataset has three views: **Overview**, **Job details** and **Companies**.

### How much does it cost?

The Actor uses lightweight HTTP requests (no browser), so 512 MB of memory is enough. Each job costs one request for its page. Each new company costs one more request, and that result is reused for all of the company's jobs. Turning off **Scrape job details** cuts the cost to about one request per 10 jobs.

### Tips

- **Use residential proxies.** LinkedIn rate-limits datacenter IPs heavily.
- **Need more than ~1000 jobs?** Turn on **Split search by city locations**, or split by date (e.g. run `past24Hours` daily).
- **Daily monitoring.** Schedule the Actor with `datePosted: "past24Hours"` and combine it with an integration (Google Sheets, Slack, webhooks).
- **Missing jobs?** The public search shows fewer results than a logged-in session, and some filters behave differently. Check your URL in an incognito window.

### Is it legal to scrape LinkedIn?

This Actor only reads data that LinkedIn shows publicly to logged-out visitors. It doesn't log in, and it doesn't access private profiles. Personal data (such as job poster names) is protected by regulations like GDPR. Only scrape personal data when you have a legitimate reason, and consult a lawyer if you're unsure.

### Development

```bash
npm install
npm test          # unit tests + end-to-end test against a local mock of LinkedIn
npm run build     # compile TypeScript to dist/
apify run         # run locally with the input in storage/key_value_stores/default/INPUT.json
apify push        # deploy to the Apify platform
```

Project layout:

```
.actor/            Actor definition: actor.json, input, dataset and output schemas, Dockerfile
src/main.ts        Entry point: input, proxy, state and crawler setup
src/routes.ts      SEARCH (pagination, de-duplication, limits) and DETAIL (job + company) handlers
src/parsers.ts     Cheerio parsers for search cards, job pages and company pages
src/urls.ts        Search URL parsing/building and AI-search filter conversion
src/sources.ts     Turns the input into searches, including split by location
src/company.ts     Per-run company cache
src/cities.ts      City lists used for split by location
test/              Vitest unit tests, HTML fixtures and an end-to-end test
assets/icon/       Actor icon: SVG source and 1024/512/256 px PNGs (upload icon-1024.png in Console)
```

# Changelog

This Actor's version history is a separate document: https://apify.com/briskpebble/linkedin-jobs-scraper/changelog.md

# Actor input Schema

## `urls` (type: `array`):

Open <a href="https://www.linkedin.com/jobs/search" target="_blank">linkedin.com/jobs/search</a> in an incognito window, search with the filters you want and paste the URL from the address bar. You can add several searches, and also single job URLs (<code>linkedin.com/jobs/view/...</code>).<br><br>When URLs are provided, the search filters below are ignored.

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

Job title, skills or company, for example <code>data engineer python</code>.

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

City, region or country as you would type it on LinkedIn, e.g. <code>United States</code>, <code>London, United Kingdom</code> or <code>Remote</code>.

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

Optional LinkedIn location id for precise targeting (the <code>geoId=</code> value of a LinkedIn search URL, e.g. <code>103644278</code> = United States). Takes precedence over the location text.

## `distance` (type: `integer`):

Search radius around the location. Leave empty for LinkedIn's default.

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

Only return jobs posted within this window.

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

LinkedIn numeric company ids (the <code>f_C=</code> value of a search URL), e.g. <code>1441</code> for Google.

## `under10Applicants` (type: `boolean`):

Only jobs with fewer than 10 applicants.

## `easyApply` (type: `boolean`):

Only jobs that can be applied to directly on LinkedIn.

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

LinkedIn's public AI search no longer supports this as a URL filter; with "Auto convert" on it's added to the keywords instead.

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

Converted to keywords when "Auto convert" is on.

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

Converted to keywords when "Auto convert" is on.

## `minSalary` (type: `string`):

Converted to keywords when "Auto convert" is on.

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

Order of the search results.

## `autoConvertToAiSearch` (type: `boolean`):

LinkedIn's public job search now runs on AI search, which ignores many classic filters (job type, experience, workplace type, salary). When on, those filters are removed from the URL and added to the keywords in plain English, e.g. <code>f_WT=2</code> becomes "remote". Date posted, company, Easy Apply and under 10 applicants stay URL filters.

## `scrapeJobDetails` (type: `boolean`):

Open each job to get the description, seniority, employment type, job function, industries, applicants, apply URL and job poster. Turn off for a fast, cheap run with search card data only (title, company, location, salary, date).

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

Adds company website, size, employee count, industry, headquarters, followers and description. Each company is fetched once per run. Requires "Scrape job details".

## `limitPerSource` (type: `integer`):

Maximum jobs from each search URL (or from the filters). Leave empty to scrape everything LinkedIn returns, up to ~1000 per search.

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

Stop the whole run after this many jobs, across all searches. Leave empty for no limit.

## `splitByLocation` (type: `boolean`):

Runs each search once per city of the chosen country (or of your own list) to get past LinkedIn's ~1000 results cap. Duplicate jobs are removed. This overrides the location of your search.

## `splitCountry` (type: `string`):

Its major cities are searched one by one. Used only when "Split search by city locations" is on.

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

Your own list of locations to split by, e.g. <code>Austin, Texas, United States</code>. Overrides the country.

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

Skip jobs whose title contains any of these words (case-insensitive), e.g. <code>senior</code>, <code>intern</code>.

## `companyExcludeNames` (type: `array`):

Skip jobs from these companies (case-insensitive, partial match), e.g. staffing agencies.

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

LinkedIn rate-limits datacenter IPs heavily. Residential proxy gives the most reliable results.

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

Maximum parallel requests. Higher is faster but increases the chance of being rate-limited.

## `maxRequestRetries` (type: `integer`):

How many times a failed or blocked request is retried with a new proxy session.

## Actor input object example

```json
{
  "urls": [
    "https://www.linkedin.com/jobs/search/?keywords=software%20engineer&location=United%20States&f_TPR=r86400"
  ],
  "datePosted": "anyTime",
  "companyIds": [],
  "under10Applicants": false,
  "easyApply": false,
  "autoConvertToAiSearch": true,
  "scrapeJobDetails": true,
  "scrapeCompany": true,
  "limitPerSource": 100,
  "splitByLocation": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxConcurrency": 10,
  "maxRequestRetries": 6
}
```

# Actor output Schema

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

All scraped jobs with job details, job poster and company information.

# 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 = {
    "urls": [
        "https://www.linkedin.com/jobs/search/?keywords=software%20engineer&location=United%20States&f_TPR=r86400"
    ],
    "limitPerSource": 100,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("briskpebble/linkedin-jobs-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 = {
    "urls": ["https://www.linkedin.com/jobs/search/?keywords=software%20engineer&location=United%20States&f_TPR=r86400"],
    "limitPerSource": 100,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("briskpebble/linkedin-jobs-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 '{
  "urls": [
    "https://www.linkedin.com/jobs/search/?keywords=software%20engineer&location=United%20States&f_TPR=r86400"
  ],
  "limitPerSource": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call briskpebble/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,briskpebble/linkedin-jobs-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/U6J08bCM6MpuZbGH1/builds/XpkSWQJIjdFtT3s88/openapi.json
