Scraper Workers

LinkedIn prospector

The Ultron LinkedIn Prospector takes a set of search filters and returns matching profiles with their public fields: name, headline, location, current company, current title, profile URL. It pages through LinkedIn's People search using the caller's li_at cookie behind a residential proxy, follows redirects up to three hops, and dedupes by canonical profile id.

Updated today

Overview

At a glance
Worker
ultron-linkedin
Runtime
Node 20, raw https requests through HttpsProxyAgent
Source
apify-actors/linkedin/
Auth
li_at cookie required
Redirect handling
Up to 3 hops
Dedupe key
Canonical profile id from the final URL
Downstream
decision-maker-prospector, vc-prospector, hiring-manager-prospector

The prospector is the lightest of the LinkedIn scrapers because it does not need a full browser. The People search endpoint returns enough useful HTML in the initial response that a plain GET plus a small parser produces the fields the platform needs. Skipping Playwright saves seconds per request and removes the headache of Chromium memory inside the worker container.

Authentication

The li_at cookie is passed in headers on every request. Cookie missing means abort.

apify-actors/linkedin/src/main.jsjs
1if (!input.li_at) {
2 log.error('li_at cookie missing - refusing to run')
3 await Actor.fail('li_at cookie required')
4 return
5}
6
7const headers = {
8 'cookie': `li_at=${input.li_at}`,
9 'user-agent': ua,
10 'accept': 'text/html,application/xhtml+xml',
11 'accept-language': 'en-US,en;q=0.9',
12}

Input schema

Search filters + cookie + proxy controls.

apify-actors/linkedin/.actor/actor.jsonjson
1{
2 "queries": ["AI angel investor", "operator angel"],
3 "locations": ["United States", "United Kingdom"],
4 "current_company": [],
5 "title_keywords": ["investor", "partner"],
6 "industry": [],
7 "max_per_query": 100,
8 "li_at": "<required>",
9 "proxy_url": "http://session-<id>:<pass>@proxy.apify.com:8000",
10 "webhook_url": "https://app.51ultron.com/api/apify-webhook?source=linkedin",
11 "webhook_secret": "<shared>",
12 "user_id": "<uuid>",
13 "conversation_id": "<uuid>"
14}

Crawl flow

One pass per query × location. Each pass walks paged results.

apify-actors/linkedin/src/main.jsjs
1for (const query of input.queries) {
2 for (const location of input.locations) {
3 let page = 0
4 while (true) {
5 const url = buildPeopleSearchUrl({ query, location, page })
6 const res = await httpsGet(url, headers, proxyAgent)
7 const profiles = parseProfileCards(res.body)
8 if (!profiles.length) break
9
10 for (const p of profiles) {
11 if (seen.has(p.profile_id)) continue
12 seen.add(p.profile_id)
13 await Actor.pushData({ ...p, query, location, scraped_at: now() })
14 }
15 page += 1
16 if (page * pageSize >= input.max_per_query) break
17 }
18 }
19}

Proxy and redirects

Residential proxy with a hop-bounded redirect chaser.

apify-actors/linkedin/src/main.jsjs
1async function httpsGet(url, headers, agent, timeout = 20000, _depth = 0) {
2 const res = await httpsGetOnce(url, headers, agent, timeout)
3 if (
4 (res.status === 301 || res.status === 302 || res.status === 307 || res.status === 308) &&
5 res.headers.location &&
6 _depth < 3
7 ) {
8 const next = new URL(res.headers.location, url).toString()
9 return httpsGet(next, headers, agent, timeout, _depth + 1)
10 }
11 return res
12}
Note
LinkedIn frequently redirects unauthenticated and auth-edge cases through a few hops before settling on either the result page or the login wall. The chaser caps at three hops so it cannot loop forever. Any response after three hops that is still a redirect is treated as a soft failure for that page.

Output shape

One row per unique profile.

dataset-row.jsonjson
1{
2 "profile_id": "AC123abc",
3 "profile_url": "https://www.linkedin.com/in/maria-schultz",
4 "name": "Maria Schultz",
5 "headline": "Head of Sales at Acme | Angel @ ...",
6 "location": "Berlin, Germany",
7 "current_company": "Acme",
8 "current_title": "Head of Sales",
9 "query": "AI angel investor",
10 "scraped_at": "2026-05-11T18:42:00Z"
11}

How to trigger it

Same shape as the other Ultron Workers: HTTP POST against the Apify run endpoint with the platform-injected metadata.

invocationtext
1POST https://api.apify.com/v2/acts/ultron-linkedin/runs?token=<APIFY_TOKEN>
2Content-Type: application/json
3
4{
5 "queries": ["AI angel investor"],
6 "locations": ["United States"],
7 "max_per_query": 100,
8 "li_at": "<from the connected LinkedIn integration>"
9}

File map

apify-actors/linkedin
package.json
src
main.jsauth + crawl loop + parser + dedupe + push