Skip to main content

LinkedIn Scraper

PyPI version Python 3.8+ License

Async LinkedIn scraper built with Playwright for extracting profile, company, and job data from LinkedIn.

⚠️ Breaking Changes in v3.0.0

Version 3.0.0 introduces breaking changes and is NOT backwards compatible with previous versions.

What Changed:

  • Playwright instead of Selenium - Complete rewrite using Playwright for better performance and reliability
  • Async/await throughout - All methods are now async and require await
  • New package structure - Imports have changed (e.g., from linkedin_scraper import PersonScraper)
  • Updated data models - Using Pydantic models instead of simple objects
  • Different API - Method signatures and return types have changed

Migration Guide:

Before (v2.x with Selenium):

from linkedin_scraper import Person

person = Person("https://linkedin.com/in/username", driver=driver)
print(person.name)

After (v3.0+ with Playwright):

import asyncio
from linkedin_scraper import BrowserManager, PersonScraper

async def main():
    async with BrowserManager() as browser:
        await browser.load_session("session.json")
        scraper = PersonScraper(browser.page)
        person = await scraper.scrape("https://linkedin.com/in/username")
        print(person.name)

asyncio.run(main())

If you need the old Selenium-based version:

pip install linkedin-scraper==2.11.2

Quick Start (development)

git clone https://github.com/joeyism/linkedin_scraper.git
cd linkedin_scraper

# Install dependencies + Playwright browser
just install

# Create your LinkedIn session (manual login in browser)
just session

# Scrape a profile (slug or full URL)
just run-person your-linkedin-slug

# Scrape your feed
just run-feed

Run just with no arguments to list all available commands.



Features

  • Person Profiles - Scrape comprehensive profile information

    • Basic info (name, headline, location, about)
    • Work experience with details
    • Education history
    • Skills and accomplishments
  • Company Pages - Extract company information

    • Company overview and details
    • Industry and size
    • Headquarters location
  • Company Posts - Scrape posts from company pages

    • Post content and text
    • Reactions, comments, reposts counts
    • Posted date and images
  • Feed - Scrape your authenticated LinkedIn feed

    • Author name and profile URL
    • Post content, date, reactions, comments, reposts
    • Sponsored/promoted posts automatically filtered out
  • Job Listings - Scrape job postings

    • Job details and requirements
    • Company information
    • Application links
  • Async/Await - Modern async Python with Playwright

  • Type Safety - Full Pydantic models for all data

  • Progress Callbacks - Track scraping progress

  • Session Management - Reuse authenticated sessions

Installation

From PyPI:

pip install linkedin-playwright-scraper
playwright install chromium

From source (recommended for development):

# Requires uv and just
just install

Quick Start

Basic Usage

import asyncio
from linkedin_scraper import BrowserManager, PersonScraper

async def main():
    # Initialize browser
    async with BrowserManager(headless=False) as browser:
        # Load authenticated session
        await browser.load_session("session.json")
        
        # Create scraper
        scraper = PersonScraper(browser.page)
        
        # Scrape a profile
        person = await scraper.scrape("https://linkedin.com/in/williamhgates/")
        
        # Access data
        print(f"Name: {person.name}")
        print(f"Headline: {person.headline}")
        print(f"Location: {person.location}")
        print(f"Experiences: {len(person.experiences)}")
        print(f"Education: {len(person.educations)}")

asyncio.run(main())

Company Scraping

from linkedin_scraper import CompanyScraper

async def scrape_company():
    async with BrowserManager(headless=False) as browser:
        await browser.load_session("session.json")
        
        scraper = CompanyScraper(browser.page)
        company = await scraper.scrape("https://linkedin.com/company/microsoft/")
        
        print(f"Company: {company.name}")
        print(f"Industry: {company.industry}")
        print(f"Size: {company.company_size}")
        print(f"About: {company.about_us[:200]}...")

asyncio.run(scrape_company())

Job Scraping

from linkedin_scraper import JobSearchScraper

