DEV Community

Mena489
Mena489

Posted on

How to Scrape LinkedIn Profiles Without Cookies or a Session Token

You can extract public LinkedIn profile data — name, headline, location, follower count, full work history, education and recent posts — without a li_at cookie, a logged-in account, or a headless browser. This guide covers why cookie-based LinkedIn scraping keeps breaking, and the approach that does not.

Short answer: send profile URLs to the LinkedIn Profile Scraper and get structured JSON back. No cookie required.

Why cookie-based LinkedIn scraping fails

Almost every LinkedIn scraper on the market asks you for your li_at session cookie. That design has three problems, and all of them eventually cost you the account.

1. The cookie is your account. Handing li_at to a scraper is handing over full session access. If the tool leaks it, someone else is logged in as you.

2. LinkedIn rate-limits per account, not per IP. Rotating proxies does not help when the identity is constant. LinkedIn tracks request volume against the session, and an account pulling a few thousand profiles gets restricted — often permanently, and often on a real account someone uses professionally.

3. Cookies expire, and they expire silently. A pipeline built on li_at works until it does not, usually mid-run, usually returning HTTP 999 or an empty page rather than a clean auth error.

The result is a scraper that needs a human to babysit it, plus a steady supply of burner accounts.

The approach that avoids the account entirely

LinkedIn serves a genuine public renderer for profiles that are visible to logged-out visitors — that is how profiles show up in Google results. The trick is convincing LinkedIn you are an ordinary logged-out reader rather than a bot.

That means getting several things right at once:

  • A coherent browser identity. TLS fingerprint, User-Agent and sec-ch-ua headers must all describe the same browser. Mismatched values are the single most common tell.
  • Plausible provenance. A request claiming same-origin with zero LinkedIn cookies is a contradiction. Arriving from a search engine is how a public profile actually gets read.
  • Country rotation. Retrying from a different country spreads load across IP pools rather than hammering one.

The scraper handles all of it. You send URLs; it deals with the rest.

Step 1 — Give it profile URLs

{
    "profile_urls": [
        "https://www.linkedin.com/in/williamhgates/",
        "https://www.linkedin.com/in/satyanadella/"
    ],
    "proxy_country": "US,GB,DE,CA,FR",
    "max_concurrency": 5
}
Enter fullscreen mode Exit fullscreen mode

proxy_country accepts a comma-separated list. Rotating across countries per retry measurably reduces 999 and 404 rates compared with pinning every attempt to one country.

Step 2 — Read the results

{
    "url": "https://www.linkedin.com/in/williamhgates/",
    "status": 200,
    "name": "Bill Gates",
    "headline": "Chair, Gates Foundation and Founder, Breakthrough Energy",
    "location": "Seattle, Washington, United States",
    "country": "US",
    "follower_count": 40304014,
    "experience": [
        { "name": "Gates Foundation", "url": "...", "start_year": 2000, "end_year": null }
    ],
    "education": [
        { "name": "Harvard University", "url": "...", "start_year": 1973, "end_year": 1975 }
    ],
    "recent_posts": [
        { "text": "...", "url": "...", "published_at": "...", "likes": 1203 }
    ]
}
Enter fullscreen mode Exit fullscreen mode

An end_year of null means the role is current — useful for filtering to people actively at a target company.

Calling it from your own code

Python

from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("mina_safwat/linkedin-profile-scraper-no-cookies").call(
    run_input={
        "profile_urls": ["https://www.linkedin.com/in/williamhgates/"],
        "proxy_country": "US,GB,DE,CA,FR",
    }
)

for profile in client.dataset(run["defaultDatasetId"]).iterate_items():
    current = [job for job in profile.get("experience", []) if job.get("end_year") is None]
    print(profile["name"], "", current[0]["name"] if current else "no current role")
Enter fullscreen mode Exit fullscreen mode

cURL

curl -X POST "https://api.apify.com/v2/acts/mina_safwat~linkedin-profile-scraper-no-cookies/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"profile_urls":["https://www.linkedin.com/in/williamhgates/"]}'
Enter fullscreen mode Exit fullscreen mode

What it costs

Per profile returned: $0.0075 on the free plan, $0.00583 on Bronze, $0.00417 on Silver, and $0.0025 on Gold and above. Enriching a 5,000-row prospect list costs about $12.50 on Gold.

Compare that with LinkedIn Sales Navigator at roughly $99/month per seat, which still does not let you export data programmatically.

Common use cases

  • B2B lead enrichment. Turn a list of profile URLs into titles, companies and tenure for scoring and routing.
  • Recruitment sourcing. Filter candidates by current employer, years in role, or education without paying per InMail.
  • Sales prospecting. Detect job changes — a new VP in your ICP is the strongest buying signal there is.
  • Market mapping. Chart who works where across an industry segment.

FAQ

Do I really not need a cookie?
Correct, for public profiles. The scraper does accept an optional li_at as a fallback for profiles that are not publicly visible, but you never have to provide one, and the default path does not use it.

Is scraping public LinkedIn profiles legal?
In hiQ Labs v. LinkedIn, US courts held that scraping publicly available profile data does not violate the Computer Fraud and Abuse Act. That is not legal advice, and it is not the whole picture: LinkedIn's Terms of Service still prohibit scraping, and profile data is personal data under GDPR, which means you need a lawful basis to process it and must honour deletion requests. Consult a lawyer for your specific use case.

What happens with private profiles?
You get a record with a status code and no profile fields, rather than a silent failure. Filter on status before processing.

Can I search for profiles rather than supply URLs?
Yes — use the LinkedIn Search Scraper to find profiles by keyword, then feed those URLs into this one.

How fast is it?
Concurrency is configurable. Keep max_concurrency low (1–3) for large batches; LinkedIn tolerates a steady trickle far better than a burst.


Try it: LinkedIn Profile Scraper on Apify Store — no cookies, no account, pay per profile.

Top comments (0)