# linkme Scrapes LinkedIn job listings into a local CSV. Run it daily, delete rows you've applied to. ## Usage ```bash bun fetch # scrape and append new jobs to results/jobs.csv bun recheck # show jobs currently filtered out + keyword suggestions ``` No setup needed. Results are appended to `results/jobs.csv` — reruns are safe, already-seen jobs are skipped by ID. ## Structure ``` config.ts # search keywords and areas fetch.ts # scraper, HTML parser, filters fetch-other.ts # extra sources (datacareer.ch, stellen-basel.ch) index.ts # orchestrator: runs searches, writes CSV recheck.ts # raw-mode fetch, lists filtered-out jobs scripts/ # one-off search scripts (temp, not in config.ts) cv/ # polo.md (profile) + CV files results/ # jobs.csv output jobs/ # notes on specific job leads plans/ # LinkedIn post drafts ``` ## Output `results/jobs.csv` — one row per job: | column | description | |---|---| | `date_found` | date you ran the fetch | | `posted_at` | date the job was posted | | `search` | which search config matched it | | `title` | job title | | `company` | hiring company | | `link` | canonical job URL | | `source` | site the job came from | Open in any spreadsheet app. Delete rows as you apply or dismiss. ## How it works - **Paginated scraping** — fetches up to 6 pages per search (150 jobs) with a 400 ms delay between pages - **Card-boundary-safe parsing** — each job card is isolated before extracting fields - **Language filter** — drops German (`m/w/d`, umlauts, gender markers) and French (`H/F`, accents) titles - **Software-role filter** — requires a relevant term in the title (software, developer, data, AI, backend, frontend, devops, platform, architect, tech lead, typescript, python, rails, node.js, ML, cloud, security, SRE, etc.) - **Seniority filter** — drops junior, intern, trainee, apprentice, werkstudent, entry-level - **Global deduplication** — IDs indexed from existing CSV on startup; already-seen jobs are never re-added - **30-day recency** — only jobs posted in the last month ## recheck `bun recheck` fetches all searches in raw mode (bypassing the software-role filter) and lists every job that would be filtered out — useful for spotting false negatives. Also prints a word-frequency table of filtered titles to help decide what to add to `SOFTWARE_TITLE_RE`. ## Temporary searches Write a throwaway script in `scripts/` instead of editing `config.ts`: ```bash bun run scripts/bern-temp.ts ``` Scripts in `scripts/` import from `../config.ts` and `../fetch.ts`. ## Configure searches Edit `config.ts`: ```ts export const KEYWORDS = "Software Engineer OR Data Engineer OR ..."; export const searches: SearchConfig[] = [ { label: "Basel (city)", keywords: KEYWORDS, geoId: "103383283", location: "Basel", workType: ["onsite", "hybrid"], timePosted: "month", }, ]; ``` Known Swiss geoIds: - `103383283` — Basel (city) - `90009885` — Basel Metropolitan Area - `107117530` — Basel-Country - `102436504` — Zürich (city) - `107814425` — Zürich Metropolitan Area - `90009888` — Zürich Area You can also paste any LinkedIn search URL directly as `url:` instead of building from filters. `SearchConfig` also supports `jobType` (full-time, part-time, contract, temporary, internship) and `seniority` (internship, entry, associate, mid-senior, director, executive).