How to Scrape LinkedIn: A Python Guide (Tested)
- LinkedIn's guest jobs endpoint returns clean, parseable HTML with no login. I pulled 10 real job listings per page with plain requests and pasted the output below.
- Member profiles return a gated teaser only: name and headline are public, but experience, connections, and posts sit behind a signup wall.
- Scraping public LinkedIn data likely does not violate the CFAA (hiQ v. LinkedIn, 9th Cir., 2022), but it does breach LinkedIn's User Agreement. Those are different risks.
- For profiles, company pages, or jobs at volume, a scraper API like ChocoData handles the JS rendering and IP rotation that plain requests cannot.
LinkedIn is the richest source of professional data on the web: jobs, company headcounts, hiring signals, profiles. It is also one of the hardest targets to scrape, because most of that data sits behind a signup wall and the User Agreement bans automated access outright. I spent June 2026 testing what a plain Python scraper can actually pull from a logged-out session. The short version: the public jobs endpoint works and I have the real output below, while profiles hand you a teaser and nothing more. This guide shows exactly where the line sits, with tested code, and where a scraper API takes over.
Can you legally scrape LinkedIn?
Scraping public LinkedIn data sits in two separate legal buckets you have to weigh independently: US computer-crime law, where the risk is now low, and LinkedIn’s own User Agreement, which it breaches outright. The landmark case is hiQ Labs v. LinkedIn. On April 18, 2022, the Ninth Circuit ruled that scraping publicly available profiles likely does not violate the Computer Fraud and Abuse Act, because the CFAA’s “without authorization” bar does not apply to data that is open to the public without a login. The case was later terminated in December 2022.
That ruling is about a criminal statute. It says nothing about your contract with LinkedIn. LinkedIn’s User Agreement section 8.2 prohibits scraping in plain language. Three clauses matter:
| Clause | What it prohibits (verbatim) |
|---|---|
| 8.2(2) | “Develop, support or use software, devices, scripts, robots or any other means or processes (such as crawlers, browser plugins and add-ons or any other technology) to scrape or copy the Services” |
| 8.2(4) | “Copy, use, display or distribute any information (including content) obtained from the Services… without the consent of the content owner” |
| 8.2(13) | “Use bots or other unauthorized automated methods to access the Services… or otherwise drive inauthentic engagement” |
So the practical picture has three layers. CFAA risk on public data is low after hiQ. Contract risk is real: scraping breaches the User Agreement, and LinkedIn can ban accounts or sue for breach. Data-protection risk is separate again: profiles are personal data, so GDPR and similar laws apply the moment you store EU residents’ information. I cover the general framing in my is web scraping legal guide. For LinkedIn specifically, the safest posture is to scrape only public, non-personal data (job listings, not people), and never log in a bot account.
What data can you actually get from LinkedIn?
You can reliably get public job listings; profile and company data come back as a gated preview only. I tested each surface from a logged-out session in June 2026. Here is what each one returns:
| Surface | Logged-out access | What you get | Notes |
|---|---|---|---|
| Jobs search (guest API) | Yes, returns 200 | Title, company, location, posting URL, date | 10 cards per request, paginated |
| Jobs search (HTML page) | Yes, returns 200 | Same fields, ~25 cards in initial HTML | Title showed “17,000+ Python Developer jobs” |
| Member profile | Teaser only | Name, headline, one-line summary | Experience, connections, posts gated |
| Company page | Teaser only | Name, tagline, basic about | Employee lists, updates gated |
| Feed / posts | No | Nothing | Full signup wall |
The takeaway is to build around jobs, not people. Job postings are public by design because LinkedIn wants them indexed and shared. Profiles are the opposite: LinkedIn gives search engines a thin preview and gates everything else. The next two sections show the jobs scraper I tested, then the profile wall in detail.
How do you scrape LinkedIn jobs with Python? (Method 1: DIY)
Hit LinkedIn’s guest jobs API and parse the returned HTML cards with BeautifulSoup. LinkedIn’s logged-out jobs page loads its results from https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search, and that endpoint returns a plain list of job cards, 10 per request, with no authentication. This is the surface I tested. Here is the exact code I ran:
import requests
from bs4 import BeautifulSoup
UA = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/126.0.0.0 Safari/537.36"
)
}
url = (
"https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search"
"?keywords=python%20developer&location=United%20States&start=0"
)
r = requests.get(url, headers=UA, timeout=20)
print("STATUS:", r.status_code)
soup = BeautifulSoup(r.text, "html.parser")
cards = soup.select("div.base-card")
print("cards on page:", len(cards))
print()
for c in cards[:5]:
title = c.select_one("h3.base-search-card__title")
company = c.select_one("h4.base-search-card__subtitle")
location = c.select_one("span.job-search-card__location")
print(
title.get_text(strip=True), "|",
company.get_text(strip=True), "|",
location.get_text(strip=True),
)
The real output, run June 2026:
STATUS: 200
cards on page: 10
Python Software Engineer 2 | Garmin | Olathe, KS
Python Developer | LTIMindtree | Charlotte, NC
Software Engineer L5, Python Platform | Netflix | United States
Junior Full Stack Engineer ($250k - $300k salary) | Baton Corporation | New York, NY
Python Developer | VBeyond Corporation | United States
That is genuine data with no login and no proxy. A few things to know. Each request returns 10 cards; increment the start parameter by 10 (&start=10, &start=20) to page through results. The posting URL lives on the a.base-card__full-link element’s href, and the posting date is in a time tag’s datetime attribute. The keywords and location parameters take URL-encoded strings, so python%20developer is python developer.
I also probed the endpoint for rate limiting by sending eight requests back to back. All eight returned 200, so this surface is fairly tolerant at low volume. That tolerance ends if you scrape thousands of pages from one IP. If you are new to this stack, my BeautifulSoup guide covers selectors and .get_text() in depth, and the CSS selectors guide explains how to target elements like div.base-card reliably.
Why can’t you scrape LinkedIn profiles the same way?
Profiles return a public teaser, not the full record, so a plain GET request gives you a name and headline and nothing useful past that. I fetched a well-known public profile (linkedin.com/in/williamhgates) from a logged-out session to see exactly what comes back. The request returned a 200 with a large HTML body, and the page title and Open Graph description carried the public preview:
TITLE: Bill Gates - Chair, Gates Foundation and Founder, Breakthrough Energy | LinkedIn
og:description: Chair, Gates Foundation and Founder, Breakthrough Energy ...
Experience: Gates Foundation ... Education: Harvard University ...
Location: Seattle ... View Bill Gates' profile on LinkedIn ...
So the name, current headline, and a one-line summary are public. Everything substantive past that is gated: the page is peppered with “join to view” prompts where the full experience history, connection count, posts, and skills would be. The detailed data is not hidden in the HTML waiting to be parsed; it is not served to a logged-out client at all.
This is the wall the hiQ case was fought over, and it is why LinkedIn profile scraping at any depth means one of two things. Either you log in a bot account, which breaches User Agreement section 8.2 head-on and is the fastest way to get an account restricted, or you use a service that renders the page through a real, authorized session and rotates IPs. Plain requests against profile URLs will only ever give you the teaser. The same gating applies to company pages: name and tagline are public, employee lists and updates are not.
How do you scrape LinkedIn at scale? (Method 2: scraper API)
Route the request through a scraper API so it handles IP rotation, JavaScript rendering, and retries, then you parse the JSON or HTML it returns. This is the practical answer for three jobs that defeat plain requests: pulling job listings by the thousand without tripping throttles, fetching the full job-description pages, and getting any profile or company data past the teaser. A scraper API maintains the proxy pool and session handling so you get a usable response instead of a block.
ChocoData is one such service, with a universal endpoint plus a set of dedicated endpoints. Its request shape is a single GET with your target URL and an API key. Here is the pattern for sending a LinkedIn jobs URL through the universal endpoint with JavaScript rendering on:
import requests
API_KEY = "YOUR_CHOCODATA_KEY"
target = (
"https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search"
"?keywords=python%20developer&location=United%20States&start=0"
)
r = requests.get(
"https://api.chocodata.com/api/v1/universal",
params={
"api_key": API_KEY,
"url": target,
"render_js": "true",
},
timeout=60,
)
print("STATUS:", r.status_code)
# r.text holds the rendered HTML; parse the job cards exactly
# as in Method 1 with BeautifulSoup.
I did not run this snippet (it needs a paid key), so treat the field names as illustrative and confirm the exact parameters against ChocoData’s own docs before you rely on them. The principle holds across any scraper API: you hand it the URL, it returns the page from a rotating residential IP with optional JS rendering, and the throttling problem becomes the vendor’s problem instead of yours. ChocoData advertises residential proxy rotation and optional JavaScript rendering, which are the two features that matter for LinkedIn’s gated, JS-heavy pages.
The honest framing: a scraper API removes the technical block, not the legal one. Routing through proxies does not grant you consent under the User Agreement, and it does not exempt you from GDPR if you collect personal profiles. Use this approach for public, non-personal data such as job listings, and apply the same restraint you would to any scraping that needs to avoid getting blocked.
Should you use a scraper API or build your own LinkedIn scraper?
Build your own for public jobs at low volume; reach for a scraper API for profiles, company data, or jobs by the thousand. The decision comes down to which surface you need and how much of it. Here is how the two approaches line up for LinkedIn specifically:
| Factor | DIY (requests + BeautifulSoup) | Scraper API |
|---|---|---|
| Public jobs, low volume | Works, tested above | Works, overkill |
| Jobs at scale (1000s) | Throttled from one IP | Handled via IP rotation |
| Profile data past teaser | Not accessible | Accessible via rendering |
| JavaScript-heavy pages | No rendering | Optional JS rendering |
| Cost | Free | Paid per request |
| Maintenance | You fix broken selectors | Vendor maintains |
| User Agreement breach | Yes | Yes |
The pattern I would follow: start with the DIY jobs scraper from Method 1, because it is free and it genuinely works for public listings. Add a scraper API the moment you need profile depth, full job descriptions, or volume that one IP cannot sustain. Both methods breach LinkedIn’s User Agreement, so neither is a free pass; scrape only public, non-personal data and you keep the risk where the courts have been most forgiving.
For the fundamentals under all of this, including headers, pagination, and parsing, see the web scraping guide. If you want to build the same jobs scraper inside a larger crawl framework, the Scrapy guide shows how, and the Python scraping guide covers the requests-and-parsing stack end to end. If jobs data is your actual goal, my web scraping jobs walkthrough goes deeper on parsing listings across sites.
FAQ
Scraping publicly visible LinkedIn data likely does not violate the US Computer Fraud and Abuse Act, per the Ninth Circuit in hiQ v. LinkedIn (2022). That is a criminal-statute question. Separately, LinkedIn's User Agreement (section 8.2) flatly prohibits scraping, so doing it breaches your contract with LinkedIn and can get your account banned or trigger a civil suit. Personal-data laws like GDPR add a third layer if you collect EU residents' profiles.
Partly. The logged-out jobs search and its guest API return real job listings you can parse. Member profiles show only a public preview (name, headline, a one-line summary) and gate the rest behind a signup wall. Company pages behave the same way. Anything past the teaser needs either a logged-in session, which violates the User Agreement more directly, or a scraper API.
Yes, aggressively, on profile and feed pages. Logged-out profiles return a gated teaser, and a logged-in account that requests too fast hits the 'commercial use limit' or gets restricted. The public jobs guest endpoint is the most tolerant surface: in my test, eight rapid requests all returned 200. Push any endpoint hard from one IP and you will see throttling or blocks.
Use the guest jobs API at linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search. It returns 10 job cards of plain HTML per request with a start offset for pagination, no login required. Parse the cards with BeautifulSoup. For thousands of listings or for the full job description pages, add a scraper API to rotate IPs and avoid throttling.