# LinkedIn Profile Scraper (`hensen-software-services/linkedin-profile-scraper`) Actor

Scrapes comprehensive LinkedIn profile data (experience, education, skills, email) from public profiles. Supports optional session cookie for authenticated scraping.

- **URL**: https://apify.com/hensen-software-services/linkedin-profile-scraper.md
- **Developed by:** [Dino](https://apify.com/hensen-software-services) (community)
- **Categories:** Lead generation, Jobs, Social media
- **Features:** Limited permissions
- **Stats:** 41 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 profile scrapeds

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?

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 Profile Scraper

Scrapes comprehensive data from public LinkedIn profiles: identity, work experience, education, skills, languages, certifications, and more. Optionally infers and verifies professional email addresses.

Built with [Crawlee for Python](https://crawlee.dev/python/) + [Camoufox](https://camoufox.com/) (stealthy Firefox) for reliable scraping with minimal bot detection.

***

### Features

- Extracts full profile data: name, headline, summary, location, connections, followers, experience, education, skills, languages, certifications, publications, patents, volunteer work, recommendations
- Infers professional email addresses from company domain patterns
- Optional SMTP email verification
- Optional LinkedIn session cookie for authenticated scraping (unlocks more data)
- Optional Apify proxy or local Tor proxy support
- Pipeline mode: consume URLs from a named RequestQueue (for use with a discovery actor)
- Graceful per-profile error handling — one failure never aborts the whole run

***

### Input

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `profileUrls` | `string[]` | — | LinkedIn `/in/` profile URLs to scrape. Required (unless using `inputRequestQueueName`). |
| `maxProfiles` | `integer` | unlimited | Cap the number of profiles processed. |
| `maxConcurrency` | `integer` | `1` | Parallel browser tabs (max 5). Keep at 1–2 to avoid blocks. |
| `requestDelay` | `integer` | `3000` | Milliseconds to wait between requests. Higher = safer. |
| `proxyConfiguration` | `object` | disabled | Apify proxy config. Recommended when running on the platform. |
| `linkedinCookies` | `string` | — | Value of the `li_at` LinkedIn session cookie. Enables authenticated scraping. |
| `verifyEmail` | `boolean` | `false` | Verify inferred emails via SMTP. Slower but more accurate. |
| `inputRequestQueueName` | `string` | — | Name of an Apify RequestQueue to consume URLs from (pipeline mode). Overrides `profileUrls`. |

#### Example input

```json
{
  "profileUrls": [
    "https://www.linkedin.com/in/williamhgates",
    "https://www.linkedin.com/in/jeffweiner08"
  ],
  "maxConcurrency": 1,
  "requestDelay": 4000,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

#### Using a session cookie

Providing a LinkedIn session cookie (`li_at`) allows the scraper to access authenticated profile data and greatly reduces the risk of bot blocks.

To find your cookie:

1. Log in to LinkedIn in your browser
2. Open DevTools → Application → Cookies → `linkedin.com`
3. Copy the value of `li_at`
4. Paste it into the `linkedinCookies` input field

> The cookie is marked `isSecret` and will not appear in logs.

***

### Output

Each profile produces one record in the default dataset. On success:

```json
{
  "linkedinUrl": "https://www.linkedin.com/in/williamhgates",
  "linkedinPublicUrl": "https://linkedin.com/in/williamhgates",
  "publicIdentifier": "williamhgates",
  "urn": "ACoAAABxN1IBk82L_jCY2opFKNZpSCCcsr6X1MU",
  "firstName": "Bill",
  "lastName": "Gates",
  "fullName": "Bill Gates",
  "headline": "Co-chair, Bill & Melinda Gates Foundation",
  "summary": "...",
  "profilePictureUrl": "https://...",
  "backgroundPictureUrl": "https://...",
  "location": "Seattle, Washington, United States",
  "connections": 500,
  "followers": 35000000,
  "email": "bill.gates@gatesfoundation.org",
  "emailVerificationStatus": "valid",
  "jobTitle": "Co-chair",
  "jobStartedOn": "2021-01",
  "jobLocation": "Seattle, Washington, United States",
  "jobStillWorking": true,
  "currentJobDuration": "4 yrs",
  "currentJobDurationInYrs": 4.0,
  "companyName": "Bill & Melinda Gates Foundation",
  "companyIndustry": "Non-profit Organizations",
  "companyWebsite": "https://www.gatesfoundation.org",
  "companyLinkedin": "https://www.linkedin.com/company/gatesfoundation",
  "companySize": "1001-5000",
  "experiences": [
    {
      "companyName": "Bill & Melinda Gates Foundation",
      "title": "Co-chair",
      "jobDescription": "...",
      "jobStartedOn": "2021-01",
      "jobEndedOn": null,
      "jobStillWorking": true,
      "jobLocation": "Seattle, Washington, United States",
      "companyWebsite": "https://www.gatesfoundation.org",
      "companyIndustry": "Non-profit Organizations",
      "companySize": "1001-5000"
    }
  ],
  "educations": [
    {
      "schoolName": "Harvard University",
      "degree": "...",
      "fieldOfStudy": "...",
      "startYear": 1973,
      "endYear": 1975
    }
  ],
  "skills": [{ "title": "Strategic Planning" }],
  "languages": [{ "name": "English", "proficiency": "NATIVE_OR_BILINGUAL" }],
  "certifications": [],
  "publications": [],
  "patents": [],
  "volunteerWork": [],
  "recommendations": [],
  "succeeded": true
}
```

On failure, a minimal error record is pushed and the run continues:

```json
{
  "inputUrl": "https://www.linkedin.com/in/nonexistent-profile",
  "succeeded": false,
  "error": "Profile not found or access denied."
}
```

#### Email verification status values

| Value | Meaning |
|-------|---------|
| `valid` | SMTP check passed — mailbox exists |
| `invalid` | SMTP check failed — address rejected |
| `catch_all` | Domain accepts all addresses — unverifiable |
| `unverified` | SMTP check skipped (`verifyEmail` is false) |
| `no_email` | No email was inferred |

***

### Pipeline mode

This actor can consume profile URLs from a named Apify RequestQueue instead of the `profileUrls` input. This is useful when chaining it with a discovery actor that enqueues profiles for processing.

```json
{
  "inputRequestQueueName": "linkedin-discovery-output"
}
```

Set `inputRequestQueueName` to the same value as the discovery actor's `outputRequestQueueName`. The scraper will drain the queue and process all URLs found.

***

### Anti-detection notes

- **Keep `maxConcurrency` at 1** (default). LinkedIn blocks aggressive parallel scraping quickly.
- **Increase `requestDelay`** if you observe bot detection. 5000–10000 ms is safer for large batches.
- **Use a proxy** (`proxyConfiguration`) when running on the Apify platform — residential proxies work best.
- **Provide a session cookie** (`linkedinCookies`) to reduce blocks and access more profile data.
- The scraper uses [Camoufox](https://camoufox.com/) — a hardened Firefox with realistic browser fingerprints — which passes most bot-detection heuristics.

***

### Limitations

- Scrapes only **publicly visible** data. Fields hidden behind LinkedIn's login wall may not be available without a session cookie.
- LinkedIn's page structure changes frequently; some fields may occasionally fail to parse.
- Email inference is pattern-based (e.g. `firstname.lastname@company.com`) and not guaranteed to be correct. Use `verifyEmail: true` for higher accuracy.

# Actor input Schema

## `profileUrls` (type: `array`):

List of LinkedIn profile URLs to scrape (must be /in/ URLs).

## `maxProfiles` (type: `integer`):

Maximum number of profiles to scrape. Leave empty for no limit.

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

Number of profiles to process in parallel. Keep low (1–3) to avoid bot detection.

## `requestDelay` (type: `integer`):

Milliseconds to wait between profile requests. Higher values reduce block risk.

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

Proxy settings. Recommended when running on Apify platform.

## `linkedinCookies` (type: `string`):

Value of the 'li_at' LinkedIn session cookie. Providing this allows scraping without bot blocks and unlocks authenticated profile data. Find it in browser DevTools → Application → Cookies → linkedin.com.

## `verifyEmail` (type: `boolean`):

Check each generated email pattern against the mail server. Slower but accurate. Skipped for catch-all domains.

## `inputRequestQueueName` (type: `string`):

Name of an Apify RequestQueue to consume profile URLs from (pipeline mode). Overrides profileUrls when set. Use together with the LinkedIn Discovery Actor's outputRequestQueueName.

## Actor input object example

```json
{
  "profileUrls": [
    "https://www.linkedin.com/in/williamhgates"
  ],
  "maxConcurrency": 1,
  "requestDelay": 3000,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "verifyEmail": false
}
```

# Actor output Schema

## `profiles` (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 = {
    "profileUrls": [
        "https://www.linkedin.com/in/williamhgates"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("hensen-software-services/linkedin-profile-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 = { "profileUrls": ["https://www.linkedin.com/in/williamhgates"] }

# Run the Actor and wait for it to finish
run = client.actor("hensen-software-services/linkedin-profile-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 '{
  "profileUrls": [
    "https://www.linkedin.com/in/williamhgates"
  ]
}' |
apify call hensen-software-services/linkedin-profile-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hensen-software-services/linkedin-profile-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/aG8yb9s4btLF241JZ/builds/WoX3bCYL6bjj0sCoE/openapi.json