async def search_jobs():
    async with BrowserManager(headless=False) as browser:
        await browser.load_session("session.json")
        
        scraper = JobSearchScraper(browser.page)
        jobs = await scraper.search(
            keywords="Python Developer",
            location="San Francisco",
            limit=10
        )
        
        for job in jobs:
            print(f"{job.title} at {job.company}")
            print(f"Location: {job.location}")
            print(f"Link: {job.linkedin_url}")
            print("---")

asyncio.run(search_jobs())

Company Posts Scraping

from linkedin_scraper import BrowserManager, CompanyPostsScraper

async def scrape_company_posts():
    async with BrowserManager(headless=False) as browser:
        await browser.load_session("session.json")

        scraper = CompanyPostsScraper(browser.page)
        posts = await scraper.scrape(
            "https://linkedin.com/company/microsoft/",
            limit=10
        )

        for post in posts:
            print(f"Posted: {post.posted_date}")
            print(f"Text: {post.text[:200]}...")
            print(f"Reactions: {post.reactions_count}")
            print(f"Comments: {post.comments_count}")
            print(f"URL: {post.linkedin_url}")
            print("---")

asyncio.run(scrape_company_posts())

Feed Scraping

from linkedin_scraper import BrowserManager, FeedScraper

async def scrape_feed():
    async with BrowserManager(headless=False) as browser:
        await browser.load_session("session.json")

        scraper = FeedScraper(browser.page)
        posts = await scraper.scrape(limit=20)

        for post in posts:
            print(f"Author: {post.author_name} ({post.author_url})")
            print(f"Posted: {post.posted_date}")
            print(f"Text: {post.text[:200]}...")
            print(f"Reactions: {post.reactions_count}")
            print("---")

asyncio.run(scrape_feed())

Or via the command line:

just run-feed

Authentication

LinkedIn requires authentication. You need to create a session file first:

Option 1: Manual Login Script

from linkedin_scraper import BrowserManager, wait_for_manual_login

async def create_session():
    async with BrowserManager(headless=False) as browser:
        # Navigate to LinkedIn
        await browser.page.goto("https://www.linkedin.com/login")
        
        # Wait for manual login (opens browser)
        print("Please log in to LinkedIn...")
        await wait_for_manual_login(browser.page, timeout=300)
        
        # Save session
        await browser.save_session("session.json")
        print("✓ Session saved!")

asyncio.run(create_session())

Option 2: Programmatic Login

from linkedin_scraper import BrowserManager, login_with_credentials
import os

async def login():
    async with BrowserManager(headless=False) as browser:
        # Login with credentials
        await login_with_credentials(
            browser.page,
            username=os.getenv("LINKEDIN_EMAIL"),
            password=os.getenv("LINKEDIN_PASSWORD")
        )
        
        # Save session for reuse
        await browser.save_session("session.json")

asyncio.run(login())

Progress Tracking

Track scraping progress with callbacks:

from linkedin_scraper import ConsoleCallback, PersonScraper

async def scrape_with_progress():
    callback = ConsoleCallback()  # Prints progress to console
    
    async with BrowserManager(headless=False) as browser:
        await browser.load_session("session.json")
        
        scraper = PersonScraper(browser.page, callback=callback)
        person = await scraper.scrape("https://linkedin.com/in/williamhgates/")

asyncio.run(scrape_with_progress())

Custom Callbacks

from linkedin_scraper import ProgressCallback

class MyCallback(ProgressCallback):
    async def on_start(self, scraper_type: str, url: str):
        print(f"Starting {scraper_type} scraping: {url}")
    
    async def on_progress(self, message: str, percent: int):
        print(f"[{percent}%] {message}")
    
    async def on_complete(self, scraper_type: str, url: str):
        print(f"Completed {scraper_type}: {url}")
    
    async def on_error(self, error: Exception):
        print(f"Error: {error}")

Data Models

All scraped data is returned as Pydantic models:

Person

class Person(BaseModel):
    name: str
    headline: Optional[str]
    location: Optional[str]
    about: Optional[str]
    linkedin_url: str
    experiences: List[Experience]
    educations: List[Education]
    skills: List[str]
    accomplishments: Optional[Accomplishment]

Company

class Company(BaseModel):
    name: str
    industry: Optional[str]
    company_size: Optional[str]
    headquarters: Optional[str]
    founded: Optional[str]
    specialties: List[str]
    about: Optional[str]
    linkedin_url: str

