How to Scrape LinkedIn Profiles in 2026: Step-by-Step Guide
TL;DR
Only publicly-visible LinkedIn profile fields are safe to collect. Anything visible to a logged-out visitor (name, headline, current title, location, and public posts) sits in a different legal and technical category than login-walled data such as connections, messages, or full work history.
LinkedIn's Terms of Service prohibit automated scraping outright, and its bot detection can rate-limit, block, or ban the account or IP making the requests.
hiQ Labs v. LinkedIn did not make scraping LinkedIn "legal" in a general sense. The Ninth Circuit found that scraping public data doesn't violate the federal Computer Fraud and Abuse Act, but a separate 2022 ruling found hiQ breached LinkedIn's contract terms, and the case ended in a permanent injunction against hiQ, not a win for scrapers.
A working extraction path uses requests and BeautifulSoup (or Playwright for JavaScript-rendered sections) against a public profile URL, parsing the page's embedded structured data rather than guessing at CSS class names that change without notice.
Rate limiting, session handling, and IP reputation determine whether a request succeeds, not clever workarounds β treat these as reliability engineering, not evasion.
GDPR and CCPA apply to scraped personal data the same way they apply to any other personal data you store, so a lawful basis and a retention limit matter before you build any database of names and titles.
Building a lead list or contact database from scraped profiles carries its own legal exposure separate from the scraping method itself, and commercial-scale collection should go through legal counsel first.
Introduction: what "scraping LinkedIn profiles" actually means
LinkedIn profile pages mix two very different data surfaces: the subset visible to anyone with the URL, and the subset visible only after logging in. A logged-out visitor to typically sees the person's name, headline, current company and title, general location, and any posts they've made public β the same information a search engine can index. Everything past that point, including full connection lists, private messages, and most detailed history sections, sits behind LinkedIn's authentication wall.
This guide only covers the public surface. It shows a working Python approach for extracting that public data, explains why the compliance decision underneath the technical approach matters more than the code itself, and is explicit about where the line sits between "possible" and "permitted."
Use cases for public LinkedIn profile data
Recruiters, sales teams, and researchers pull public LinkedIn data for a narrow set of recurring jobs, and each one has a different tolerance for risk and scale.
Recruiting pipeline enrichment β attaching a candidate's current title and public headline to an existing applicant-tracking record, one lookup at a time rather than a bulk crawl.
Company org-chart research β confirming who currently holds a role at a target account before a sales call, using individual public profile checks.
Academic and market research on public career trends β aggregating anonymized, publicly-visible role titles and industries across a sample, without retaining names.
Brand and mention monitoring β checking whether a public post references a company or product, similar to monitoring any other public web page.
None of these use cases require touching a connections list, an inbox, or any field gated behind login β and none of them justify running an unbounded, automated crawl across LinkedIn's entire member base. The same targeted, single-page pattern shows up in scraping public Amazon product data: pull one page at a time, on a defined schedule, rather than treating the whole site as one crawl target.
Is it legal to scrape LinkedIn profiles?
LinkedIn's own User Agreement prohibits automated data collection from the platform, and violating that agreement risks account suspension for the account involved and, in some cases, legal action from LinkedIn against the operator. That prohibition applies regardless of whether the data being collected is technically public β "public" describes who can view the data, not whether LinkedIn has consented to automated collection of it.
The case most often cited on this question is hiQ Labs v. LinkedIn, and its actual outcome is narrower than most summaries suggest. The Ninth Circuit ruled in 2019, and again in 2022 after a 2021 Supreme Court remand tied to Van Buren v. United States, that scraping data that's publicly accessible without logging in does not violate the federal Computer Fraud and Abuse Act (CFAA) β LinkedIn being a private company doesn't give it standing to invoke a federal anti-hacking statute against a scraper that never bypassed any access control. That precedent still stands, and it was reinforced by a separate 2024 federal ruling in a different scraping dispute that again rejected a CFAA theory against a scraper collecting publicly accessible data.
But the same hiQ litigation did not end well for hiQ overall. In November 2022, the same district court found that hiQ had separately breached LinkedIn's User Agreement by scraping and by directing contractors to create accounts for the work β a contract claim, not a CFAA claim. The case closed in December 2022 with a stipulated permanent injunction against hiQ, not a trial verdict in hiQ's favor. The practical lesson: surviving a CFAA challenge and surviving a breach-of-contract claim are two separate legal questions, and LinkedIn has won on the second one against the same plaintiff that won the first.
Three practical rules follow from that:
Only collect data visible without logging in, and never fields gated behind LinkedIn's authentication (connections, messages, full profile sections shown only to logged-in viewers).
Treat GDPR and CCPA as applicable the moment a scraped record identifies a real person. That means having a documented lawful basis for holding the data, minimizing what's stored to what the use case actually needs, and not building an unsolicited contact-outreach database purely from scraped names and emails.
Get legal counsel involved before any commercial-scale collection effort, since the contract-breach exposure demonstrated in hiQ's case exists independently of whatever the CFAA allows.
This guide does not cover, and will not cover, bypassing LinkedIn's login wall, defeating CAPTCHAs, automating fake accounts, or any technique aimed at evading LinkedIn's bot detection at scale β those cross from "collecting public data" into "circumventing access controls," which is both against LinkedIn's terms and a materially different, riskier legal position than the public-data question above.
Approach and tool fit
The workflow below has three moving parts: fetch a public profile page, parse its embedded structured data instead of brittle CSS selectors, and manage requests at a rate and from an IP reputation that doesn't get the fetch blocked before it starts.
Public LinkedIn profile pages served to a logged-out visitor include a JSON-LD <script type="application/ld+json"> block with a Person schema β name, job title, and affiliation in a structured format designed for search engines to read. Parsing that block is more durable than parsing visual HTML classes, which LinkedIn changes without notice and which vary by locale and by whether a page is server-rendered or hydrated client-side.
The remaining variable is the request itself. A single occasional lookup from a residential connection rarely raises any flag. A sustained run of many lookups per minute from one data-center IP is the pattern LinkedIn's automated-traffic detection is built to catch, independent of what data is being requested. This is where proxy infrastructure fits: not as a way past authentication, but as a way to keep a modest, spaced-out request pattern looking like the ordinary residential traffic it's supposed to resemble, so retries and pagination behave predictably instead of failing halfway through a batch.
Nstproxy's Residential Lite Proxies are built for exactly that kind of steady, moderate-volume collection job. The line draws from a pool of 50M+ real residential IPs across 200+ countries and regions, billed on prepaid packages rather than a subscription that auto-renews, which suits a workload that runs in bursts rather than continuously. For this tutorial's use case specifically:
Residential IP pool β requests route through real consumer ISP connections rather than data-center ranges, which matches the traffic pattern a legitimate, low-volume public-profile lookup is expected to have.
Broad country coverage β useful when the profiles being checked belong to people in different regions and a locally-plausible request origin matters for consistent page rendering.
Prepaid, pay-as-you-go billing β fits a bounded, occasional lookup job better than a fixed monthly commitment sized for continuous crawling.
Teams that outgrow a hand-rolled requests/BeautifulSoup script β because they need JavaScript rendering, retries, and structured output (Markdown, JSON, or screenshots) bundled behind one API instead of maintained in-house β can look at Nstproxy Crawl as a separate option; see its API documentation for the request/response shape. It is not the primary recommendation for the DIY approach in this tutorial, but it solves the same "fetch reliably without building the plumbing yourself" problem for larger extraction pipelines in general, on any public page, not specifically LinkedIn.
Keep public profile lookups reliable
Route occasional, low-volume public-data requests through real residential IPs across 200+ regions, so retries and pagination don't stall on a flagged data-center address.
Python 3.9 or later installed and on the system path.
A list of specific, individual public profile URLs to check β this walkthrough is built for targeted lookups, not an open-ended crawl of LinkedIn's member base.
A proxy or IP-rotation plan if the lookup volume goes beyond a handful of manual checks, since a burst of requests from one IP is the pattern most likely to get rate-limited β see how to use a proxy with BeautifulSoup for a closer look at wiring proxy rotation into this exact parsing stack.
Awareness that the code below has not been run live against a real LinkedIn profile at any volume for this article. LinkedIn's bot detection and ToS make that kind of test run both unreliable and non-compliant to perform just to generate output for a blog post β the code is illustrative, built against the same JSON-LD structured-data pattern documented in the schema.org Person specification and used by public search engines, and should be validated against a single profile you're authorized to check before any wider use.
Install the Python environment
Create an isolated environment and install the two libraries this walkthrough needs. Playwright is optional and only required for the JavaScript-rendered fallback in Step 3.
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activatepip install requests beautifulsoup4 playwright
python -m playwright install chromium # only needed for Step 3
Verification status: config-only β standard pip/venv syntax, not a claim specific to LinkedIn.
Step 1: Fetch a public profile page
Send a single GET request to the public profile URL with a realistic browser User-Agent header. Do not attempt to log in or attach any session cookie β this step only works on the logged-out, publicly-served version of the page.
import requests
PROFILE_URL ="https://www.linkedin.com/in/example-public-profile"headers ={"User-Agent":("Mozilla/5.0 (Windows NT 10.0; Win64; x64) ""AppleWebKit/537.36 (KHTML, like Gecko) ""Chrome/128.0.0.0 Safari/537.36"),"Accept-Language":"en-US,en;q=0.9",}# Route through a residential proxy for anything beyond a single manual check.proxies ={"http":"http://USERNAME:PASSWORD@gate.nstproxy.com:PORT","https":"http://USERNAME:PASSWORD@gate.nstproxy.com:PORT",}response = requests.get(PROFILE_URL, headers=headers, proxies=proxies, timeout=15)response.raise_for_status()html = response.text
Verification status: illustrative β the requests API shown (requests.get, headers, proxies, timeout, raise_for_status) matches the current official requests documentation, but this snippet was not executed against a live LinkedIn URL for this article; see Prerequisites.
Step 2: Parse the embedded structured data
LinkedIn's public profile pages, like most pages built for search-engine indexing, include a <script type="application/ld+json"> block describing the person using schema.org's Person vocabulary. Parsing that block is more stable across markup changes than targeting visual CSS classes.
import json
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,"html.parser")profile_data ={}for script_tag in soup.find_all("script",type="application/ld+json"):try: payload = json.loads(script_tag.string or"{}")except json.JSONDecodeError:continueif payload.get("@type")=="Person": profile_data ={"name": payload.get("name"),"headline": payload.get("description"),"location":(payload.get("address")or{}).get("addressLocality"),"current_role":(payload.get("worksFor")or{}).get("name"),}breakprint(profile_data)# Example illustrative output:# {'name': 'Jordan Example', 'headline': 'Senior Data Analyst at Example Corp',# 'location': 'Austin, Texas', 'current_role': 'Example Corp'}
Verification status: illustrative β Person schema field names follow the schema.org specification and the general pattern documented across current LinkedIn-scraping references reviewed for this article; sample values are placeholders, not scraped from a real profile.
Step 3: Handle JavaScript-rendered sections with Playwright
Some sections of a public profile (older posts, some detail panels) load after the initial HTML response through client-side JavaScript. When the JSON-LD block doesn't carry a field you need, render the page with a headless browser instead of adding more request headers to a plain HTTP client β see scraping JavaScript-rendered websites for a broader treatment of when this step is necessary versus optional.
Verification status: illustrative β sync_playwright, chromium.launch, page.goto, and page.content match Playwright's current official Python API documentation; no live browser session was run against LinkedIn for this article, per the disclosed prerequisite gap.
Output schema
Normalize whatever fields you extract into one consistent record shape before storing anything, so downstream code doesn't have to branch on which step produced a given field.
Field
Type
Source
Notes
name
string
JSON-LD Person.name
Public display name only
headline
string
JSON-LD Person.description
The one-line title shown under the name
location
string
JSON-LD Person.address.addressLocality
General city/region, not a precise address
current_role
string
JSON-LD Person.worksFor.name
Current employer name if publicly listed
profile_url
string
The URL requested
Store for deduplication and audit trail
fetched_at
ISO 8601 timestamp
Set at request time
Needed to enforce a retention/expiry policy
Do not add fields that only exist behind the login wall β if a field isn't present in the logged-out HTML or the JSON-LD block, it isn't part of this public-data workflow.
Observations and limits
The JSON-LD block doesn't carry every field a logged-in view shows. Full work-history lists, skills endorsements, and recommendations are typically incomplete or absent from the public, logged-out page.
Markup and JSON-LD field availability can change without notice. Treat every field extraction as something to re-verify periodically rather than a permanent contract.
A single flagged IP or an unusually fast request pattern can trigger a temporary block on that IP, independent of whether the request targeted public or private data β this is a reliability constraint to plan around, not a security control to defeat.
This workflow does not return connections, messages, or any authenticated-only field, by design β extending it to do so would require logging in, which moves the activity outside the scope this article covers and outside LinkedIn's permitted use.
Bulk, unbounded collection of names and titles is a separate risk from the scraping method itself. Even correctly-scraped public data can create GDPR/CCPA exposure once it's aggregated into a searchable database of identifiable people.
Conclusion
Scraping public LinkedIn profile data is technically straightforward: fetch the logged-out page, parse its structured Person data, and manage request volume the way any responsible scraper manages request volume against any site. The harder part is staying inside the boundary LinkedIn's terms and current case law actually draw β public fields only, no login bypass, and a documented lawful basis before that data becomes a stored, searchable dataset of real people. Build the technical piece from the steps above β and for structuring this into a larger, maintained codebase rather than a one-off script, see building a Python web scraping project as a full pipeline β and treat the compliance piece as a gate the project passes through before it scales, not an afterthought bolted on once a scraper already works.
Scraping data that's visible without logging in doesn't by itself violate the federal Computer Fraud and Abuse Act, per the Ninth Circuit's rulings in hiQ Labs v. LinkedIn, but it does violate LinkedIn's User Agreement, and LinkedIn separately won a breach-of-contract finding against hiQ in the same litigation. Treat "not a CFAA violation" and "permitted by LinkedIn's terms" as two different questions with two different answers.
Q: Do I need to log in to run this tutorial's code?
No β every step in this guide operates on the page LinkedIn serves to a logged-out visitor, and none of the code attaches a session cookie or credential. Logging in to scrape additional fields moves outside what this guide covers and outside LinkedIn's permitted use.
Q: Can I collect connections or messages this way?
No. Connections, messages, and most detailed profile sections are only shown to authenticated viewers and are not present in the logged-out HTML or its JSON-LD block, so this workflow cannot and does not retrieve them.
Q: Will LinkedIn block my IP if I run this at scale?
Running many requests quickly from one IP is the pattern most likely to trigger a temporary block, independent of whether the requested data is public. Spacing out requests and rotating through a residential IP pool reduces that risk as a matter of traffic pattern, not as a way to defeat any access control.
Q: How stable is the JSON-LD field extraction in Step 2?
LinkedIn can change page markup and structured-data fields without notice, so treat every field mapping as something to re-check periodically rather than a permanent schema, and expect to update the parsing logic when a field goes missing.
Q: Does GDPR or CCPA apply to scraped LinkedIn data?
Yes, the moment a scraped record identifies a real, living person, standard data-protection rules apply the same way they would to personal data collected through any other method, including having a lawful basis to hold it and a defined retention period.
Q: Is an official LinkedIn API a better option than scraping?
For most use cases, yes where access is available β LinkedIn's official APIs are heavily gated to approved partners for most data types, so many teams outside that partner program are left choosing between scraping only the public surface (this guide's scope) or not collecting the data at all; there is no general-purpose public API that returns full profile data to arbitrary developers.
Q: What should I do before scraping at commercial scale?
Have legal counsel review the specific data being collected, the jurisdictions of the people involved, and the intended use, since the contract-breach exposure demonstrated in hiQ's case exists independently of whatever the CFAA permits, and GDPR/CCPA add their own separate requirements once the dataset is large enough to be a real compliance surface.
How to Scrape LinkedIn Profiles in 2026: Step-by-Step Guide
Scrape public LinkedIn profile data with Python the compliant way: what's legal, what hiQ v. LinkedIn really decided, and working requests/BeautifulSoup code for name, headline, location, and current role β no login, no connections data, no bypassing LinkedIn's bot detection.
Kai Watanabe
Sep. 7th 2026
110M+ real IPs with 99.9% access success
Blazing-fast average response ~0.5s for high-concurrency tasks
From only $0.1/GB
Get immediate access to premium residential, datacenter, IPv6 and ISP proxy pools.