# LinkedIn Skills Scraper | No Cookies | from $2.996/1k (`b2bdata/linkedin-skills-scraper`) Actor

LinkedIn skills scraper: give profile URLs, get education and work experience as clean arrays, plus skills when the profile lists them, with name, headline, title and company. No login or cookies. Pay per profile, misses free. Does not search people; use people-search. Free plan: 100 per run.

- **URL**: https://apify.com/b2bdata/linkedin-skills-scraper.md
- **Developed by:** [Open Enrich](https://apify.com/b2bdata) (community)
- **Categories:** Lead generation, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 profile founds

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?

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 Skills Scraper | No Cookies | from $2.996/1k

This LinkedIn skills scraper returns education and work experience for the profiles on your list, plus skills when a profile lists them. Paste profile links and get one row per person: name, headline, current title and company, plus clean `skills`, `education` and `experience` arrays. In our small test (2026-10-13, 7 profiles returned) 3 listed skills, 6 listed education and all 7 listed experience. No LinkedIn account, no cookies, no ban risk. Export JSON or CSV, or call it from the API and MCP.

**No login | Profile URL, short URL or bare slug | Skills (when listed) + education + experience arrays | Up to 1,000 profiles per run | JSON/CSV export**

> **Not a scraper of linkedin.com.** The actor never opens a LinkedIn page. Each row is read from a profile database by the profile address you supply. Not affiliated with LinkedIn.

[See all actors by b2bdata](https://apify.com/b2bdata)

***

### 💳 Pricing and plans

Pay per result, see the Pricing tab. Normal Apify platform usage applies.

| Event | When it is charged | Free plan, per 1,000 | Gold plan, per 1,000 |
|---|---|---|---|
| `profile` | One profile found with at least one of the lists you asked for, delivered as a row | $3.963 | $3.052 |

On the Free plan the $5 monthly credit covers about 1,260 profiles. Bronze and Silver are in between ($3.646 and $3.369 per 1,000); Platinum and Diamond match Gold. Higher Apify plans only lower the price. There is no start fee.

#### Success-only billing

- A profile that is not found is free.
- A profile that exists but has none of the lists you asked for is free (listed in `MISSES`).
- A failed lookup is free.
- An input that is not a LinkedIn profile link is skipped and is free.
- Empty runs and notices are free.

#### The free plan

Free Apify accounts get up to 100 inputs looked up per run, and as many runs as their Apify credit covers. Nothing is stored on our side. When a list is longer, the first 100 are looked up and one free notice row (`reason: "free_plan_limit"`) says how many inputs were not looked up. Paid Apify plans have no per-run limit.

#### Keep a run small while you test

Set `limit` to 1 or 5, or set a spend cap in `maxChargeUsd`. The run stops as soon as the cap is reached.

### ⚖️ This vs. the alternatives

Public Store prices checked on 2026-10-13. Re-check before you decide: prices and field lists change.

| Actor | Event | Price per profile |
|---|---|---|
| This actor (Free plan / Gold plan) | `profile` | $0.003963 / $0.003052 |
| [`b2bdata/linkedin-profile-by-url`](https://apify.com/b2bdata/linkedin-profile-by-url) | `profile` (flat row, no lists) | from $0.003663 |
| [`harvestapi/linkedin-profile-scraper`](https://apify.com/harvestapi/linkedin-profile-scraper) | `profile` (flat, no plan tiers) | $0.004 |
| [`atomus/linkedin-profile-scraper`](https://apify.com/atomus/linkedin-profile-scraper) | `profile-enriched` | $0.006 |

Field count, measured on 2026-10-13 on three public profiles (non-empty top-level fields per row; empty values, zeros and false flags not counted): `harvestapi/linkedin-profile-scraper` returned 32, 33 and 35. This actor with `include: ["all"]` returned 46, 46 and 54. Three profiles is a small test, not a statistic.

### ⚡ Why this LinkedIn skills scraper?

- **Three lists in one row.** `skills`, `education` and `experience` come back as arrays you can filter, not as one blob of text.
- **You pay only when the lists have content.** A row is charged when at least one list you asked for (`include`) has content. Ask for `skills` only and a profile that lists no skills costs nothing. Ask for all three (the default) and a profile with education or experience but no skills is still charged.
- **Counts for filtering.** `skillsCount`, `educationCount` and `experienceCount` sit next to the arrays, so a spreadsheet filter works without opening them.
- **Same input as the flat actor.** Need only the cheap flat row (name, title, company)? Use [`b2bdata/linkedin-profile-by-url`](https://apify.com/b2bdata/linkedin-profile-by-url) instead.

### ➕ What you can add

Beyond the three default lists, `include` takes opt-in sections. They are returned only when you request them and the profile has them; empty ones are left out. They are never charged separately: the price stays one `profile` event, decided only by `skills`, `education` and `experience`.

- `about`: the About text.

- `certifications`, `languages`, `honors` (honors and awards), `projects`, `publications`, `patents`, `interests`, `articles`, `groups`: arrays of entries with the fields the profile has.

- `recommendations`: array of recommendation texts.

- `profileDetails`: `industry`, `followersCount`, `profileImageUrl`, `backgroundImageUrl`. The image URLs expire, so download the images soon after the run if you need them.

- `volunteer` (as `volunteerExperience`), `organizations`, `courses`, `testScores`: arrays of entries.

- `location`: `city`, `state`, `stateFullName`, `countryFullName`, `countryIso3`.

- `links`: `githubUrl`, `twitterUrl`, `facebookUrl`, `websiteUrl` (URLs only; the LinkedIn link is already `profileUrl`).

- `currentJob`: `currentCompanyLinkedinId`, `currentCompanyLogoUrl` (expiring URL), `currentJobStartDate`, `currentJobDescription`, `currentJobLocation`, `currentEmploymentType`, `currentJobFunction`, `currentSeniority`, `jobTitleNormalized`.

- `analytics`: `seniorityLevel`, `isDecisionMaker`, `averageTenureYears`, `certificationsCount`, `languagesCount`, `companiesCount`, `highestDegree` (first education entry), and the booleans `hasWorkEmail`, `hasPersonalEmail`, `hasPhone`. The addresses and numbers themselves are never returned.

- `network`: `relatedProfiles` (up to 10 of `url`, `name`, `headline`, `location`), `similarNames`, `activities`.

- `quality`: `dataQualityPublicExperienceAvailable`, `sectionsIncluded`.

- `all`: every section and group above.

Example: `{ "items": ["https://www.linkedin.com/in/satyanadella"], "include": ["skills", "education", "experience", "about", "certifications", "profileDetails"] }`

Entry shapes for these sections are passed through as found; we have only checked a few of them on a small number of profiles, so treat the field names inside `certifications`, `languages`, `honors`, `publications`, `patents`, `interests` and `groups` as best-effort.

### 🎯 Use cases for an education scraper

- **Recruiting shortlists.** Turn candidate links into a table of schools, degrees and skills.
- **Skills matching.** Check which people on a list list a given skill, with a filter on `skills`.
- **CRM enrichment.** Add education and work history to contacts you already hold.
- **Research.** Study career paths and schooling across a group of public profiles.

### 🔗 Pipeline guide: from a name to a deep profile

```text
[1] linkedin-person-search / linkedin-people-search   -> profile links
[2] linkedin-skills-scraper (this actor)           -> skills, education, experience
[3] email-phone-finder                                 -> work email and phone
```

### 🚀 Quick start

#### 1. One profile, all lists

```json
{ "items": ["https://www.linkedin.com/in/satyanadella"], "limit": 1 }
```

#### 2. A list in mixed formats, skills only

```json
{ "items": ["https://www.linkedin.com/in/satyanadella", "linkedin.com/in/jeffweiner08", "reidhoffman"], "include": ["skills"] }
```

#### 3. A bigger batch with a spend cap and audit view

```json
{ "items": ["…up to 1000 links…"], "limit": 0, "maxChargeUsd": 5, "includeAudit": true }
```

### ⚙️ Inputs

| Key | Type | Default | Notes |
|---|---|---|---|
| `items` | string\[] | required | Profile URL, `linkedin.com/in/slug` or bare slug. Duplicates collapse. At most 1,000 per run; more is rejected and nothing is charged. |
| `limit` | integer | 25 | Max rows pushed. `0` = all items. Above 1,000 is rejected, not clamped. |
| `include` | string\[] | skills, education, experience | Any of `skills`, `education`, `experience` plus the opt-in extras `about`, `certifications`, `languages`, `honors`, `projects`, `publications`, `patents`, `recommendations`, `interests`, `articles`, `groups`, `profileDetails`, `volunteer`, `organizations`, `courses`, `testScores`, and the groups `location`, `links`, `currentJob`, `analytics`, `network`, `quality`. `all` returns everything. |
| `includeAudit` | boolean | false | Adds `audit` (requestId, unitsCharged, latencyMs, inputRaw, inputCanonical). |
| `maxChargeUsd` | number | 0 | Hidden. Stops the run when this much has been charged. `0` = no cap. |
| `sourceTag` | string | none | Hidden. Copied onto every row and the run summary. |

Unknown fields are rejected with a message ("remove unsupported fields: x") so a typo never returns unrelated results.

### 📤 Output: LinkedIn education history and skills

Example row (keys as returned in our 2026-10-13 test runs; values are illustrative; `skills` appears only when the profile lists skills):

```json
{
  "profileUrl": "https://www.linkedin.com/in/example-person",
  "slug": "example-person",
  "fullName": "Example Person",
  "headline": "CTO at Example Corp",
  "jobTitle": "CTO",
  "seniority": "cxo",
  "company": "Example Corp",
  "location": "Berlin, Germany",
  "country": "DE",
  "startYear": 2020,
  "tenureYears": 5,
  "totalExperienceYears": 14,
  "connections": 500,
  "updatedAt": "2026-10-13T09:00:00.000Z",
  "skillsCount": 2,
  "skills": ["Distributed Systems", "Go"],
  "educationCount": 1,
  "education": [{ "school": "Example University", "startYear": 2006, "endYear": 2010 }],
  "experienceCount": 1,
  "experience": [{ "title": "CTO", "company": "Example Corp", "startDate": "2020-03", "current": true, "location": "Berlin, Germany" }],
  "sourceTag": "my-campaign",
  "inputQuery": "https://www.linkedin.com/in/example-person",
  "scrapedAt": "2026-10-13T10:00:00.000Z"
}
```

| Key | Type | Notes |
|---|---|---|
| `profileUrl` | string | Canonical profile link. |
| `slug` | string | Profile slug. |
| `fullName` | string | Full name. |
| `firstName` | string | First name. |
| `lastName` | string | Last name. |
| `headline` | string | Profile headline. |
| `jobTitle` | string | Current job title. |
| `company` | string | Current company. |
| `seniority` | string | `cxo`, `vp`, `director`, `manager`, `founder` or `other`. |
| `department` | string | Conditional: only when the profile has a job function. |
| `location` | string | Free-text location. |
| `country` | string | ISO-2 country code. |
| `startYear` | number | Start year of the current role. |
| `tenureYears` | number | Years in the current role. |
| `totalExperienceYears` | number | Total years of experience. |
| `connections` | number | Connections count (LinkedIn caps it at 500). |
| `updatedAt` | string | When the source record was last updated. |
| `skillsCount` | integer | Number of skills; shown when `skills` is requested, `0` when the profile lists none. |
| `skills` | string\[] | Skill names. Present only when the profile lists skills. |
| `educationCount` | integer | Number of education entries; shown when `education` is requested. |
| `education` | object\[] | `school`, `startYear`, `endYear`; `degree`, `fieldOfStudy`, `grade`, `activities` and `description` only when the profile has them. |
| `experienceCount` | integer | Number of jobs; shown when `experience` is requested. |
| `experience` | object\[] | `title`, `company`, `startDate`, `current`, `location`; `endDate`, `employmentType` and `description` only when present. |
| `about` | string | Only when requested and present. |
| `certifications`, `languages`, `honors`, `projects`, `publications`, `patents`, `interests`, `articles`, `groups` | object\[] or string\[] | Only when requested and present. |
| `recommendations` | string\[] | Only when requested and present. |
| `industry`, `followersCount`, `profileImageUrl`, `backgroundImageUrl` | string / integer | Only with `profileDetails`, when present. Image URLs expire. |
| `volunteerExperience`, `organizations`, `courses`, `testScores` | object\[] | Only when requested and present. |
| `city`, `state`, `stateFullName`, `countryFullName`, `countryIso3` | string | Only with `location`, when present. |
| `githubUrl`, `twitterUrl`, `facebookUrl`, `websiteUrl` | string | Only with `links`, when present. |
| `currentCompanyLinkedinId`, `currentCompanyLogoUrl`, `currentJobStartDate`, `currentJobDescription`, `currentJobLocation`, `jobTitleNormalized` | string | Only with `currentJob`, when present. The logo URL expires. |
| `currentEmploymentType`, `currentJobFunction`, `currentSeniority` | string\[] | Only with `currentJob`, when present. |
| `seniorityLevel`, `highestDegree` | string | Only with `analytics`, when present. |
| `isDecisionMaker`, `hasWorkEmail`, `hasPersonalEmail`, `hasPhone` | boolean | Only with `analytics`. They say whether the source has the data; no address or number is returned. |
| `averageTenureYears`, `certificationsCount`, `languagesCount`, `companiesCount` | number | Only with `analytics`. |
| `relatedProfiles`, `similarNames`, `activities` | object\[] / string\[] | Only with `network`, when present. |
| `dataQualityPublicExperienceAvailable`, `sectionsIncluded` | boolean / string\[] | Only with `quality`. |
| `sourceTag` | string | Echo of your `sourceTag` input, when set. |
| `inputQuery` | string | The line you supplied. |
| `scrapedAt` | string | ISO time the row was produced. |
| `audit` | object | Only with `includeAudit`. |

Fields with no value are left out of the row. Fields you might see only when present: `department`, `skills`, `degree`, `fieldOfStudy`, `grade`, `activities`, `employmentType`.

Other records in the run: a `free_plan_limit` notice row on the free plan when a list is longer than 100 (`type: "notice"`, never charged), the key-value records `MISSES`, `RUN_SUMMARY` and `errors-linkedin-skills-scraper`.

#### What is NOT returned

- ❌ Emails and phone numbers (only presence booleans with `analytics`; use [`b2bdata/email-phone-finder`](https://apify.com/b2bdata/email-phone-finder))
- ❌ Posts, comments, connections lists
- ❌ People search by name or company (use [`b2bdata/linkedin-people-search`](https://apify.com/b2bdata/linkedin-people-search))

### 🛡️ Reliable results

If a record is outdated, a person has moved or a list is wrong, report it in the Issues tab with the run link. Every report is reviewed and resolved case by case.

Not every person lists skills or schools. In our small test (2026-10-13, 7 profiles returned, so treat it as a rough guide) 3 listed skills, 6 listed education and all 7 listed experience. A profile with none of the lists you asked for is not charged and appears in `MISSES` with the reason `no_requested_sections`.

### 🤖 For AI agents (MCP)

Run through the Apify MCP server. Prompt to copy:

> "Use b2bdata/linkedin-skills-scraper with items=\["https://www.linkedin.com/in/satyanadella"] and limit=5 and return fullName, skills and education."

- **Input contract:** `items` required; everything else optional. `limit: 1` returns one row for a well-known profile.
- **Output contract:** one dataset row per found profile; same input twice gives the same rows.
- **Cost per call:** one `profile` event per delivered row. Nothing for misses, errors or notices.
- **Chaining:** feed the links from `linkedin-person-search`; pass results to `email-phone-finder`.

### 🔌 Integrations: send results anywhere

Fetch results over the Apify API, add a webhook on run success, or connect through n8n, Make, Zapier, Google Sheets, Clay (HTTP API), Airtable, HubSpot or Salesforce. Map `skills` to a multi-select and `education` to a text or JSON field.

### 🕐 Schedule examples

- Weekly refresh of a candidate list: schedule the run every Monday with the same `items`.
- Monthly CRM top-up: schedule a run with `maxChargeUsd` set to your monthly budget.

### 🔗 Related actors

- `b2bdata/linkedin-profile-by-url` — the cheap flat row (name, title, company, tenure) when you do not need the lists.
- `b2bdata/linkedin-person-search` — find the profile link from a name.
- `b2bdata/linkedin-people-search` — find people by title, company and location.
- `b2bdata/email-phone-finder` — add a work email and phone to the people you found.

### ❓ FAQ

**How do I scrape LinkedIn skills from profile URL links?**
Paste the profile links in `items` and set `include` to `["skills"]`. Each charged row has a `skills` array of names; profiles that list no skills are skipped and not charged.

**Can I get a LinkedIn skills list from profile URL links without logging in?**
Yes. No account or cookies are needed and nothing is opened on linkedin.com.

**Can I get skills and education from LinkedIn profile without login?**
Yes. Leave `include` empty to get `skills`, `education` and `experience` in one row, with no account and no cookies.

**How do I extract LinkedIn education history bulk, up to 1,000 links?**
Put the links in `items`. Each row has an `education` array with school, degree, field and years. Export as CSV from the dataset.

**How do I run a LinkedIn profile education and skills export to a spreadsheet?**
Export the dataset as CSV or JSON, or send it to Google Sheets, Airtable or Clay. Use `skillsCount` and `educationCount` for filters.

**Do I pay when a profile has no skills?**
It depends on `include`. With `include` set to `["skills"]`, a profile without skills is not charged. With the default (skills, education and experience), a profile is charged when any one of the three has content, so a profile with education but no skills is charged.

### 🛟 Support

Use the Issues tab for questions, wrong data or feature requests. We answer within 48 h.

See `CHANGELOG.md` for changes.

*This actor is not affiliated with, endorsed by, or sponsored by LinkedIn. LinkedIn is a trademark of its owner.*

# Changelog

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

# Actor input Schema

## `items` (type: `array`):

Accepted forms: https://www.linkedin.com/in/slug, linkedin.com/in/slug, or just the slug. Example: https://www.linkedin.com/in/satyanadella. Results keep input order; duplicates are collapsed. At most 1000 profiles per run (more is rejected, nothing is charged).

## `limit` (type: `integer`):

Maximum rows pushed to the dataset. 0 means all items. Hard maximum 1000; a higher number is rejected, not clamped. Omit for 25.

## `include` (type: `array`):

Which sections to add to each row. Default: skills, education, experience. Optional extras, returned only when requested and present on the profile: about, certifications, languages, honors, projects, publications, patents, recommendations, interests, articles, groups, volunteer (volunteerExperience), organizations, courses, testScores, and profileDetails (industry, followersCount, profileImageUrl, backgroundImageUrl; the image URLs expire). Field groups: location (city, state, stateFullName, countryFullName, countryIso3), links (githubUrl, twitterUrl, facebookUrl, websiteUrl), currentJob (currentCompanyLinkedinId, currentCompanyLogoUrl which expires, currentJobStartDate, currentJobDescription, currentJobLocation, currentEmploymentType, currentJobFunction, currentSeniority, jobTitleNormalized), analytics (seniorityLevel, isDecisionMaker, averageTenureYears, certificationsCount, languagesCount, companiesCount, highestDegree, and the booleans hasWorkEmail, hasPersonalEmail, hasPhone; never the addresses or numbers), network (relatedProfiles up to 10, similarNames, activities), quality (dataQualityPublicExperienceAvailable, sectionsIncluded). Use all for every section and group. Extras are never charged separately. A profile is charged only when at least one requested list among skills, education, experience has content, so always include at least one of those three (all includes them).

## `includeAudit` (type: `boolean`):

Adds requestId, unitsCharged, latencyMs, inputRaw, inputCanonical to each row (audit view). Omit for false.

## `maxChargeUsd` (type: `number`):

Stops the run once this much has been charged. 0 means no cap.

## `sourceTag` (type: `string`):

Free text copied onto every row and the run summary, for your own tracking.

## Actor input object example

```json
{
  "items": [
    "https://www.linkedin.com/in/satyanadella"
  ],
  "limit": 25,
  "include": [
    "skills",
    "education",
    "experience"
  ],
  "includeAudit": false
}
```

# Actor output Schema

## `profiles` (type: `string`):

One row per profile found: name, title, company, skills, education, experience

# 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 = {
    "items": [
        "https://www.linkedin.com/in/satyanadella"
    ],
    "include": [
        "skills",
        "education",
        "experience"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("b2bdata/linkedin-skills-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 = {
    "items": ["https://www.linkedin.com/in/satyanadella"],
    "include": [
        "skills",
        "education",
        "experience",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("b2bdata/linkedin-skills-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 '{
  "items": [
    "https://www.linkedin.com/in/satyanadella"
  ],
  "include": [
    "skills",
    "education",
    "experience"
  ]
}' |
apify call b2bdata/linkedin-skills-scraper --silent --output-dataset

```

## MCP server setup

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