~ / guides / How to Scrape LinkedIn Profiles (2026)

How to Scrape LinkedIn Profiles (2026)

PN
Priya Nair
LinkedIn data engineer · about the author
the short version
  • A public LinkedIn profile holds the fields most people want - headline, experience, education, and skills - but the logged-out /in/ page returns HTTP 200 with a sign-in wall, not the data. The real name shows in the page title, but the structured fields do not.
  • Three routes return profile fields: a logged-in browser session driven by linkedin_scraper (now Playwright, async), residential proxies with a real headless browser, or a scraper API that returns parsed JSON and handles the blocks.
  • The ChocoData profile endpoint returns name, headline, experience, education, and skills as JSON from one GET request. No login, 1,000 free requests, billed only on success.
  • Scraping a public profile is defensible under hiQ v. LinkedIn, but a profile is personal data under the GDPR and LinkedIn's User Agreement forbids automation, so the account and compliance risk is real. Details here.

I wanted to know exactly how to scrape LinkedIn profiles in 2026, so I ran the requests myself against live profiles in July 2026 and watched what came back. The short version: a public LinkedIn profile holds the fields most people want - the headline, experience, education, and skills - but the logged-out page hands you a sign-in wall instead of the data.

This guide is what I measured and the code that actually returns profile fields. Every snippet is Python I executed against live LinkedIn targets, and where a route is walled I show the status code I got so you can recognize it in your own logs.

What can you scrape from a public LinkedIn profile?

From a public LinkedIn profile you can scrape the headline, experience, education, and skills, plus the name, location, and the About summary. These are the fields a member chooses to display publicly, and together they are what a profile scraper is built to collect. What you cannot scrape is the private layer: the personal email, the phone number, and the connection list, which all sit behind a logged-in session.

Here is the split between what a public profile exposes and what stays private even to a logged-in scrape:

Profile fieldOn the public profile?Notes
Name, headlineYesThe core identity fields
ExperienceYesTitles, companies, and dates when the member lists them
EducationYesSchools and degrees, when public
SkillsYesThe listed skills section
Location, AboutYesCity or region and the summary text
Personal email, phoneNoPrivate, in the logged-in Contact-info modal
Connections listNoConnection-only, needs a logged-in account

The catch is that “on the public profile” does not mean “in the HTML you get back from a plain request.” LinkedIn renders those public fields for a logged-in visitor and hides them behind a sign-in wall for an anonymous one. So the practical question is not which fields are public, it is how you get the page to render them, which is where the block starts.

Why does a LinkedIn profile return a sign-in wall?

A LinkedIn profile returns a sign-in wall because the logged-out /in/ page serves an authwall shell to anonymous clients instead of the profile data. When I fetched https://www.linkedin.com/in/williamhgates with a current Chrome User-Agent in July 2026, LinkedIn returned HTTP 200 and a page of about 650 KB. The <title> held the real name, which is good for a search snippet, but the body carried repeated “sign in” prompts and none of the structured headline, experience, education, or skills fields.

Drop the User-Agent and it gets worse. A request with no User-Agent, or one identifying as python-requests, returns HTTP 999, LinkedIn’s non-standard “Request Denied” code. The 999 means the request never reached the data tier at all, so it is a hard tell that you are being filtered on the IP and fingerprint before the page even renders.

The reason the fields are missing from the 200 page is architectural. LinkedIn’s own front end loads profile data from an internal REST API called Voyager, and Voyager needs a logged-in session cookie plus a CSRF token. The logged-out HTML I fetched carries no Voyager payload, so there is nothing structured to parse out of it. This is also why LinkedIn has no public people API you could call instead: the Consumer and Sign In APIs only return your own account after OAuth, and richer data is gated behind Partner Program approval. To get another member’s experience and education, you have to render the authenticated page, so the next step is a real browser.

How do you scrape a LinkedIn profile with Python?

You scrape a LinkedIn profile with Python by driving a real logged-in browser that renders the authenticated page, then reading the parsed fields, because a genuine browser session is what clears the fingerprint checks that block plain HTTP. First, here is the naive request that fails, so you can recognize the wall when you see it in your own output:

import requests

ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36"
r = requests.get("https://www.linkedin.com/in/williamhgates", headers={"User-Agent": ua}, timeout=20)

print(r.status_code)                 # -> 200
html = r.text.lower()
print("sign in" in html)             # -> True  (auth wall, not the profile)
print('"experience"' in html)        # -> False (no structured experience to parse)

The 200 looks like a win until you check the body: the sign in string is present and the structured fields are not. To load the page where the data actually renders, the most used open-source option is linkedin_scraper by joeyism. Version 3.x moved from Selenium to Playwright and made every method async, exposing a PersonScraper you point at a saved logged-in session:

# pip install linkedin_scraper playwright && playwright install chromium
import asyncio
from linkedin_scraper import BrowserManager, PersonScraper

async def main():
    async with BrowserManager(headless=False) as browser:
        await browser.load_session("session.json")     # a saved, logged-in session
        scraper = PersonScraper(browser.page)
        person = await scraper.scrape("https://www.linkedin.com/in/williamhgates/")

        print(person.name, "-", person.headline)
        for exp in person.experiences:                  # work history
            print(exp.position_title, "at", exp.institution_name)
        for edu in person.educations:                   # schools and degrees
            print(edu.institution_name, edu.degree)
        print("Skills:", ", ".join(person.skills[:10])) # List[str]

asyncio.run(main())

The Person object gives you exactly the fields this article is about: name, headline, experiences, educations, and skills. Several are typed Optional, so a member who left a section blank returns None, and you should guard for that before writing a row. The tradeoff is honest: this runs through your own logged-in account against a site whose User Agreement forbids automation, so the per-account risk is yours to carry, and one browser session is sequential and slow. For the full end-to-end build, including the login step and the selectors, see how to scrape LinkedIn with Python. Pushing this past a few hundred profiles is where the IP and blocking problem takes over.

How do you scrape LinkedIn profiles without getting blocked?

You scrape LinkedIn profiles without getting blocked by taking the proxy rotation, the browser rendering, the login, and the retries off your own machine, which is what a scraper API does. You send one LinkedIn URL and get parsed JSON back, with no 999 to debug and no authwall to defeat. In my runs against the ChocoData profile endpoint, a single GET returned the profile as clean JSON, residential proxies handled server-side:

curl "https://chocodata.com/api/v1/linkedin/profile?url=https://www.linkedin.com/in/williamhgates&api_key=$CHOCO_API_KEY"

The same request in Python returns the headline, experience, education, and skills you would otherwise have to log in and parse out of the rendered DOM:

import requests

CHOCO_API_KEY = "your_api_key"   # from the ChocoData dashboard

resp = requests.get(
    "https://chocodata.com/api/v1/linkedin/profile",
    params={
        "url": "https://www.linkedin.com/in/williamhgates",
        "api_key": CHOCO_API_KEY,
    },
    timeout=60,
)
data = resp.json()

print(data["name"], "-", data["headline"])
print(data["location"])

for job in data["experience"]:          # work history
    print(job["title"], "at", job["company"])

for school in data["education"]:        # schools and degrees
    print(school["degree"], "-", school["school"])

print("Skills:", ", ".join(data["skills"][:10]))

The response is the same profile data the logged-in browser route would give you, returned as a single JSON object with an experience array, an education array, and a skills list, without a session cookie, a CSRF token, or a residential proxy of your own. The free plan covers 1,000 requests with no card, Pro works out to about $0.60 per 1,000 requests, and you are billed only for successful calls, so retries across proxy tiers behind a good response are not charged separately. Because the rotation lives on the server, your own LinkedIn login never touches the job, which removes the account risk the Python route carries. When you need more than one profile, the same endpoint scales without any extra setup.

How do you scrape many LinkedIn profiles at once?

You scrape many LinkedIn profiles at once by firing the same endpoint concurrently across a list of profile URLs, because each request is independent and the proxy rotation happens server-side. There is no shared session to keep warm and no per-account view ceiling to pace against, so a list of thousands of profiles collects in parallel. Here is an async batch with httpx that pulls the headline plus experience and skill counts for each URL:

