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

Scrape LinkedIn job postings without a login or cookie. Get titles, companies, locations, salaries, posting dates, applicant counts, seniority, employment type and full job descriptions. Paste a LinkedIn search URL or give keywords and locations.

- **URL**: https://apify.com/jmlp/linkedin-jobs-scraper.md
- **Developed by:** [Mary Lou](https://apify.com/jmlp) (community)
- **Categories:**
- **Stats:** 1 total users, 0 monthly users, 70.0% runs succeeded, 2 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.15 / 1,000 results

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

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 Jobs Scraper — extract job postings without a login or cookie

Scrape LinkedIn job postings at scale and export them as JSON, CSV or Excel.
Get job titles, companies, locations, salaries, posting dates, applicant
counts, seniority levels, employment types and **full job descriptions** — from
the public LinkedIn job search, with no account and no credentials.

**No login. No `li_at` cookie. No LinkedIn account to get restricted.** This
LinkedIn job scraper reads the same public endpoints LinkedIn serves to
signed-out visitors and to search engines, so there is nothing of yours at
risk.

***

### What you can do with it

- **Build a job board** or aggregate LinkedIn job listings into your own site
- **Track hiring signals** — watch which companies are growing, and in which
  cities and functions
- **Recruitment and talent market research** — salary benchmarking, job title
  taxonomies, demand for specific skills
- **Competitor hiring intelligence** — monitor exactly what your competitors
  are recruiting for and how fast
- **Lead generation** — companies actively hiring are companies actively
  spending
- **Feed a job-matching or CV-matching model** with real, current postings
- **Daily new-jobs alerts** — schedule it with *Most recent* + *Past 24 hours*

***

### How to scrape LinkedIn jobs

**The easy way — paste a search URL.** Open LinkedIn job search in your
browser, click whatever filters you want, and copy the URL from the address
bar into **LinkedIn search URLs**. Every filter in it is honoured: keywords,
location, geoId, date posted, experience level, job type, workplace type,
company, and sort order.

**Or build the search here.** Fill in **Keywords** and **Locations**. Each
keyword is paired with each location, so 3 keywords x 2 locations runs 6
searches in one go.

Leave **Proxy** on (it is on by default) — LinkedIn throttles a single IP
quickly.

***

### Input

| Field | What it does |
| --- | --- |
| **LinkedIn search URLs** | Paste job-search URLs straight from your browser. The recommended input |
| **Keywords** | Job titles or skills, e.g. `python developer`, `product manager` |
| **Locations** | Cities, regions or countries as LinkedIn spells them |
| **Geo ID** | LinkedIn's numeric location id, for when a name is ambiguous |
| **Company IDs** | Restrict results to specific companies |
| **Date posted** | Any time, past month, past week, past 24 hours |
| **Sort by** | Relevance, or most recent |
| **Experience level** | internship, entry, associate, mid-senior, director, executive |
| **Employment type** | full-time, part-time, contract, temporary, volunteer, internship, other |
| **Workplace type** | on-site, remote, hybrid |
| **Distance** | Search radius in miles |
| **Scrape full job descriptions** | Adds the description and criteria. One extra request per job |
| **Max jobs** | Hard cap on results, across every search |

***

### Output

One row per job posting. Export as **JSON, CSV, Excel, XML or RSS**.

```json
{
  "job_id": "4459150828",
  "title": "Python Developer Django SQL - FinTech",
  "company": "Client Server",
  "company_url": "https://uk.linkedin.com/company/client-server",
  "company_logo": "https://media.licdn.com/dms/image/...",
  "location": "London, England, United Kingdom",
  "posted_date": "2026-08-26",
  "posted_text": "4 days ago",
  "salary": null,
  "benefits": null,
  "seniority_level": "Mid-Senior level",
  "employment_type": "Full-time",
  "job_function": "Engineering, Information Technology",
  "industries": "Software Development",
  "applicants": "51 applicants",
  "description": "We are looking for a Python Developer...",
  "description_html": "<p>We are looking for...</p>",
  "job_url": "https://uk.linkedin.com/jobs/view/...-4459150828",
  "easy_apply": false
}
```

| Field | Notes |
| --- | --- |
| `job_id` | LinkedIn's stable id. Dedup on this |
| `posted_date` | From the machine-readable date, **not** the "4 days ago" text — so it sorts and filters properly |
| `salary` | Only present when the advertiser published one. Most do not |
| `applicants` | LinkedIn's public count, e.g. "51 applicants" or "Be among the first 25" |
| `description` / `description_html` | Plain text and original markup. Detail page only |
| `easy_apply` | Whether the posting uses LinkedIn's one-click apply |

***

### Two limits worth knowing before you scale up

**1. Descriptions cost one request each.** Title, company, location, salary and
posting date come from the search results and are nearly free. The full
description, seniority, employment type, function, industries and applicant
count come from each job's own page — one extra request per job. That is also
the first thing LinkedIn rate-limits: measured at a couple of dozen from a
single IP.

If it does get throttled, **the run does not fail**. It logs a warning, stops
fetching descriptions, and finishes with the search cards. Use a proxy, or turn
descriptions off for a fast list.

**2. LinkedIn caps one search at about 1,000 results.** Past that it serves
empty pages rather than an error — a scraper that does not know this reports
"no more results" and quietly loses the rest. This one detects the ceiling and
tells you, in the log and in the run summary.

To get past it, **split the search**: more locations, a narrower date window,
or separate employment types. Four searches of 1,000 beat one search of 4,000,
which does not exist.

***

### Pricing

**$0.15 per 1,000 jobs, plus Apify platform usage.**

| Plan tier | Price per 1,000 jobs |
| --- | --- |
| Free / Bronze | $0.17 |
| Silver, Gold, Platinum, Diamond | $0.15 |

Platform usage is billed by Apify at your own plan rate, separately from the
scraper charge. Measured with descriptions on and a residential proxy:
**roughly $0.24 per 1,000 jobs**, most of it proxy traffic for the one extra
request each description costs. Turn **Scrape full job descriptions** off and
that drops by around 10x.

Set **Max jobs** to cap any run exactly. It counts unique jobs across every
search, so overlapping keywords never bill twice — the same posting found by
two searches is delivered once.

***

### FAQ

**Do I need a LinkedIn account or cookie?**
No. That is the whole point of this scraper. Tools that ask for your `li_at`
cookie are logging in as you, and LinkedIn restricts accounts for it.

**What am I giving up by not logging in?**
The signed-out surface has no recruiter contact details and no "job poster"
profile, and applicant counts are the coarse public ones. Everything else —
description, company, salary, criteria — is here.

**Is scraping LinkedIn jobs legal?**
This reads public job adverts that LinkedIn publishes deliberately and serves
to anyone, with no login and no paywall. Job postings are advertisements meant
to be seen. What you do with the data afterwards is your responsibility.

**Why is the salary field usually empty?**
Because most advertisers do not publish one. When it is on the posting, it is
in the field.

**Why did I get fewer jobs than LinkedIn's own count shows?**
Either the search hit the ~1,000 ceiling (check the run summary), or LinkedIn's
count is inflated — it includes postings it will not serve to a signed-out
visitor.

**Can I scrape jobs from a specific company?**
Yes. Use **Company IDs**, or paste a search URL already filtered to that
company.

**Can I run it on a schedule?**
Yes. Set **Sort by** to *Most recent* and **Date posted** to *Past 24 hours*,
then run it daily to collect only what is new.

**How fast is it?**
Measured on the platform: 40 jobs from 4 searches in 33 seconds, 37 of them
with full descriptions.

***

### Related scrapers

- **Website Contact Scraper** — emails, phones and socials for any list of
  company domains. Pairs well with the companies you find here
- **Google Search Scraper** — organic SERP results with rank tracking
- **TikTok Ad Library Scraper**, **Meta Ads Library Scraper**, **Google Ads
  Transparency Center Scraper** — competitor ad intelligence

# Actor input Schema

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

Paste LinkedIn job-search URLs straight from your address bar after applying filters in the UI. Every filter in the URL is honoured, which is far easier than reproducing them below by hand. This is the recommended input.

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

Job titles or skills, e.g. 'python developer'. Each keyword is combined with each location below, so 3 keywords x 2 locations is 6 searches.

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

Cities, regions or countries as LinkedIn spells them, e.g. 'London', 'Berlin', 'United States'.

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

LinkedIn's numeric location id, taken from a search URL. More precise than a location name when several places share one.

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

Restrict results to these LinkedIn company ids (the f\_C value in a search URL).

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

How recent the posting must be.

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

Relevance is LinkedIn's default; date gives you the newest postings first, which is what you want when running this on a schedule.

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

Any of: internship, entry, associate, mid-senior, director, executive.

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

Any of: full-time, part-time, contract, temporary, volunteer, internship, other.

## `workplaceType` (type: `array`):

Any of: on-site, remote, hybrid.

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

Search radius around the location.

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

Adds the full description, seniority level, employment type, job function, industries and applicant count. This costs ONE EXTRA REQUEST PER JOB and is what LinkedIn throttles first - measured at a couple of dozen from a single IP. Use a proxy when this is on, or turn it off for a fast list of cards.

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

Stop after this many unique jobs across every search. Caps both runtime and cost. Prefilled so a first run finishes quickly.

## `maxPagesPerSearch` (type: `integer`):

Safety cap per search. One page is 10 jobs - LinkedIn's page size is fixed and cannot be raised.

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

How many searches to run in parallel. LinkedIn throttles by IP, so higher is not always faster - 2 is a good balance, and raise it only with a proxy.

## `delayMs` (type: `integer`):

Politeness pause. This is the main lever against being throttled; raise it if the log reports refused searches.

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

Strongly recommended, especially with descriptions on. LinkedIn throttles a single IP quickly and the job-description endpoint hits that limit long before the search does.

## `proxyRotations` (type: `integer`):

If a search is throttled, mint a new proxy session and retry this many times.

## `resume` (type: `boolean`):

Save progress every ~30s so a migrated or restarted run picks up where it stopped.

## `continueFromLastRun` (type: `boolean`):

If your previous run with the same input was interrupted or throttled, fetch only what it missed. Earlier jobs stay in THAT run's dataset.

## `impersonate` (type: `string`):

Which browser TLS fingerprint to present. LinkedIn refuses plain HTTP clients outright, so this must stay a real browser build.

## Actor input object example

```json
{
  "keywords": [
    "python developer"
  ],
  "locations": [
    "London"
  ],
  "datePosted": "any",
  "sortBy": "relevance",
  "scrapeJobDetails": true,
  "maxItems": 50,
  "maxConcurrency": 2,
  "delayMs": 1000,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "proxyRotations": 3,
  "resume": true,
  "continueFromLastRun": false,
  "impersonate": "chrome131"
}
```

# Actor output Schema

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

One record per unique job posting.

## `summary` (type: `string`):

How many jobs and companies were found, which searches hit LinkedIn's result ceiling, and whether anything was throttled.

# 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": [
        "python developer"
    ],
    "locations": [
        "London"
    ],
    "maxItems": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("jmlp/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 = {
    "keywords": ["python developer"],
    "locations": ["London"],
    "maxItems": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("jmlp/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 '{
  "keywords": [
    "python developer"
  ],
  "locations": [
    "London"
  ],
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call jmlp/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jmlp/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/WH3Pk79yWE19AapPz/builds/38LztnNvjpsS06V3n/openapi.json
