# LinkedIn People Finder - Profiles, Companies, No Login (`snow_leo_data/linkedin-profile-scraper`) Actor

No login, no cookie: 44 consecutive LinkedIn pages at one request per second returned zero blocks. 62 columns. LinkedIn company people scraper where one seed expands into 10 LinkedIn similar companies, up to 7 affiliated pages and 10-20 similar profiles.

- **URL**: https://apify.com/snow\_leo\_data/linkedin-profile-scraper.md
- **Developed by:** [Snow Leo Data](https://apify.com/snow_leo_data) (community)
- **Categories:** Social media, Lead generation, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.90 / 1,000 result rows

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 Company & People Scraper

Scrape LinkedIn **company pages and public profiles** without a login, a
cookie, a session or a browser. Point it at a company or a person, and it
returns the page as structured columns — and, if you ask it to, everything
that page links to.

Every number on this page was measured from Apify's cloud on 2026-09-17 and
is checkable by running `python3 tools/verify_readme.py`.

### What you get

**62 distinct columns** — 36 on a company row, 39 on a person row.

For a company:

| | |
|---|---|
| Identity | name, LinkedIn's own numeric company ID, slug, URL, tagline, logo, cover image |
| About | full description, website, industry, specialties |
| Size | LinkedIn's size band, plus the band split into a low and a high number, plus the exact employee count LinkedIn publishes |
| Address | street, city, region, postal code, country — split, not one label |
| More | company type, founding year, followers, office locations, jobs page |
| Neighbours | similar companies, affiliated pages, the employees the page names |
| Posts | recent posts and articles with their text and dates |

For a person:

| | |
|---|---|
| Identity | name, first and last name, headline, photo, cover image, badges |
| About | about text, location, country |
| Work | current title, current company with its LinkedIn slug and start date, and the full experience list with start and end dates |
| Study | education with start and end dates |
| Reach | followers, connections |
| Neighbours | similar profiles |
| Posts | recent posts and articles with their text and dates |

The address and the dates are the part worth looking at twice. A company row
carries `street_address`, `city`, `region`, `postal_code` and `country` as
five separate columns, and an experience entry carries `start` and `end`,
because "South San Francisco, California" in one cell cannot be filtered and
"Microsoft" without a date cannot be sorted.

### One seed becomes a neighbourhood

LinkedIn closed its signed-out people search. Measured on 2026-09-17 from the
cloud, on four spellings each: `/search/results/people/` answers HTTP 200 and
returns the login page, `/pub/dir/` answers HTTP 999, `/directory/people-s-1/`
answers HTTP 999. There is no public query endpoint left to wrap, and this
Actor does not pretend there is.

What is open is every page's own links to its neighbours, and they are dense:

| A page of this kind | links out to | measured on |
|---|---|---|
| company | 10 similar companies | 4 of 4 company pages |
| company | up to 7 affiliated pages | Microsoft: 7, Zalando: 2, Notion: 1 |
| company | 1-4 named employees | 4 of 4 company pages |
| profile | 10-20 similar profiles | 3 of 3 profiles |

So set **Expand depth** to 1 and one company seed becomes itself plus the
companies and people it links to; set it to 2 and each of those expands
again. The walk is breadth-first, so if a run hits your row limit it has
spent it on the rows closest to what you asked about. Every run also reports
`neighbours_reachable` — how many more entities the pages it fetched point
at — so you can see what raising the depth would buy before you pay for it.

### No proxy, and that is measured

25 company pages in a row, one request per second, from one Apify address
with **no proxy at all**: 25 answered, 23 with data and 2 with HTTP 404 for
slugs that do not exist. Zero blocks. 19 profile pages the same way: 16
answered with data and 3 answered HTTP 999 — and the request after each 999
succeeded, so it is a per-request refusal that a retry clears, not a wall.

The pace is the one that was proven rather than a guess at a faster one: a
default of 1 second between requests, which is what those 44 consecutive
pages were fetched at.

The same pages through Apify's UNBLOCKER proxy return the same bytes and cost
about $0.001 each. This Actor therefore runs direct by default. A proxy group
is there in the input if your own runs ever start being refused, and it is
off until you turn it on.

### The trap this Actor is built around

Five public LinkedIn paths answer **HTTP 200 with half a megabyte that is the
login screen**, not the page you asked for:

| Path | Answer | Body |
|---|---|---|
| `/company/<slug>/about/` | 200 — login page | 488 084 bytes |
| `/company/<slug>/people/` | 200 — login page | 491 287 bytes |
| `/company/<slug>/posts/` | 200 — login page | 490 985 bytes |
| `/company/<numeric id>/` | 200 — login page | 484 081 bytes |
| `/search/results/people/` | 200 — login page | 52 486 bytes |

Neither the status code nor the body size can tell them apart from a real
page — a real company page measured anywhere from 100 549 to 484 513 bytes.
The only honest signal is the `og:url` meta tag, and this Actor reads it on
every response. A page that turns out to be the login screen is reported as
`login_wall` in the run report instead of being parsed into a row of blanks.

The same applies to what you paste in: if you copy an address while sitting
on a company's People tab, the tab is stripped back to the company root,
which does carry data.

### Honest limits

- **The full employee list is not available.** `/company/<slug>/people/` is
  behind the login for everyone who is not signed in. What a company page
  gives a signed-out reader is 1-4 named employees, measured on four
  companies. No signed-out scraper can do better; anything claiming a
  complete employee roster is using a logged-in cookie.
- **Keyword search does not exist here.** See above — the endpoints are
  closed. The filters in this Actor run on rows it has already fetched, and
  they say so.
- **LinkedIn masks some fields for signed-out readers.** Measured on
  `/in/jaycarney`: all 11 job titles came back as asterisks. They are dropped
  rather than delivered, and counted in `masked_job_titles` so you know it
  happened.
- **`connections` is a floor, not a count.** LinkedIn prints "500+" for most
  people. The numeric column holds 500 and `connections_label` holds the
  string exactly as printed.
- **Slugs are not brand names.** `tesla` and `intel` both answer HTTP 404 —
  those companies sit on other slugs. Copy the address from the page.
- **`founded` and `specialties` are often absent.** Microsoft publishes no
  founding year; Stripe and Notion publish no specialties.

### Running it on a schedule

Turn on **Monitor mode** and the Actor remembers what it already delivered, in
a named key-value store that survives between runs. The next run returns only
pages that are new or whose details moved, each labelled `NEW` or `UPDATED`
with `changed_fields` naming what was there before.

Follower and connection counts are deliberately not watched: they drift
hourly, and watching them would label every row `UPDATED` on every run, which
is the same as having no monitor mode at all.

This matters more than it sounds in this niche. `harvestapi/linkedin-company`
shows 10 897 785 runs against 21 293 users — 512 runs per user (Apify Store
API, 2026-09-17). People schedule this kind of scraper, and without memory a
daily run bills them for the same rows every morning.

### When a run returns nothing

It tells you why, in the log and in `RUN_REPORT`: which addresses answered
404, which came back as the login page, which filter removed how many rows,
or that monitor mode found everything unchanged. A successful run with an
empty dataset and no explanation is the worst thing a paid scraper can do.

### Output shapes

- **Compact rows** replaces the long lists with their lengths — a company row
  measured at 14 KB becomes under 2 KB.
- **Drop empty fields** leaves out what is blank on a given row while keeping
  the identifying columns. For AI agents and anything paying per token.

### Verification

- `python3 -m tests.test_logic` — 222 checks on 11 real responses saved from
  the cloud on 2026-09-17, no network needed.
- `python3 tools/verify_readme.py` — every number on this page traced back to
  the code or the saved responses.
- `python3 tools/prove.py` — breaks the Actor on purpose, 42 different ways,
  and fails if any break goes unnoticed.

# Actor input Schema

## `companies` (type: `array`):

LinkedIn company pages to scrape. Any spelling works: a slug (stripe), a bare address (linkedin.com/company/stripe) or a full URL. Country subdomains are fine. If you paste a tab such as /about/ or /people/, it is stripped back to the company root, because those tabs answer with LinkedIn's login page instead of data.

## `profiles` (type: `array`):

Public LinkedIn profiles to scrape, as a slug (williamhgates) or a full /in/ URL. Only public profiles exist to a signed-out reader; a private one answers with the login page and is reported as such rather than returned blank.

## `expandDepth` (type: `integer`):

How far to walk out from your seeds. 0 scrapes only what you listed. 1 adds everything your pages link to: measured on the saved pages, one company page links 10 similar companies, up to 7 affiliated pages and 1-4 named employees, and one profile links 10-20 similar profiles. 2 repeats that from each of those. Use this instead of a keyword search, which LinkedIn has closed to signed-out readers.

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

Stop after this many rows. 0 means no limit. The walk is breadth-first, so a run that stops early has spent its budget on the rows nearest your seeds.

## `expandCompanies` (type: `boolean`):

While expanding, follow the similar and affiliated companies linked from a page.

## `expandPeople` (type: `boolean`):

While expanding, follow the similar profiles linked from a profile page.

## `followEmployees` (type: `boolean`):

While expanding, follow the employees a company page names. Measured: 1-4 per company page. The full employee list sits behind LinkedIn's login and is not available to any signed-out scraper.

## `entityTypes` (type: `array`):

Leave empty to keep both. Otherwise choose company, person, or both.

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

Keep a row only if one of these words appears in its name, headline, about text, description, tagline, industry, current title or current company.

## `countries` (type: `array`):

Keep a row only if its country matches. Two-letter codes (US, DE, GB) are matched against the postal address; longer text is matched against the headquarters or location label.

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

Keep a row only if its location, headquarters, city or region contains one of these.

## `industries` (type: `array`):

Keep a company only if its industry contains one of these, for example Software Development or Retail.

## `minCompanySize` (type: `integer`):

Keep a company only if its size band reaches this. The band is LinkedIn's own, for example 501-1,000 employees.

## `maxCompanySize` (type: `integer`):

Keep a company only if its size band starts at or below this. 0 means no ceiling.

## `minEmployees` (type: `integer`):

Keep a company only if it has at least this many employees with LinkedIn profiles. This is the exact count LinkedIn publishes, not the band.

## `minFollowers` (type: `integer`):

Keep a row only if the page has at least this many followers.

## `requireWebsite` (type: `boolean`):

Keep a company only if it publishes a website address.

## `includePosts` (type: `boolean`):

Include the recent posts and articles the page carries, with their text and publication dates. Measured: 2-10 per page. They come with the page at no extra request.

## `compactOutput` (type: `boolean`):

Replace the long lists - posts, similar pages, employees, experience, education - with their lengths. Measured on a company row: 14 KB becomes under 2 KB.

## `dropEmptyFields` (type: `boolean`):

Leave out fields that are empty on a given row, keeping the identifying ones. Useful for AI agents and anything paying per token.

## `monitorMode` (type: `boolean`):

Remember what was already delivered and return only pages that are new or whose details changed, each labelled NEW or UPDATED with the exact fields that moved. Follower and connection counts are deliberately not watched: they drift hourly and would mark every row changed on every run.

## `emitUnchanged` (type: `boolean`):

In monitor mode, also return pages that have not changed, labelled UNCHANGED.

## `requestGapSeconds` (type: `integer`):

Pace of requests. The default of 1 second is the pace that was measured to draw zero blocks over 44 consecutive pages. Going faster is your risk.

## `proxyGroup` (type: `string`):

Off by default, and that is the measured default: 44 consecutive pages from an Apify address with no proxy at all drew zero blocks, so a proxy would be pure cost. Turn it on only if your own runs start being refused. UNBLOCKER costs roughly $0.001 per page on top of this Actor's price, charged to your account by Apify.

## Actor input object example

```json
{
  "companies": [
    "stripe",
    "notionhq",
    "zalando"
  ],
  "profiles": [
    "satyanadella",
    "williamhgates"
  ],
  "expandDepth": 0,
  "maxItems": 0,
  "expandCompanies": true,
  "expandPeople": true,
  "followEmployees": true,
  "minCompanySize": 0,
  "maxCompanySize": 0,
  "minEmployees": 0,
  "minFollowers": 0,
  "requireWebsite": false,
  "includePosts": true,
  "compactOutput": false,
  "dropEmptyFields": false,
  "monitorMode": false,
  "emitUnchanged": false,
  "requestGapSeconds": 1,
  "proxyGroup": "NONE"
}
```

# Actor output Schema

## `results` (type: `string`):

All collected rows

# 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 = {
    "companies": [
        "stripe",
        "notionhq",
        "zalando"
    ],
    "profiles": [
        "satyanadella",
        "williamhgates"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("snow_leo_data/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 = {
    "companies": [
        "stripe",
        "notionhq",
        "zalando",
    ],
    "profiles": [
        "satyanadella",
        "williamhgates",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("snow_leo_data/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 '{
  "companies": [
    "stripe",
    "notionhq",
    "zalando"
  ],
  "profiles": [
    "satyanadella",
    "williamhgates"
  ]
}' |
apify call snow_leo_data/linkedin-profile-scraper --silent --output-dataset

```

## MCP server setup

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