LinkedIn saved jobs → EROI scoring → structured reports → git-committed market intelligence
Automated pipeline that scrapes your LinkedIn saved jobs, scores them against your profile using a 6-dimension EROI model, and writes actionable reports. Anti-bot aware — uses human-like patterns to avoid detection.
LinkedIn's job recommendations are noisy. Of 71 saved jobs analyzed, only 6% were relevant (SLEDOVAT/follow). The rest are noise — AI hype roles, fake-engineer titles, distant locations, non-strategic employers.
This tool replaces manual scrolling with a cache-aware, anti-bot pipeline:
📌 Your LinkedIn saved jobs (71 tracked)
↓
🔄 Skip-existing filter ← 95% reduction (only new jobs scraped)
↓
🕷️ Sequential scraper (30s, no parallelism) ← anti-bot — human rhythm
↓
📊 EROI scoring engine ← 6 dimensions, YAML-configured weights
↓
📝 KB write-back ← metadata_stacku.json + agregovany_report.md
↓
📈 Synthetic market analysis ← Frequency matrix + SNR + gap detection
↓
📦 Auto-committed to KB ← git commit with full history
| Feature | What it does |
|---|---|
| Cache-aware scraping | Skips jobs already in KB (--skip-existing) — 95% time reduction |
| Anti-bot pattern | Sequential scraping (no parallelism), random delay 3-7s, fingerprint mix |
| Adaptive delay | Speeds up on success (0.95×), slows on errors (1.5×) — self-tuning |
| Session heartbeat | Refreshes LinkedIn auth every N jobs — prevents mid-run session expiry |
| 3-layer YAML config | Source (max_pages) / Runtime (delay, timeout) / Analysis (N profiles) |
| Configurable EROI | --profile industrial switches thresholds + weights from YAML |
| Fingerprint mix | Random viewport, user-agent, locale, timezone per browser launch |
| 6-dimension EROI scoring | Domain (35%), Tech (25%), Role (20%), Growth (10%), Formal (5%), Location (5%) |
| Fake-engineer detection | Identifies roles with "Engineer" in title but service/sales content |
| Synthetic report | Frequency matrix + SNR + gap detection + cluster analysis |
| Raw text cache | Stores raw job text in KB for re-scoring with different profiles |
| Git commit | Every pipeline run auto-commits to your knowledge base |
- Python 3.12+
- uv (faster pip alternative —
pip install uv)
git clone https://github.com/outpost2026/linkedin-mcp-custom.git
cd linkedin-mcp-custom
uv sync.\linkedin-mcp.bat --login
# Opens a browser window — log into LinkedIn, then press Enter.\linkedin-mcp.bat --status
# ✅ Session valid | page.url = https://www.linkedin.com/feed/# Full pipeline (scrapes only new jobs, skips existing KB entries)
.venv\Scripts\python scripts\run_pipeline.py --skip-existing
# Fast mode (reduced delays, no fingerprint — for quick tests)
.venv\Scripts\python scripts\run_pipeline.py --skip-existing --fast
# Partial run (first 15 jobs — for testing)
.venv\Scripts\python scripts\run_pipeline.py --limit 15
# Custom config + analysis profile
.venv\Scripts\python scripts\run_pipeline.py --config ~/.linkedin-mcp-custom/config.yaml --profile industrial
# Via MCP client
.\linkedin-mcp.bat
# Then in your MCP client: call analyze_saved_jobsOutput: agregovany_report.md + metadata_stacku.json + synteticky_report.md in your KB directory.
The pipeline uses a 3-layer YAML config (~/.linkedin-mcp-custom/config.yaml):
user: "default"
source:
max_pages: 10 # how many tracker pages to scan
runtime:
headless: true
delay_range: [3.0, 7.0] # anti-bot delay between jobs
page_timeout_ms: 30000
session_heartbeat: 30 # refresh auth every N jobs
fingerprint_mix: true # random viewport/UA/locale
analysis:
default: # baseline EROI profile
thresholds: { sledovat: 65, medium: 50, hranicni: 40 }
weights: { domain: 0.35, tech: 0.25, role: 0.20, growth: 0.10, formal: 0.05, location: 0.05 }
industrial: # custom profile — switch via --profile industrial
thresholds: { sledovat: 70, medium: 55, hranicni: 45 }
weights: { domain: 0.50, tech: 0.20, role: 0.15, growth: 0.05, formal: 0.05, location: 0.05 }PIPELINE REPORT
============================================================
Conclusion: ok
Duration: 142.31s total
Job IDs found: 71 (70 already in KB, 1 new)
Jobs scored: 1
Errors: 0
Verdicts: {'SLEDOVAT': 2, 'MEDIUM': 8, 'HRANICNI': 3, 'NESLEDOVAT': 2}
Full report: synteticky_report_analyza.md
| Dimension | Weight | What it measures |
|---|---|---|
| Domain | 35% | Industrial automation (core) vs adjacent vs noise |
| Tech | 25% | Skill overlap — content-aware match ratio × coverage |
| Role | 20% | Engineering role vs "fake engineer" (service/sales) |
| Growth | 10% | Strategic employer (Siemens, ABB, Thermo Fisher…) |
| Formal | 5% | Degree requirements with flexibility detection |
| Location | 5% | Remote/hybrid/CZ vs distant/office-only |
| Score | Verdict |
|---|---|
| ≥65% | 🟢 SLEDOVAT (follow — apply now) |
| 50–64% | 🟡 MEDIUM (consider — mitigate gaps) |
| 40–49% | 🟡 HRANIČNÍ (borderline — only if time permits) |
| <40% | 🔴 NESLEDOVAT (skip — no time allocation) |
- Fake engineer: title says "Engineer" but content is service/sales → penalizes
role - Electronics manufacturing SMT/PCBA: caps
domainscore (adjacent, not core) - Degree flexibility: "equivalent practical experience" found → adds ~5% to
formal - Positioning match: strong
rolematch compensates for weakdomain - No-match penalty:
techscore drops sharply when key skills missing
| Tool | Description |
|---|---|
analyze_saved_jobs |
Full pipeline: scrape → EROI score → KB write → git commit |
get_saved_jobs |
List all saved job IDs from LinkedIn tracker |
get_job_details <id> |
Full posting text for a single job ID |
analyze_job <id> |
EROI score a single job (avoids timeout) |
check_session |
Verify LinkedIn auth status with diagnostics |
Timeout strategy:
analyze_saved_jobsuses time-budgeted batch processing (default 45s). For full analysis, use the CLI pipeline or callanalyze_jobper job.
B2B-Knowledge-Base/
└── 02_ANALYZY/
└── 00_linkedin/
├── agregovany_report.md # Human-readable EROI entries
├── metadata_stacku.json # Machine-readable (schema v1.1)
└── synteticky_report_analyza.md # Market intelligence report
# Tests
.venv\Scripts\python -m pytest tests/ -v
# Lint
.venv\Scripts\python -m ruff check src/
# Type check
.venv\Scripts\python -m mypy src/See the pitevni_kniha (autopsy book) for 28 documented bugs, root causes, fixes, and engineering rules.
| Known issue | Status |
|---|---|
| MCP transport timeout for batch ops | ✅ Fixed (time-budget + per-job tool) |
| Cookie lifecycle — silent expiry | ✅ Fixed (session cache + checkpoint detection) |
| KB dedup fallback (industry=None) | ✅ Fixed |
| Summary table non-idempotent | ✅ Fixed |
| Pagination missing pages | ✅ Fixed |
| CSS selector fragility | ✅ Fixed |
| Refactor branch regression (34% → 100%) | ✅ Fixed (new baseline branch) |
| Anti-bot vs. speed tradeoff | ✅ Configurable (--fast, --profile) |
| Redundant scraping of known jobs | ✅ Fixed (--skip-existing) |
PRs welcome! This project especially needs:
- CI/CD pipeline — GitHub Actions for weekly scraping
- Docker deployment — containerize the MCP server
- More scorers — add dimensions (salary, benefits, team size)
- UI — simple dashboard for browsing scored jobs
- Translations — localize EROI labels for your market
Please read CONTRIBUTING.md first (coming soon).
MIT — see LICENSE.
Iterační backlog odvozený z 29 bug post-mortem záznamů a obscura-inspired pattern transferu. Každý návrh s hodnocením přínos/riziko.
| # | Návrh | Přínos | Riziko | Doporučení |
|---|---|---|---|---|
| 1 | Optimalizovat page-turn na trackeru — zkrátit wait_for_timeout(17000) nebo najít specifický selector pro detekci načtení |
↓40% času (ušetří ~60-80s) | Nízké — rychlejší navigace, stále sekvenční | ✅ Teď — nejvyšší poměr přínos/riziko |
| 2 | Parallel per-job scraping — asyncio.gather s N kontexty |
3-5× rychlejší (↓20-30s místo ~110s) | Vysoké — emergentní bot fingerprint (Z025) už jednou spadl z 98% na 34% | ❌ Počkat na lepší anti-bot strategii |
| 3 | CI/CD weekly scrape — rozchodit GitHub Actions workflow | Plně automatický monitoring | Střední — cookie export (Z021) vyžaduje ruční refresh každých pár týdnů | ⏳ Po stabilizaci skórovacího profilu |
| 4 | Synthetic report 2.0 — trend analýza, skill gap evoluce, časové řady | Vyšší vypovídací hodnota než statický snapshot | Nízké — jen nová analytická vrstva | ⏳ Vyžaduje 2-3 historické snapshoty |
| 5 | Multi-portál (Jobs.cz, Profesia) — nový scraper per obscura worker pattern | Širší pokrytí trhu | Střední — předčasná abstrakce (obscura transfer sekce 8.3) | ❌ Počkat na 100+ jobů v KB |
| 6 | Auth guard deduplication — parametr skip_auth_check v extract_page() pro pipeline režim |
Eliminace redundantní navigace na feed | Nízké — čistě refaktor bez změny chování | ✅ Teď — navazuje na fix Z029 |
| 7 | Sbírat job IDs z API interceptu místo DOM/script scanningu (Z018, Z013) | Rychlejší + spolehlivější extrakce | Střední — API formát se může změnit | ⏳ Až LinkedIn změní aktuální strukturu |
| Kdy | Co |
|---|---|
| Teď (1-2 běhy) | #1 page-turn optimalizace + #6 auth dedup |
| Brzy (3-5 běhů) | #3 CI/CD + #4 report 2.0 |
| Až bude dost dat (100+) | #5 multi-portál |
| Až LinkedIn zlomí aktuální extrakci | #7 API intercept |
Built by a systems integration engineer who got tired of LinkedIn's noise-to-signal problem. The name "EROI" comes from energy-return-on-investment — a concept borrowed from off-grid solar (which the author also builds). The same principle applies to job hunting: don't spend energy where the return is negative.
"LinkedIn recommends everything. This tool tells you what matters."