Job

class Job(BaseModel):
    title: str
    company: str
    location: Optional[str]
    description: Optional[str]
    employment_type: Optional[str]
    seniority_level: Optional[str]
    linkedin_url: str

Post

class Post(BaseModel):
    linkedin_url: Optional[str]
    urn: Optional[str]
    author_name: Optional[str]
    author_url: Optional[str]
    text: Optional[str]
    posted_date: Optional[str]
    reactions_count: Optional[int]
    comments_count: Optional[int]
    reposts_count: Optional[int]
    image_urls: List[str]
    video_url: Optional[str]
    article_url: Optional[str]

Advanced Usage

Browser Configuration

browser = BrowserManager(
    headless=False,  # Show browser window
    slow_mo=100,     # Slow down operations (ms)
    viewport={"width": 1920, "height": 1080},
    user_agent="Custom User Agent"
)

Error Handling

from linkedin_scraper import (
    AuthenticationError,
    RateLimitError,
    ProfileNotFoundError
)

try:
    person = await scraper.scrape(url)
except AuthenticationError:
    print("Not logged in - session expired")
except RateLimitError:
    print("Rate limited by LinkedIn")
except ProfileNotFoundError:
    print("Profile not found or private")

Best Practices

  1. Rate Limiting - Add delays between requests

    import asyncio
    await asyncio.sleep(2)  # 2 second delay
    
  2. Session Reuse - Save and reuse sessions to avoid frequent logins

  3. Error Handling - Always handle exceptions (rate limits, auth errors, etc.)

  4. Headless Mode - Use headless=False during development, True for production

  5. Respect LinkedIn - Don't scrape aggressively, respect rate limits

Requirements

  • Python 3.9+
  • uv (package manager)
  • just (task runner)
  • Playwright (installed automatically via just install)

License

Apache License 2.0 - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Disclaimer

This tool is for educational purposes only. Make sure to comply with LinkedIn's Terms of Service and use responsibly. The authors are not responsible for any misuse of this tool.

Metadata

Release files for linkedin-playwright-scraper 4.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for linkedin-playwright-scraper 4.7.0
File Size Uploaded
linkedin_playwright_scraper-4.7.0.tar.gz 297.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for linkedin-playwright-scraper 4.7.0
File Interpreter ABI Platform
linkedin_playwright_scraper-4.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 403.4 kB

Release files / linkedin_playwright_scraper-4.7.0.tar.gz

Download URL linkedin_playwright_scraper-4.7.0.tar.gz
Size 297.4 kB
Tags Source
SHA-256 checksum
How to use checksums
810b6ecc7c7be3c83f69fca07d745a7d47b2f7869428543ccf22843e6006428e
BLAKE2b-256 checksum
How to use checksums
b67abaa979707d09dbabf293e4dd449a519dad5c289c530903805baf68e5fa6e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.

Transparency log

Release files / linkedin_playwright_scraper-4.7.0-py3-none-any.whl

Download URL linkedin_playwright_scraper-4.7.0-py3-none-any.whl
Size 106.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
00bd6f56b87c7d5d764e7e64d74291cb2040b691e0b3f53bafb7dc6cf3f6e3bc
BLAKE2b-256 checksum
How to use checksums
96bdd75b32fd35f1c3641bf8538f51c3f9c269d61008b8344ddb2770b2ff2f03
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.

Transparency log

Release history Release notifications | RSS feed

4.7.7

2 release files

4.7.6

2 release files

4.7.5

2 release files

4.7.4

2 release files

4.7.3

2 release files

4.7.2

2 release files

4.7.1

2 release files

This release

4.7.0 This release

2 release files

4.6.0

2 release files

4.5.1

2 release files

4.5.0

2 release files

4.4.8

2 release files

4.4.7

2 release files

4.4.6

2 release files

4.4.5

2 release files

4.4.4

2 release files

4.4.3

2 release files

4.4.2

2 release files

4.4.1

2 release files

4.4.0

2 release files

4.3.0

2 release files

4.2.0

2 release files

4.1.2

2 release files

4.1.1

2 release files

4.1.0

2 release files

4.0.1

2 release files

4.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page