linkedin-playwright-scraper 4.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. linkedin_playwright_scraper-4.0.0/.env.example +8 -0
  2. linkedin_playwright_scraper-4.0.0/.github/workflows/publish.yml +25 -0
  3. linkedin_playwright_scraper-4.0.0/.gitignore +45 -0
  4. linkedin_playwright_scraper-4.0.0/CLAUDE.md +86 -0
  5. linkedin_playwright_scraper-4.0.0/CONTRIBUTING.md +206 -0
  6. linkedin_playwright_scraper-4.0.0/FORK_CONTEXT.md +73 -0
  7. linkedin_playwright_scraper-4.0.0/Justfile +87 -0
  8. linkedin_playwright_scraper-4.0.0/LICENSE +674 -0
  9. linkedin_playwright_scraper-4.0.0/MANIFEST.in +39 -0
  10. linkedin_playwright_scraper-4.0.0/PKG-INFO +502 -0
  11. linkedin_playwright_scraper-4.0.0/README.md +469 -0
  12. linkedin_playwright_scraper-4.0.0/TESTING.md +207 -0
  13. linkedin_playwright_scraper-4.0.0/docs/README.md +46 -0
  14. linkedin_playwright_scraper-4.0.0/docs/adr/001-playwright-over-official-api.md +36 -0
  15. linkedin_playwright_scraper-4.0.0/docs/adr/002-headless-false-required.md +40 -0
  16. linkedin_playwright_scraper-4.0.0/docs/adr/003-js-evaluation-over-locators.md +41 -0
  17. linkedin_playwright_scraper-4.0.0/docs/adr/004-data-urn-stable-anchor.md +45 -0
  18. linkedin_playwright_scraper-4.0.0/docs/adr/005-playwright-storage-state.md +43 -0
  19. linkedin_playwright_scraper-4.0.0/docs/adr/006-pydantic-models.md +43 -0
  20. linkedin_playwright_scraper-4.0.0/docs/adr/007-async-architecture.md +38 -0
  21. linkedin_playwright_scraper-4.0.0/docs/adr/008-uv-just-toolchain.md +48 -0
  22. linkedin_playwright_scraper-4.0.0/docs/adr/009-library-samples-separation.md +59 -0
  23. linkedin_playwright_scraper-4.0.0/docs/adr/010-single-version-source.md +43 -0
  24. linkedin_playwright_scraper-4.0.0/docs/adr/011-xvfb-linux-deployment.md +56 -0
  25. linkedin_playwright_scraper-4.0.0/docs/adr/012-lnkd-url-resolution.md +47 -0
  26. linkedin_playwright_scraper-4.0.0/docs/adr/013-mcp-integration-architecture.md +62 -0
  27. linkedin_playwright_scraper-4.0.0/docs/adr/014-cdp-existing-browser.md +55 -0
  28. linkedin_playwright_scraper-4.0.0/docs/adr/015-local-editable-install.md +51 -0
  29. linkedin_playwright_scraper-4.0.0/docs/adr/016-rate-limit-avoidance.md +47 -0
  30. linkedin_playwright_scraper-4.0.0/docs/adr/017-remote-cdp-homelab-chromium.md +41 -0
  31. linkedin_playwright_scraper-4.0.0/docs/adr/README.md +35 -0
  32. linkedin_playwright_scraper-4.0.0/docs/integration-linkedin-mcp.md +287 -0
  33. linkedin_playwright_scraper-4.0.0/docs/local-dev-setup.md +118 -0
  34. linkedin_playwright_scraper-4.0.0/docs/post-mortem/2026-06-25-feed-repost-aria-label.md +70 -0
  35. linkedin_playwright_scraper-4.0.0/docs/post-mortem/2026-07-22-rate-limit-hang.md +74 -0
  36. linkedin_playwright_scraper-4.0.0/linkedin_scraper/__init__.py +106 -0
  37. linkedin_playwright_scraper-4.0.0/linkedin_scraper/callbacks.py +162 -0
  38. linkedin_playwright_scraper-4.0.0/linkedin_scraper/core/__init__.py +78 -0
  39. linkedin_playwright_scraper-4.0.0/linkedin_scraper/core/auth.py +314 -0
  40. linkedin_playwright_scraper-4.0.0/linkedin_scraper/core/browser.py +265 -0
  41. linkedin_playwright_scraper-4.0.0/linkedin_scraper/core/exceptions.py +39 -0
  42. linkedin_playwright_scraper-4.0.0/linkedin_scraper/core/permalink_cache.py +49 -0
  43. linkedin_playwright_scraper-4.0.0/linkedin_scraper/core/rate_limit_guard.py +122 -0
  44. linkedin_playwright_scraper-4.0.0/linkedin_scraper/core/utils.py +299 -0
  45. linkedin_playwright_scraper-4.0.0/linkedin_scraper/models/__init__.py +20 -0
  46. linkedin_playwright_scraper-4.0.0/linkedin_scraper/models/company.py +80 -0
  47. linkedin_playwright_scraper-4.0.0/linkedin_scraper/models/job.py +59 -0
  48. linkedin_playwright_scraper-4.0.0/linkedin_scraper/models/person.py +133 -0
  49. linkedin_playwright_scraper-4.0.0/linkedin_scraper/models/post.py +54 -0
  50. linkedin_playwright_scraper-4.0.0/linkedin_scraper/scrapers/__init__.py +19 -0
  51. linkedin_playwright_scraper-4.0.0/linkedin_scraper/scrapers/base.py +271 -0
  52. linkedin_playwright_scraper-4.0.0/linkedin_scraper/scrapers/company.py +208 -0
  53. linkedin_playwright_scraper-4.0.0/linkedin_scraper/scrapers/company_posts.py +346 -0
  54. linkedin_playwright_scraper-4.0.0/linkedin_scraper/scrapers/feed.py +1760 -0
  55. linkedin_playwright_scraper-4.0.0/linkedin_scraper/scrapers/job.py +205 -0
  56. linkedin_playwright_scraper-4.0.0/linkedin_scraper/scrapers/job_search.py +145 -0
  57. linkedin_playwright_scraper-4.0.0/linkedin_scraper/scrapers/person.py +720 -0
  58. linkedin_playwright_scraper-4.0.0/pyproject.toml +79 -0
  59. linkedin_playwright_scraper-4.0.0/samples/create_session.py +75 -0
  60. linkedin_playwright_scraper-4.0.0/samples/scrape_company.py +45 -0
  61. linkedin_playwright_scraper-4.0.0/samples/scrape_company_posts.py +41 -0
  62. linkedin_playwright_scraper-4.0.0/samples/scrape_feed.py +53 -0
  63. linkedin_playwright_scraper-4.0.0/samples/scrape_jobs.py +51 -0
  64. linkedin_playwright_scraper-4.0.0/samples/scrape_person.py +56 -0
  65. linkedin_playwright_scraper-4.0.0/samples/scrape_person_contacts.py +60 -0
  66. linkedin_playwright_scraper-4.0.0/tests/README.md +152 -0
  67. linkedin_playwright_scraper-4.0.0/tests/conftest.py +103 -0
  68. linkedin_playwright_scraper-4.0.0/tests/test_auth.py +25 -0
  69. linkedin_playwright_scraper-4.0.0/tests/test_browser.py +54 -0
  70. linkedin_playwright_scraper-4.0.0/tests/test_company_scraper.py +79 -0
  71. linkedin_playwright_scraper-4.0.0/tests/test_feed_scraper.py +360 -0
  72. linkedin_playwright_scraper-4.0.0/tests/test_job_scraper.py +85 -0
  73. linkedin_playwright_scraper-4.0.0/tests/test_person_scraper.py +127 -0
  74. linkedin_playwright_scraper-4.0.0/uv.lock +2047 -0
