LinkedIn Scraper in Python: Libraries, Code, and What Each Option Costs
Most guides to building a LinkedIn scraper in Python compare the routes on how much code you write. That is the smaller half of the decision. The larger half is what runs your LinkedIn account while the code executes, because the routes differ far more there than they do in what they can extract.
This guide covers all three practical routes, with the same extraction written twice so you can compare them directly, and with the current release and repository status of the libraries people usually recommend.
One thing to know before you start reading around: a good share of the Python LinkedIn scraping guides you will find are published by companies that sell proxy and unblocking infrastructure – ScraperAPI, Scrapfly and Bright Data among them. They cover the routes that need that infrastructure well. The route that runs through an authenticated account is the one that tends to be missing, which is why it gets equal space here.
The short version. The Python routes differ less in what they extract than in what runs your LinkedIn account while they work, and that is what decides whether your account survives the project. Three practical routes. Linked API's authenticated account API drives a browser you signed into yourself, at human pace, under per-action limits you set, and returns structured data with no HTML parsing, no selector upkeep and no proxy pool. An open-source library is free and fastest to start, but it runs an automated browser on a session file you exported – or, in the Voyager case, calls LinkedIn's private endpoints directly – and you inherit its maintenance: the most popular scraper library rewrote its entire API in its last major version, and the widely referenced Voyager library has not shipped a release since November 2024. A DIY browser script gives maximum control and maximum upkeep, with pacing, session handling and infrastructure all yours to build. No route is risk-free for your account, so the practical question is which one you can run at a pace your account tolerates.
The Python options for scraping LinkedIn, compared
| Route | What it is | What LinkedIn's servers see | What it costs you | Best for |
|---|---|---|---|---|
| Authenticated account API (Linked API) | Hosted browser per account, structured responses | A browser you signed into yourself, acting at human pace under configured limits | Subscription; account-paced | Production pipelines that must keep running |
linkedin-scraper | Playwright library for profiles, companies, jobs | An automated browser driving authenticated state you exported | Free; you follow its releases and API changes | Getting something working today |
linkedin-api | Client for LinkedIn's internal Voyager endpoints | Direct requests to private, undocumented endpoints | Free; no release since November 2024 | Reading the approach, not shipping on it |
| DIY Playwright / Selenium | Your own browser automation | Same as above, with every detail your responsibility | Free; ongoing selector and infrastructure work | Full control, unusual requirements |
| Scrapy | HTTP crawling framework, no browser | Plain HTTP requests; signed-out unless you build session handling | Free; auth and blocking are yours to solve | Crawling at scale beyond LinkedIn |
| Proxy / unblocker products | Request infrastructure for public pages | Signed-out public-page requests spread across many addresses | Metered; a different dataset | Bulk signed-out collection |
What LinkedIn's servers see from each route
This is the part most comparisons skip, and it is the one that decides whether a scraper survives contact with production. What follows describes what each route provides architecturally. Nobody outside LinkedIn can tell you what its systems flag, and this guide does not try.
Authenticated account API (Linked API)
Each connected account runs in its own dedicated cloud browser on LinkedIn's real pages. You sign in yourself inside that browser, and Linked API never sees or stores your password. The browser keeps a stable residential IP from your region and a consistent device profile, and actions run at human pace – a simple visit-and-like takes around 20 seconds by design. You configure per-action daily, weekly and monthly limits, and the platform enforces them: an action that would cross one returns a limitExceeded error instead of executing. The safety model covers the architecture, and the limits guide covers sensible values.
linkedin-scraper, and any DIY Playwright or Selenium script
What this route gives you is reusable authenticated state. The library's own quick start calls browser.load_session("session.json"), which means a credential-equivalent artifact lives in your filesystem – and, if you are not careful, in your repository. What it does not provide is an account-bound stable device and network identity, or enforced pacing. Beyond that, the footprint depends entirely on your runtime: a script running on your own machine may well use the same region and network the account normally signs in from, and the session itself can be created by logging in manually inside that browser.
linkedin-api
This route is wire-level. In the library's own words, "To retrieve structured data, the LinkedIn Website uses a service they call Voyager. Voyager endpoints give us access to pretty much everything we could want from LinkedIn: profiles, companies, connections, messages, etc." Those endpoints are private and undocumented, which means there is no public compatibility contract behind them. If LinkedIn changes an endpoint, a client that has not adapted can keep sending an outdated request shape, and that shape is potentially distinctive on the server side. Read that alongside the release history below: nothing has shipped since November 2024.
Scrapy and plain HTTP
No browser is involved at all. Authenticated pages require session handling you build and maintain yourself, and without a session you receive only what LinkedIn shows signed-out, which is a much thinner page than the one you see logged in.
Proxy and unblocker products
These distribute signed-out public-page requests across many IP addresses. That is a different architecture solving a different problem, so it is not a like-for-like comparison with account-based throughput – there is no account in the picture at all.
No route is risk-free
Every route on this page, including ours, can end in a restricted account. Architecture changes the shape of the footprint your account leaves, not whether a footprint exists. Our guide to scraping LinkedIn covers how restrictions actually happen, and the guide to a restricted LinkedIn account covers what to do if you hit one.
The open-source libraries: current release and repository status
Both libraries below are recommended constantly in tutorials and forum threads. Their current state is worth checking before you build on either, and it is easy to check from primary sources.
linkedin-scraper
Actively maintained. The current release on PyPI is 3.1.2, published 10 April 2026, with several releases earlier that year. Its GitHub repository has around 4,369 stars and 964 forks and was created in 2017. It describes itself as an "Async LinkedIn scraper built with Playwright for extracting profile, company, and job data from LinkedIn."
The cost you inherit is API churn, and the project states it plainly in its own README: "Version 3.0.0 introduces breaking changes and is NOT backwards compatible with previous versions." That release replaced Selenium with Playwright in a complete rewrite, moved everything to async/await, restructured the package, and switched to Pydantic data models. Code written against 2.x does not run on 3.x; the migration path offered is to pin the old version with pip install linkedin-scraper==2.11.2.
That is healthy maintenance, not a defect – a scraper that never changes is a scraper that has stopped tracking its target. But it is work that lands on you rather than on a vendor.
One detail to check before embedding it in a commercial product: the LICENSE file in the repository is the GNU General Public License v3, while the PyPI metadata and the README badge both say Apache 2.0. Those licenses have very different implications for closed-source distribution, so confirm which applies to your use before shipping.
linkedin-api
The current release on PyPI is 2.3.1, published 7 November 2024, and nothing has shipped since. It requires Python 3.10 or newer, and its PyPI metadata lists MIT.
Its linked sources are, at the time of writing, unreachable. Under PyPI's "Unverified details" the project lists a GitHub URL as both Homepage and Repository, and a ReadTheDocs URL as Documentation. Both return HTTP 404. The tomquirk GitHub account itself resolves normally, so this is specific to the repository rather than the account, and a control request to another LinkedIn scraping repository returned normally, so it is not a rate-limiting artifact.
We report that as observed state and draw no conclusion about why. What it means practically is straightforward: the package is still installable, its source and documentation are not currently reachable at the addresses it publishes, and the wire-level approach described above has had no shipped adaptation for well over a year.
Rolling your own with Playwright
Here is the task both of the next two blocks solve, so you can compare them line for line: given one LinkedIn profile URL, return the person's name, headline, and location.
The DIY version launches a browser, loads a session you saved earlier, opens the page, and reads three fields:
import asyncio
from playwright.async_api import async_playwright
async def scrape_profile(url: str) -> dict:
async with async_playwright() as p:
browser = await p.chromium.launch()
# storage_state carries the logged-in session you exported earlier
context = await browser.new_context(storage_state="session.json")
page = await context.new_page()
await page.goto(url, wait_until="domcontentloaded")
name = await page.locator("main h1").first.inner_text()
headline = await page.locator("main div.text-body-medium").first.inner_text()
location = await page.locator("main span.text-body-small").first.inner_text()
await browser.close()
return {
"name": name.strip(),
"headline": headline.strip(),
"location": location.strip(),
}
print(asyncio.run(scrape_profile("https://www.linkedin.com/in/john-doe")))Three lines in that snippet are the whole maintenance story. The selectors are illustrative and will not match forever – LinkedIn's class names are generated and its profile layout varies by account type, by whether sections are present, and by which experiment a given page is in. storage_state="session.json" is a file holding your authenticated session. And nothing in the script paces anything: it runs as fast as the event loop allows.
Scaling this means solving, in order: selector drift, layout variants, pagination, session expiry, retry logic, and eventually request infrastructure. That work never finishes, which is the honest argument for and against the route – it is entirely yours to control, and entirely yours to maintain.
The same extraction through an authenticated account API
The same task, through a connected account. Install the SDK and initialize it with your tokens:
pip install linkedapifrom linkedapi import LinkedApi, LinkedApiConfig, FetchPersonParams
linkedapi = LinkedApi(
LinkedApiConfig(
linked_api_token="your-linked-api-token",
identification_token="your-identification-token",
)
)
workflow = linkedapi.fetch_person.execute(
FetchPersonParams(person_url="https://www.linkedin.com/in/john-doe")
)
# result() waits for the workflow to finish
result = linkedapi.fetch_person.result(workflow.workflow_id)
for error in result.errors or []:
print("workflow error:", error.type, error.message)
if result.data:
print({
"name": result.data.name,
"headline": result.data.headline,
"location": result.data.location,
})No selectors, no session file, no browser to launch. fetch_person takes flags for the heavier parts of a profile – experience, education, skills, languages, recent posts – and returns them as typed fields rather than text you parse; the SDK reference has the full parameter list, and people search covers finding profiles rather than reading a known one.
Requests are not instant, and that is deliberate: the workflow runs in a real browser at human pace, so a simple read takes seconds and a heavy one can take minutes. What you get in exchange is that the pacing, the limits and the browser are handled rather than left to your code.
Two ways to use it, both first-class. You can build it in – the REST API, the Node and Python SDKs, or the shell CLI – and embed extraction directly in your own backend. Or you can skip the wiring: an AI agent can run the same workflows through the MCP server, the agent-friendly CLI in Claude Code, Cursor or Codex, or ready-made skills installed with npx @linkedapi/skills – building the extraction from a plain-language description instead of code.
On capacity. Activity capacity is account-bound: exceeding what one account can do means connecting additional accounts, on any route. Each account you connect to Linked API gets its own isolated cloud browser, its own identification token, and its own configurable limits.
For entity-specific extraction, the deeper guides carry working code: profiles, companies, jobs, posts, and Sales Navigator. If you want the API-product comparison rather than the language one, see LinkedIn scraper API.
When the DIY route is still the right call
Plenty of cases, and they are not consolation prizes:
- You want full control and no vendor dependency. Your scraper, your infrastructure, your roadmap. Nothing about it can be repriced or deprecated by someone else.
- You are collecting from sources beyond LinkedIn. If LinkedIn is one target among many, one scraping stack across all of them is simpler than one integration per source.
- You need raw speed on a single account. Linked API enforces sequential, human-paced execution, so a simple action takes around 20 seconds by design. A Playwright script, and especially a Voyager client issuing direct HTTP requests, leaves pace and concurrency entirely to your code, so its wall-clock rate on one account can be considerably higher. That speed is unmanaged, and the safety burden moves to you – but the raw number is real, and it is a different question from what volume is sustainable, which nobody here can quantify for you.
- You are collecting signed-out public pages in bulk. At high volume that genuinely bursts harder than any account-based route. It is a thinner dataset, and the legal picture around it is its own subject – our scraping guide covers it.
- You need a one-off dataset, once, with no subscription attached.
- You are learning. Writing the Playwright version teaches you more about how LinkedIn is built than any API call will.
Frequently Asked Questions (FAQ)
Yes, by three practical routes: an open-source library such as linkedin-scraper, your own browser automation with Playwright or Selenium, or an authenticated account API that returns structured data directly. They differ in how much code you maintain and in what runs your LinkedIn account while they execute. None of them is risk-free for the account doing the work.
It depends on the route you want, and the current state of each is worth checking before you commit. The linkedin-scraper library is actively maintained on Playwright, at version 3.1.2 as of April 2026, with a major API rewrite in 3.0.0 that broke compatibility with 2.x. The linkedin-api library takes the wire-level approach against LinkedIn's internal Voyager endpoints and has not shipped a release since November 2024, with its published repository and documentation URLs currently returning 404.
Account restriction is possible on every route, and nothing described here removes that risk. What triggers it, how LinkedIn's restrictions work, and what recovery looks like are covered in our guide to scraping LinkedIn and our guide to a restricted LinkedIn account.
Sometimes, but not reliably, and it is the wrong tool for the job. Members can publish an email or phone number in the Contact info section of their profile, and a logged-in route can read it when the visibility setting makes it visible to that account. But it is optional and visibility-controlled: per LinkedIn Help, the registered primary email is by default visible only to 1st-degree connections, with member-chosen settings ranging from "only visible to me" to anyone on LinkedIn. That makes LinkedIn an unreliable contact source, and consented enrichment providers are the right lane for it. Linked API result types carry no email or phone field.
At low volume from an ordinary connection, no. Proxies commonly become necessary infrastructure once you are collecting public pages at scale, which is what proxy and unblocker products are built for. Two things worth separating: that is signed-out collection, and it is not a way to make a single logged-in account go faster. An authenticated account API does not require you to run a proxy pool at all.
There is an official LinkedIn API, but not one that covers this use case. Its open access is Sign In with LinkedIn via OpenID Connect, which returns the profile of the member who just authenticated and consented – their own data, not other people's. Broader profile and community access sits behind restricted or vetted partner programs, as Microsoft's own Getting Access to LinkedIn APIs documentation sets out, and there is no general people-search or profile-lookup product available by self-service. That gap is why the routes in this guide exist.
If you are deciding between building and buying, the honest split is this: build when control matters more than upkeep, and buy when the extraction has to keep working while you do something else. Our account safety model covers how Linked API runs actions, and pricing is flat per seat.
Library versions, release dates, and repository status verified 2026-07-28.