# LinkedIn Jobs Scraper (No Login) (`genius_coder/linkedin-jobs-scraper`) Actor

Scrape public LinkedIn job listings by keyword, location or search URL. No cookies, no account. Exports title, company, salary, description, seniority, applicants and more.

- **URL**: https://apify.com/genius\_coder/linkedin-jobs-scraper.md
- **Developed by:** [Genius\_Coder Sam](https://apify.com/genius_coder) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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 (No Login)

Scrape **public** LinkedIn job listings by keyword, location or search URL, and export clean structured data (JSON, CSV, Excel, API). **No LinkedIn account, no cookies, no login** — the Actor reads the same public job pages any logged-out visitor can see.

### What you get

For every job:

| Field | Example |
|---|---|
| `id`, `url` | `4012345678`, `https://www.linkedin.com/jobs/view/…` |
| `title`, `companyName`, `companyUrl`, `companyLogo` | `Senior Software Engineer`, `Acme Corp` |
| `location`, `postedAt`, `postedAtText` | `Bangkok, Thailand`, `2026-09-18`, `3 days ago` |
| `salary` | `฿120,000/mo - ฿180,000/mo` (when the employer shows it) |
| `employmentType`, `seniorityLevel`, `jobFunction`, `industries` | `Full-time`, `Mid-Senior level` … |
| `applicantsCount`, `applicantsText` | `200`, `Over 200 applicants` |
| `applyUrl` | external apply link (when the job applies off-LinkedIn) |
| `descriptionText`, `descriptionHtml` | full job description |
| `search`, `scrapedAt` | which search produced it, ISO timestamp |

### How to use

1. Either paste one or more **search URLs** copied from `linkedin.com/jobs/search` (set your filters on LinkedIn first), **or** enter **keywords** and **locations** (one search per pair).
2. Optionally set date posted, experience level, job type, workplace type.
3. Set **Maximum jobs** — you pay per job returned, so this is your budget cap.
4. Run. Download results from the **Output** tab or use the API.

#### Example input

```json
{
  "keywords": ["data engineer"],
  "locations": ["Bangkok, Thailand", "Singapore"],
  "datePosted": "week",
  "workplaceType": ["remote", "hybrid"],
  "maxItems": 500,
  "scrapeJobDetails": true
}
```

#### Example output (trimmed)

```json
{
  "id": "4012345678",
  "url": "https://www.linkedin.com/jobs/view/senior-software-engineer-at-acme-4012345678",
  "title": "Senior Software Engineer",
  "companyName": "Acme Corp",
  "location": "Bangkok, Bangkok City, Thailand",
  "postedAt": "2026-09-18",
  "employmentType": "Full-time",
  "seniorityLevel": "Mid-Senior level",
  "applicantsCount": 200,
  "descriptionText": "About the role …"
}
```

### Options worth knowing

- **Scrape full job details** — on (default) opens every job page for the description and metadata; off gives a faster, cheaper listing-only run.
- **Break the 1,000-result limit** — LinkedIn shows at most ~1,000 results per search. When a search hits that limit, the Actor automatically splits it by job type, experience level and workplace type to reach more jobs, and de-duplicates across the splits. In practice this has diminishing returns for broad searches: LinkedIn's job-type/experience/workplace facets tend to overlap heavily with the unfiltered top 1,000, so a split can spend a lot of extra requests (and proxy budget) re-fetching jobs you already have before it turns up genuinely new ones. It's most worth enabling for narrower searches, or when you specifically need more than ~1,000 results and are prepared to pay for the extra paging.
- **Proxy** — residential proxies are strongly recommended. LinkedIn blocks datacenter IPs.
- **Max concurrency** — lower it if you see blocked requests.

### Limits and honest notes

- Only publicly visible data is collected. Anything that requires logging in (e.g. some recruiter details, "insights") is **not** available.
- `applyUrl` is almost always `null`. LinkedIn now hides the real apply target (internal or external) behind a "sign in to apply" modal for logged-out visitors, so this field can only be populated on the rare posting that still leaks it in guest markup.
- `salary` reflects the employer-provided pay range and is only present when LinkedIn shows one; most postings don't have it.
- LinkedIn changes its pages and blocking rules often. If a run returns fewer jobs than expected, retry with lower concurrency; report persistent problems in the **Issues** tab.
- Results depend on what LinkedIn shows logged-out visitors for your query and location, and may differ from what you see when signed in.

### Is it legal?

The Actor accesses only public pages without logging in. Laws and platform terms differ by country and by how you use the data, and LinkedIn's User Agreement restricts automated access. You are responsible for using the data lawfully — in particular, do not use it to profile private individuals, and comply with GDPR/CCPA if you store any personal data (such as recruiter names). This is not legal advice.

### Local development

```bash
npm install
npm test            # parser + query unit tests (offline)
npm run test:e2e    # full actor run against a local mock of LinkedIn's endpoints
apify run           # run locally with storage/key_value_stores/default/INPUT.json
apify push          # deploy to your Apify account
```

Selectors and endpoint URLs live in `src/linkedin.js` only — that is the file to update when LinkedIn changes its markup.

# Actor input Schema

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

Paste URLs from linkedin.com/jobs/search?... (apply your filters on LinkedIn first, then copy the address bar). Filters set below only fill gaps the URL leaves open.

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

One search per keyword (combined with every location). Example: "data engineer".

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

City, region or country names, e.g. "Bangkok, Thailand" or "United States". One search per keyword x location pair.

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

Only jobs posted within this window.

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

Leave empty for all levels.

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

Leave empty for all types.

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

Leave empty for all.

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

Order search results by relevance or by posting date.

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

Total jobs to return across all searches. You are charged per job returned, so this caps your spend.

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

Open every job page for the full description, seniority, employment type, industries, applicants and salary. Turn off for a faster listing-only run (title, company, location, date, link).

## `splitToBypassLimit` (type: `boolean`):

LinkedIn shows at most ~1,000 results per search. When enabled, a search that hits the limit is automatically split by job type / experience level / workplace type to reach more jobs.

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

Parallel requests (1-20). Lower is gentler and less likely to be blocked.

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

Residential proxies are strongly recommended; LinkedIn blocks datacenter IPs.

## Actor input object example

```json
{
  "keywords": [
    "software engineer"
  ],
  "locations": [
    "United States"
  ],
  "datePosted": "any",
  "sortBy": "relevance",
  "maxItems": 100,
  "scrapeJobDetails": true,
  "splitToBypassLimit": true,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

Job postings matching your search, one item per job. See the dataset schema for the field list.

# 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"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("genius_coder/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": ["software engineer"],
    "locations": ["United States"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

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

```

## MCP server setup

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