@@ -0,0 +1,8 @@
1
+ # LinkedIn credentials for scraping
2
+ # Copy this file to .env and fill in your credentials
3
+
4
+ # Use either LINKEDIN_EMAIL or LINKEDIN_USERNAME (both work)
5
+ LINKEDIN_EMAIL=your.email@example.com
6
+ # LINKEDIN_USERNAME=your.email@example.com
7
+
8
+ LINKEDIN_PASSWORD=your_password_here
@@ -0,0 +1,25 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build-and-publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ id-token: write # required for PyPI trusted publishing (OIDC)
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Install uv
17
+ uses: astral-sh/setup-uv@v3
18
+
19
+ - name: Build distribution
20
+ run: uv build
21
+
22
+ - name: Publish to PyPI
23
+ uses: pypa/gh-action-pypi-publish@release/v1
24
+ with:
25
+ packages-dir: dist/
@@ -0,0 +1,45 @@
1
+ *.swp
2
+ linkedin_user_scraper.egg-info
3
+ linkedin_scraper.egg-info
4
+ dist
5
+ build
6
+ __pycache__
7
+ *.pyc
8
+ chromedriver
9
+ .DS_Store
10
+ .vscode/
11
+ .env/
12
+ scrape.py
13
+ creds.json
14
+ venv
15
+ *.zip
16
+ .env
17
+
18
+ # Test outputs
19
+ *.log
20
+ test_*.db
21
+ test_linkedin.db
22
+ test_summary.json
23
+ results_*.json
24
+ person_*.json
25
+ *_improved.json
26
+
27
+ # Debug scripts (keep debug_connection_selectors.py)
28
+ debug_*.py
29
+ !debug_connection_selectors.py
30
+
31
+ # Backup files
32
+ *_old.py
33
+ *.backup
34
+
35
+ # Session files (sensitive cookies)
36
+ linkedin_session.json
37
+
38
+ # Build artifacts
39
+ MANIFEST
40
+ MANIFEST.ini
41
+ .pytest_cache/
42
+
43
+ # Basic package build artifacts
44
+ build-basic/
45
+ dist-basic/
@@ -0,0 +1,86 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ # Install all dependencies and Playwright browsers
9
+ just install
10
+
11
+ # Run unit tests only (no LinkedIn session required)
12
+ just test
13
+
14
+ # Run a single test file
15
+ just test-file tests/test_feed_scraper.py
16
+
17
+ # Run a single test by name
18
+ just test-one tests/test_feed_scraper.py TestFeedScraperUnit::test_parse_count
19
+
20
+ # Run integration tests (requires linkedin_session.json)
21
+ just test-integration
22
+
23
+ # Create a LinkedIn session file (manual login in browser)
24
+ just session
25
+
26
+ # Format, lint, typecheck + unit tests
27
+ just check
28
+ ```
29
+
30
+ Toolchain: **uv** manages the virtualenv (`.venv/`) and dependencies (`pyproject.toml`). **just** is the task runner (`Justfile`). Run `just` with no arguments to list all recipes.
31
+
32
+ ## Architecture
33
+
34
+ This is an async Playwright-based LinkedIn scraper. All scraping is fully async (asyncio).
35
+
36
+ **Entry point:** `linkedin_scraper/__init__.py` re-exports everything public.
37
+
38
+ ### Layer structure
39
+
40
+ ```
41
+ core/ Browser lifecycle, auth, utils, exceptions
42
+ scrapers/ One scraper class per LinkedIn surface
43
+ models/ Pydantic models for scraped data
44
+ callbacks.py Progress reporting (SilentCallback, ConsoleCallback, JSONLogCallback, MultiCallback)
45
+ ```
46
+
47
+ ### `core/`
48
+
49
+ - `browser.py` — `BrowserManager`: async context manager wrapping Playwright. Handles launch, session save/load (`linkedin_session.json` = Playwright storage state), cookie injection.
50
+ - `auth.py` — `login_with_credentials`, `login_with_cookie`, `is_logged_in`, `wait_for_manual_login`. Login detection is URL-based (not DOM) to survive LinkedIn A/B tests.
51
+ - `utils.py` — `retry_async` decorator, `detect_rate_limit`, scroll helpers, `click_see_more_buttons`, `extract_text_safe`.
52
+ - `exceptions.py` — exception hierarchy rooted at `LinkedInScraperException`.
53
+
54
+ ### `scrapers/`
55
+
56
+ All scrapers inherit `BaseScraper` (`scrapers/base.py`) which wraps `core/` helpers as instance methods.
57
+
58
+ | Scraper | Surface |
59
+ |---|---|
60
+ | `PersonScraper` | Profile page |
61
+ | `CompanyScraper` | Company overview |
62
+ | `CompanyPostsScraper` | Company `/posts/` tab |
63
+ | `FeedScraper` | `/feed/` (authenticated) |
64
+ | `JobScraper` | Individual job posting |
65
+ | `JobSearchScraper` | Job search results |
66
+
67
+ **Scraper pattern:** `scrape(url, limit)` → navigate → wait for content → scroll loop with deduplication by URN → return list of Pydantic models.
68
+
69
+ Post content is extracted via `page.evaluate()` (inline JS) rather than Playwright locators, because LinkedIn's CSS class names are obfuscated and unstable. Activity URNs (`urn:li:activity:XXXXXX`) are the stable anchor — each post element has `data-urn` set to its URN.
70
+
71
+ ### `models/`
72
+
73
+ All models are Pydantic `BaseModel`. `Post` is shared between `CompanyPostsScraper` and `FeedScraper`:
74
+
75
+ ```python
76
+ Post: linkedin_url, urn, author_name, author_url, text, posted_date,
77
+ reactions_count, comments_count, reposts_count, image_urls, video_url, article_url
78
+ ```
79
+
80
+ ## Key conventions
81
+
82
+ - **Session file:** `linkedin_session.json` at the project root — Playwright storage state (cookies + localStorage). Never commit. Generated by `samples/create_session.py`.
83
+ - **headless=False** is required for LinkedIn — headless browsers get blocked. Integration tests and samples all use `headless=False`.
84
+ - **Feed scraper requires auth:** `FeedScraper.scrape()` calls `ensure_logged_in()` before navigating. Other scrapers do not enforce this.
85
+ - **Noise filtering in FeedScraper:** Sponsored/promoted posts are filtered out in the JS evaluation block by checking for `"Promoted"` / `"Sponsorisé"` labels and `[data-control-name="promoted"]`.
86
+ - **Test markers:** `unit` (no session), `integration` (needs `linkedin_session.json`), `slow`. Unit tests mock the Playwright `Page` object.
@@ -0,0 +1,206 @@
1
+ # Contributing to LinkedIn Scraper
2
+
3
+ Thank you for your interest in contributing to LinkedIn Scraper! This document provides guidelines for contributing to the project.
4
+
5
+ ## Getting Started
6
+
7
+ ### Prerequisites
8
+
9
+ - Python 3.8 or higher
10
+ - pip package manager
11
+ - Git
12
+
13
+ ### Setting Up Development Environment
14
+
15
+ 1. **Clone the repository**
16
+ ```bash
17
+ git clone https://github.com/joeyism/linkedin_scraper.git
18
+ cd linkedin_scraper
19
+ ```
20
+
21
+ 2. **Create a virtual environment**
22
+ ```bash
23
+ python -m venv venv
24
+ source venv/bin/activate # On Windows: venv\Scripts\activate
25
+ ```
26
+
27
+ 3. **Install dependencies**
28
+ ```bash
29
+ pip install -r requirements.txt
30
+ pip install -r requirements-dev.txt
31
+ ```
32
+
33
+ 4. **Install Playwright browsers**
34
+ ```bash
35
+ playwright install chromium
36
+ ```
37
+
38
+ 5. **Set up environment variables** (for testing)
39
+ ```bash
40
+ cp .env.example .env
41
+ # Edit .env and add your LinkedIn credentials (optional, for integration tests)
42
+ ```
43
+
44
+ ## Development Workflow
45
+
46
+ ### Running Tests
47
+
48
+ ```bash
49
+ # Run all tests
50
+ pytest
51
+
52
+ # Run specific test file
53
+ pytest tests/test_person.py
54
+
55
+ # Run with verbose output
56
+ pytest -v
57
+
58
+ # Run with coverage
59
+ pytest --cov=linkedin_scraper
60
+ ```
61
+
62
+ ### Code Style
63
+
64
+ This project follows these guidelines:
65
+
66
+ - **PEP 8**: Python code style guide
67
+ - **Type hints**: Use type annotations where appropriate
68
+ - **Docstrings**: Document all public functions and classes
69
+ - **Line length**: Maximum 100 characters
70
+
71
+ Before submitting, ensure your code passes linting:
72
+
73
+ ```bash
74
+ # Format code
75
+ black linkedin_scraper/
76
+
77
+ # Check for issues
78
+ flake8 linkedin_scraper/
79
+ mypy linkedin_scraper/
80
+ ```
81
+
82
+ ### Testing Your Changes
83
+
84
+ 1. Write tests for new functionality
85
+ 2. Ensure all existing tests pass
86
+ 3. Test manually with the sample scripts in `samples/`
87
+ 4. Verify documentation is updated
88
+
89
+ ## Making Changes
90
+
91
+ ### Branching Strategy
92
+
93
+ - `main` - Stable release branch
94
+ - `feature/your-feature` - New features
95
+ - `fix/your-bugfix` - Bug fixes
96
+ - `docs/your-doc-change` - Documentation updates
97
+
98
+ ### Commit Messages
99
+
100
+ Write clear, descriptive commit messages:
101
+
102
+ ```
103
+ Add support for scraping job descriptions
104
+
105
+ - Extract full job description text
106
+ - Parse job requirements section
107
+ - Add tests for job description parsing
108
+ ```
109
+
110
+ Format:
111
+ - First line: Brief summary (50 chars or less)
112
+ - Blank line
113
+ - Detailed explanation (wrap at 72 chars)
114
+ - List specific changes with bullet points
115
+
116
+ ### Pull Request Process
117
+
118
+ 1. **Create a new branch**
119
+ ```bash
120
+ git checkout -b feature/your-feature-name
121
+ ```
122
+
123
+ 2. **Make your changes**
124
+ - Write clean, well-documented code
125
+ - Add tests for new functionality
126
+ - Update documentation as needed
127
+
128
+ 3. **Commit your changes**
129
+ ```bash
130
+ git add .
131
+ git commit -m "Your descriptive commit message"
132
+ ```
133
+
134
+ 4. **Push to your fork**
135
+ ```bash
136
+ git push origin feature/your-feature-name
137
+ ```
138
+
139
+ 5. **Create a Pull Request**
140
+ - Go to the GitHub repository
141
+ - Click "New Pull Request"
142
+ - Select your branch
143
+ - Fill out the PR template
144
+ - Wait for review
145
+
146
+ ### Pull Request Guidelines
147
+
148
+ - **Description**: Clearly describe what changes you made and why
149
+ - **Tests**: Include tests that cover your changes
150
+ - **Documentation**: Update README.md or other docs if needed
151
+ - **Small PRs**: Keep changes focused and manageable
152
+ - **Responsive**: Be ready to address feedback
153
+
154
+ ## What to Contribute
155
+
156
+ ### Good First Issues
157
+
158
+ Look for issues labeled `good first issue` for beginner-friendly tasks:
159
+
160
+ - Documentation improvements
161
+ - Bug fixes
162
+ - Additional test coverage
163
+ - Code refactoring
164
+
165
+ ### Feature Requests
166
+
167
+ Before implementing major features:
168
+
169
+ 1. Check existing issues to avoid duplication
170
+ 2. Open an issue to discuss the feature
171
+ 3. Wait for maintainer approval
172
+ 4. Implement the feature once approved
173
+
174
+ ### Bug Reports
175
+
176
+ When reporting bugs, include:
177
+
178
+ - Python version
179
+ - Operating system
180
+ - Steps to reproduce
181
+ - Expected vs actual behavior
182
+ - Error messages/stack traces
183
+ - Sample code if possible
184
+
185
+ ## Code Review Process
186
+
187
+ 1. A maintainer will review your PR
188
+ 2. They may request changes
189
+ 3. Make requested changes and push updates
190
+ 4. Once approved, a maintainer will merge your PR
191
+
192
+ ## Questions?
193
+
194
+ If you have questions about contributing:
195
+
196
+ - Open an issue on GitHub
197
+ - Check existing issues and discussions
198
+ - Review the README.md for usage examples
199
+
200
+ ## License
201
+
202
+ By contributing, you agree that your contributions will be licensed under the Apache License 2.0.
203
+
204
+ ---
205
+
206
+ Thank you for contributing to LinkedIn Scraper! Your efforts help make this project better for everyone.
@@ -0,0 +1,73 @@
1
+ # Contexte du fork — linkedin_scraper
2
+
3
+ ## Origin
4
+
5
+ Fork de [joeyism/linkedin_scraper](https://github.com/joeyism/linkedin_scraper).
6
+
7
+ ## Pourquoi ce fork
8
+
9
+ ### Problème de départ
10
+
11
+ Ce projet est né d'un constat simple : **l'API officielle LinkedIn ne permet pas de lire les posts**.
12
+
13
+ Un premier projet (`linkedin-mcp`, fork de FilippTrigub) expose des outils MCP pour interagir avec LinkedIn via l'API officielle. Il fonctionne pour :
14
+ - S'authentifier via OAuth2
15
+ - Créer des posts (`POST /v2/ugcPosts`)
16
+
17
+ Mais la **lecture des posts est bloquée** par LinkedIn :
18
+
19
+ | Scope | Usage | Disponibilité |
20
+ |-------|-------|---------------|
21
+ | `w_member_social` | Créer des posts | Apps standard ✅ |
22
+ | `r_member_social` | Lire des posts | Marketing Developer Platform uniquement ❌ |
23
+
24
+ Le scope `r_member_social` est réservé aux partenaires LinkedIn (Marketing Developer Platform). Il est inaccessible aux développeurs individuels. Même avec un code correct, l'API renvoie **403 Forbidden** sur tous les endpoints de lecture (`/v2/ugcPosts`, `/v2/shares`).
25
+
26
+ ### Solution retenue : scraping web avec Playwright
27
+
28
+ L'API officielle étant une impasse, la seule approche réaliste est d'automatiser un navigateur authentifié. Ce repo utilise déjà Playwright et expose exactement la structure nécessaire.
29
+
30
+ ## Objectif de ce fork
31
+
32
+ **Ajouter un `FeedScraper`** capable de récupérer les N premiers posts du feed LinkedIn de l'utilisateur authentifié.
33
+
34
+ Le feed LinkedIn correspond à la page `linkedin.com/feed/` — ce que l'utilisateur voit quand il se connecte : posts de ses connexions, articles partagés, etc.
35
+
36
+ ### Ce qui existe déjà dans le repo
37
+
38
+ - `CompanyPostsScraper` — scrape les posts d'une page entreprise
39
+ - `BrowserManager` — gestion de session Playwright réutilisable
40
+ - Modèle `Post` — structure de données pour un post
41
+
42
+ ### Ce qu'il faut ajouter
43
+
44
+ Un `FeedScraper` calqué sur `CompanyPostsScraper` qui :
45
+ 1. Navigue sur `https://www.linkedin.com/feed/`
46
+ 2. Scrolle N fois pour charger les posts
47
+ 3. Parse le DOM pour extraire : auteur, texte, date, reactions, comments
48
+ 4. Filtre le bruit : publicités, suggestions "Vous connaissez peut-être...", posts sponsorisés
49
+ 5. Retourne une liste de `Post`
50
+
51
+ ## Complexité anticipée
52
+
53
+ ### Identique à CompanyPostsScraper
54
+ - Session Playwright réutilisée
55
+ - Infinite scroll avec `page.evaluate("window.scrollBy(...)")`
56
+
57
+ ### Spécifique au feed
58
+ - **Bruit dans le DOM** : le feed mélange posts réels, pubs, suggestions → filtrage nécessaire
59
+ - **Sélecteurs CSS instables** : LinkedIn obfusque ses class names, ils peuvent changer
60
+ - **Anti-bot** : LinkedIn est légèrement plus vigilant sur le feed que sur les pages publiques
61
+
62
+ ## Prochaines étapes
63
+
64
+ 1. Lire le code de `CompanyPostsScraper` pour comprendre le pattern exact
65
+ 2. Inspecter le DOM du feed LinkedIn pour identifier les sélecteurs stables
66
+ 3. Implémenter `FeedScraper` sur le même modèle
67
+ 4. Tester avec différents comptes et volumes de posts
68
+
69
+ ## Liens
70
+
71
+ - Repo original : https://github.com/joeyism/linkedin_scraper
72
+ - Ce fork : https://github.com/vinzlac/linkedin_scraper
73
+ - Projet MCP LinkedIn associé : https://github.com/vinzlac/linkedin-mcp (contexte origine)
@@ -0,0 +1,87 @@
1
+ # linkedin_scraper — task runner
2
+ # Requires: uv, just
3
+
4
+ # Default: list available recipes
5
+ default:
6
+ @just --list
7
+
8
+ # Install all dependencies (including dev) and Playwright browsers
9
+ install:
10
+ uv sync --group dev
11
+ uv run playwright install chromium
12
+
13
+ # Run unit tests only (no LinkedIn session required)
14
+ test:
15
+ uv run pytest -m "not integration and not slow"
16
+
17
+ # Run a single test file
18
+ test-file FILE:
19
+ uv run pytest {{ FILE }} -m "not integration"
20
+
21
+ # Run a single test by name
22
+ test-one FILE TEST:
23
+ uv run pytest {{ FILE }}::{{ TEST }}
24
+
25
+ # Run integration tests (requires linkedin_session.json)
26
+ test-integration:
27
+ uv run pytest -m integration
28
+
29
+ # Run all tests with coverage report
30
+ test-cov:
31
+ uv run pytest -m "not integration" --cov=linkedin_scraper --cov-report=term-missing
32
+
33
+ # Format code with black
34
+ fmt:
35
+ uv run black linkedin_scraper tests samples
36
+
37
+ # Check formatting without modifying files
38
+ fmt-check:
39
+ uv run black --check linkedin_scraper tests samples
40
+
41
+ # Lint with flake8
42
+ lint:
43
+ uv run flake8 linkedin_scraper tests
44
+
45
+ # Type check with mypy
46
+ typecheck:
47
+ uv run mypy linkedin_scraper
48
+
49
+ # Run all checks (fmt-check + lint + typecheck + test)
50
+ check: fmt-check lint test
51
+
52
+ # Create a LinkedIn session file (opens browser for manual login)
53
+ session:
54
+ uv run python samples/create_session.py
55
+
56
+ # Scrape N posts from your LinkedIn feed (default: 10)
57
+ run-feed N="10":
58
+ uv run python samples/scrape_feed.py {{ N }}
59
+
60
+ # Scrape N posts from feed using a virtual display (for Linux servers without a GUI)
61
+ # Requires: sudo apt install xvfb
62
+ run-feed-xvfb N="10":
63
+ xvfb-run --server-args="-screen 0 1280x720x24" uv run python samples/scrape_feed.py {{ N }}
64
+
65
+ # Debug DOM structure of LinkedIn feed
66
+ debug-feed:
67
+ uv run python samples/debug_feed.py
68
+
69
+ # Scrape a LinkedIn profile (URL or slug)
70
+ run-person PROFILE:
71
+ uv run python samples/scrape_person.py {{ PROFILE }}
72
+
73
+ # Debug DOM selectors on a profile page (helps fix broken selectors)
74
+ debug-person PROFILE:
75
+ uv run python samples/debug_person.py {{ PROFILE }}
76
+
77
+ # Run the company posts scraper sample
78
+ run-company URL="https://www.linkedin.com/company/microsoft/":
79
+ uv run python samples/scrape_company_posts.py
80
+
81
+ # Build distribution packages
82
+ build:
83
+ uv build
84
+
85
+ # Publish to PyPI (requires TWINE_USERNAME / TWINE_PASSWORD or ~/.pypirc)
86
+ publish:
87
+ uv run twine upload dist/*