import asyncio
import httpx

CHOCO_API_KEY = "your_api_key"
PROFILES = [
    "https://www.linkedin.com/in/williamhgates",
    "https://www.linkedin.com/in/satyanadella",
]

async def scrape_one(client, url):
    r = await client.get(
        "https://chocodata.com/api/v1/linkedin/profile",
        params={"url": url, "api_key": CHOCO_API_KEY},
        timeout=60,
    )
    p = r.json()
    return p["name"], p["headline"], len(p["experience"]), len(p["skills"])

async def main():
    async with httpx.AsyncClient() as client:
        tasks = [scrape_one(client, u) for u in PROFILES]
        for name, headline, jobs, skills in await asyncio.gather(*tasks):
            print(name, "-", headline, f"({jobs} roles, {skills} skills)")

asyncio.run(main())

Each await returns one parsed profile, so scaling from two URLs to two thousand is a longer list, not a bigger infrastructure project. The same request shape covers the other LinkedIn objects by swapping the path, so a profile pull, a company lookup, and a job read share one schema and one API key. For a side-by-side of the managed tools that do this, the best LinkedIn scrapers of 2026 roundup compares them on price and coverage. Before you collect at volume, though, it is worth knowing where the legal line sits.

Scraping publicly visible LinkedIn profiles is generally treated as legal in the US, but that is not blanket permission and profiles are personal data. The Ninth Circuit held in hiQ v. LinkedIn that scraping public data likely does not violate the Computer Fraud and Abuse Act, which makes collecting a public profile defensible on the anti-hacking front.

That ruling did not settle everything, though. The same case ended with hiQ losing on breach of LinkedIn’s User Agreement, which still prohibits scraping with software, bots, or crawlers, so an account used for automation can be restricted.

The part most guides skip is data protection. A name, a job title, and an employer are personal data, and the GDPR applies to personal data even when it is public, so “it is on a public profile” is not a defense for what you do next. If you process EU or UK profiles, you need a lawful basis (usually legitimate interest with a written balancing test), you should minimize what you store, and you have to honor deletion requests. The account risk and the regulatory risk are separate from the CFAA question, and outreach is usually where the real exposure lives. I walk through the full picture in is scraping LinkedIn legal. None of this is legal advice.

Sources

FAQ

Can you scrape a LinkedIn profile without logging in?

You can scrape only a fragment of a LinkedIn profile without logging in. The logged-out /in/ page returns HTTP 200, and the person's real name sits in the page <title>, which is why it shows in a Google snippet. The body is a sign-in wall: in my July 2026 test it carried repeated 'sign in' prompts and none of the structured headline, experience, education, or skills fields. To read those you need a logged-in browser session or a scraper API that renders the authenticated page for you.

How do you scrape experience and education from a LinkedIn profile?

You scrape experience and education by loading the authenticated profile in a real browser, then reading the parsed fields. The open-source linkedin_scraper library exposes person.experiences (each with a position title and company) and person.educations (school and degree) after you load a logged-in session. A scraper API returns the same two arrays as JSON without a browser: an experience list and an education list keyed off the profile URL you send.

Can you get a LinkedIn profile's email address?

No, you cannot reliably get a member's personal email from a LinkedIn profile, because it sits behind the logged-in Contact-info modal and is essentially never public. A profile can expose an external website link, which companies do publish, but the email field itself stays private. Any tool promising a member's private email from a public profile is either guessing the address or reading data it should not have.

How many LinkedIn profiles can you scrape per day?

There is no published LinkedIn scraping rate limit, because LinkedIn does not sanction scraping at all. Practitioners widely report keeping a single logged-in account under roughly 100 to 200 profile views per day with multi-second gaps to avoid a restriction. A scraper API moves that ceiling off your account by rotating its own residential IP pool, so per-account view limits stop being the bottleneck for larger jobs.

PN
Priya Nair
I've built LinkedIn data pipelines for years. On linkedinscraperapi.com I run LinkedIn scraping methods against live pages and publish what actually holds up.