# ⚡️fast-linkedin-jobs-scraper (`unknownbrain/fast-linkedin-jobs-scraper`) Actor

⚡ BLAZING FAST — hundreds of LinkedIn jobs in seconds, not minutes. Pure HTTP means ZERO browser overhead and no login. Titles, companies, logos, locations, remote/hybrid, dates, links. Filter by keyword, location, date posted, job type, experience. Optional salary.

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

## Pricing

from $0.50 / 1,000 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/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 — Apify Actor

🚀 **Fast, HTTP-only LinkedIn job scraper** — no browser, no login, no cookies required.

This Apify actor scrapes job listings from LinkedIn's public guest-accessible endpoints using [Crawlee's CheerioCrawler](https://crawlee.dev/), making it **significantly faster and cheaper** than browser-based alternatives.

### Features

- ⚡ **HTTP-only scraping** — No Playwright/Puppeteer overhead. Pure HTTP + Cheerio parsing.
- 🔓 **No login required** — Uses LinkedIn's guest job search endpoints.
- 🔍 **Rich filtering** — Keywords, location, date posted, job type, experience level, remote/hybrid/on-site.
- 📄 **Optional detail scraping** — Get full job descriptions, seniority level, industry, and more.
- 🔄 **Auto-pagination** — Automatically paginates through search results.
- 🛡️ **Anti-blocking** — Session pool, proxy rotation, rate limiting, and retries built-in.
- 📊 **Structured output** — Clean JSON dataset ready for further processing.

### Input Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `searchQueries` | `string[]` | `["Software Engineer"]` | Job search keywords |
| `location` | `string` | `"United States"` | Location filter |
| `maxItems` | `integer` | `100` | Maximum jobs to scrape (1–1000) |
| `scrapeJobDetails` | `boolean` | `false` | Scrape full job descriptions (slower) |
| `datePosted` | `enum` | `"any"` | `any`, `past24hours`, `pastWeek`, `pastMonth` |
| `jobType` | `enum` | `"any"` | `any`, `fullTime`, `partTime`, `contract`, `temporary`, `internship` |
| `experienceLevel` | `enum` | `"any"` | `any`, `internship`, `entryLevel`, `associate`, `midSenior`, `director`, `executive` |
| `remoteFilter` | `enum` | `"any"` | `any`, `onSite`, `remote`, `hybrid` |
| `includeSalary` | `boolean` | `false` | Fetch pay for each job. **Much slower** — one extra request per job. All jobs are still returned; salary is `null` when not disclosed. |
| `maxConcurrency` | `integer` | `5` | Concurrent requests (1–20) |
| `proxyConfiguration` | `object` | — | Apify proxy settings |
| `resumeFromPreviousRun` | `boolean` | `false` | Skip jobs/companies already scraped in a prior run (persisted in the key-value store) |
| `outputFields` | `string[]` | `[]` (all fields) | Restrict pushed records to these fields, plus id fields |
| `webhookUrl` | `string` | — | URL to POST JSON run events to (`HIGH_ERROR_RATE`, `COMPLETED`) |
| `notifyOnCompletion` | `boolean` | `false` | Send a webhook event when the run finishes |
| `errorRateThreshold` | `number` | `0.3` | Blocked/failed request ratio that triggers automatic concurrency throttling and a webhook alert |

### Example Input

```json
{
    "searchQueries": ["Data Scientist", "Machine Learning Engineer"],
    "location": "San Francisco",
    "maxItems": 50,
    "scrapeJobDetails": true,
    "datePosted": "pastWeek",
    "jobType": "fullTime",
    "remoteFilter": "remote",
    "maxConcurrency": 3
}
```

### Output

Each job listing produces a JSON object like:

```json
{
    "jobId": "3912345678",
    "title": "Senior Data Scientist",
    "company": "Acme Corp",
    "location": "San Francisco, CA (Remote)",
    "salary": "$150,000 - $200,000",
    "postedDate": "2024-01-15",
    "jobUrl": "https://www.linkedin.com/jobs/view/3912345678",
    "scrapedAt": "2024-01-16T10:30:00.000Z",
    "description": "We are looking for a Senior Data Scientist...",
    "seniorityLevel": "Mid-Senior level",
    "employmentType": "Full-time",
    "jobFunction": "Engineering and Information Technology",
    "industries": "Technology, Information and Internet",
    "applicants": "Over 200 applicants",
    "companyUrl": "https://www.linkedin.com/company/acme-corp"
}
```

> **Note:** Fields like `description`, `seniorityLevel`, etc. are only available when `scrapeJobDetails` is enabled.

### Resilience & Anti-Blocking

- **429-aware backoff** — on a rate-limit or block response, the actor retires the session, waits with exponential backoff (2s → 4s → 8s… capped at 60s, with jitter), and retries with a fresh session/proxy.
- **Challenge/checkpoint detection** — LinkedIn sometimes returns a login-wall or bot-checkpoint page with a `200` status. The actor scans response bodies for these patterns and treats them as failures so they get retried like any other block.
- **Adaptive concurrency** — if the rolling blocked-request rate exceeds `errorRateThreshold` (default 30%), the actor automatically halves its concurrency ceiling and (optionally) fires a webhook alert.
- **Resumable runs** — with `resumeFromPreviousRun: true`, the actor loads the job/company IDs it saw last time from the key-value store and skips them, so a scheduled/recurring run only scrapes what's new.

### Structured Output Extras

In addition to the raw `salary` and `location` strings, each job record includes parsed breakdowns:

```json
{
    "salary": "$150,000 - $200,000",
    "salaryParsed": { "min": 150000, "max": 200000, "currency": "USD", "period": "yearly", "raw": "$150,000 - $200,000" },
    "location": "San Francisco, CA",
    "locationParsed": { "city": "San Francisco", "state": "CA", "country": null, "raw": "San Francisco, CA" }
}
```

Use `outputFields` in the input if you'd rather receive a trimmed record (e.g. `["title", "company", "salaryParsed"]`) instead of the full object.

### Performance Tips

1. **Keep `scrapeJobDetails` off** for fastest results — listing data is scraped in bulk from search pages.
2. **Use `maxConcurrency: 3-5`** for a good balance of speed and reliability.
3. **Use Apify residential proxies** for best success rates against LinkedIn's anti-bot systems.
4. **Filter aggressively** — Use specific keywords and filters to reduce the number of pages to scrape.

### Running Locally

```bash
## Install dependencies
npm install

## Run with Apify CLI
apify run --input '{"searchQueries": ["Software Engineer"], "maxItems": 10}'

## Or run directly
npm start
```

### Deployment

```bash
## Login to Apify
apify login

## Deploy to Apify platform
apify push
```

### How It Works

1. **Builds search URLs** from your input parameters, targeting LinkedIn's guest job search API endpoint.
2. **Fetches search result pages** using pure HTTP requests (CheerioCrawler) — no browser rendering needed.
3. **Parses job cards** from the HTML response using Cheerio (jQuery-like selectors).
4. **Paginates automatically** by incrementing the `start` parameter (25 jobs per page).
5. **Optionally fetches detail pages** for each job to extract full descriptions and metadata.
6. **Outputs structured data** to the Apify dataset in JSON format.

### Legal Disclaimer

This actor is intended for personal and educational use. Scraping LinkedIn may violate their Terms of Service. Users are responsible for ensuring compliance with applicable laws and LinkedIn's User Agreement. Use at your own risk.

### License

ISC

# Actor input Schema

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

Job titles or keywords to search for. Each query is searched separately and results are de-duplicated across them.

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

Target location, e.g. 'United States', 'London', 'Berlin, Germany'.

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

Optional. Provide LinkedIn job search URLs directly. When set, these replace the queries and filters above.

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

Maximum number of jobs to scrape across all queries. LinkedIn serves at most 1000 results per query.

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

Only return jobs posted within this window.

## `jobType` (type: `string`):

Filter by employment type.

## `experienceLevel` (type: `string`):

Filter by required experience level.

## `remoteFilter` (type: `string`):

Filter by remote, on-site, or hybrid work.

## `includeSalary` (type: `boolean`):

⚠️ SLOWER: leave this off to keep the scraper at full speed. LinkedIn does not put pay on search result cards, so turning this on means opening every job individually — roughly 10x the requests, and a run that normally takes seconds can take minutes. All jobs are still returned either way; with this off, salary is simply null. About half of postings disclose pay, so the rest stay null even with this on.

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

Fetch each job page for the full description, seniority, employment type, skills and apply link. Costs one extra request per job.

## `scrapeCompanyDetails` (type: `boolean`):

Fetch each company's About page for industry, size and website. Costs one extra request per unique company, and adds separate records with type 'COMPANY' to the dataset.

## `outputFields` (type: `array`):

Restrict dataset records to these fields. Leave empty to keep everything. 'jobId' and 'type' are always kept.

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

Residential proxies are strongly recommended for large scrapes. LinkedIn blocks datacenter IPs aggressively.

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

Maximum requests in flight at once. Lower this if you see a high block rate.

## `requestsPerMinuteMultiplier` (type: `integer`):

Request rate cap, calculated as Max Concurrency x this value. Bounds the request rate, which is a separate limit from how many run in parallel.

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

How many times to retry a blocked or failed request before giving up.

## `paginationBatchSize` (type: `integer`):

Search result pages queued per wave. Higher fetches more pages in parallel but over-fetches more on short result sets.

## `requestDelayMinMs` (type: `integer`):

Optional artificial delay before each request. Leave at 0: a delay here holds a concurrency slot open while it waits, so it costs throughput twice over.

## `requestDelayMaxMs` (type: `integer`):

Upper bound of the artificial per-request delay. 0 disables it entirely.

## `resumeFromPreviousRun` (type: `boolean`):

Skip jobs and companies already scraped in earlier runs of this actor, using IDs saved in the key-value store.

## `webhookUrl` (type: `string`):

Optional. Receives a POST with a JSON payload when the error rate spikes, and on completion if enabled below.

## `notifyOnCompletion` (type: `boolean`):

Send a webhook when the run finishes. Requires a Webhook URL.

## Actor input object example

```json
{
  "searchQueries": [
    "Software Engineer"
  ],
  "location": "United States",
  "startUrls": [],
  "maxItems": 100,
  "datePosted": "any",
  "jobType": "any",
  "experienceLevel": "any",
  "remoteFilter": "any",
  "includeSalary": false,
  "scrapeJobDetails": false,
  "scrapeCompanyDetails": false,
  "outputFields": [],
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 10,
  "requestsPerMinuteMultiplier": 30,
  "maxRequestRetries": 5,
  "paginationBatchSize": 8,
  "requestDelayMinMs": 0,
  "requestDelayMaxMs": 0,
  "resumeFromPreviousRun": false,
  "notifyOnCompletion": false
}
```

# Actor output Schema

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

All scraped job listings in the default dataset.

# 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": [
        "Software Engineer"
    ],
    "location": "United States",
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("unknownbrain/fast-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 = {
    "searchQueries": ["Software Engineer"],
    "location": "United States",
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("unknownbrain/fast-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 '{
  "searchQueries": [
    "Software Engineer"
  ],
  "location": "United States",
  "maxItems": 100
}' |
apify call unknownbrain/fast-linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

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