chore: update search scripts, extra sources and post drafts
Adds the one-off scripts accumulated across recent sessions, removes temp scripts whose searches now live in config.ts as role-family sweeps, and updates fetch-other.ts plus the LinkedIn post drafts.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
// jobs.ch — public search API sweep, Switzerland-wide.
|
||||
// Endpoint: /api/v1/public/search?query=…&rows=…&page=…
|
||||
import { NON_ENGLISH_RE, JUNIOR_TITLE_RE, JAVA_TITLE_RE, SOFTWARE_TITLE_RE } from "../fetch.ts";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
const csv = readFileSync("./results/jobs.csv", "utf8");
|
||||
const existing = new Set([...csv.matchAll(/"(https?:\/\/[^"]+)"/g)].map((m) => m[1]));
|
||||
|
||||
const QUERIES = [
|
||||
"software engineer", "full stack engineer", "backend engineer", "platform engineer",
|
||||
"devops engineer", "data engineer", "AI engineer", "machine learning engineer",
|
||||
"python developer", "typescript developer", "ruby on rails", "node.js developer",
|
||||
"tech lead", "engineering manager", "staff engineer", "principal engineer",
|
||||
"solution architect", "data scientist",
|
||||
];
|
||||
|
||||
const MS_RE = /\b(\.net|c#|azure|sharepoint|dynamics|power\s?(bi|platform|apps)|sccm|intune|windows|sap|abap)\b/i;
|
||||
// Entry-level roles whose *title* looks neutral — caught only in the description.
|
||||
const JUNIOR_TEXT_RE = /\b(0\s*[-–to]+\s*[1-5]\s*years|entry.level|junior|graduate program|no experience required)\b/i;
|
||||
const DE_FR_WORDS_RE =
|
||||
/\b(und|oder|wir|Sie|Ihre|Ihrer|Kenntnisse|Erfahrung|Aufgaben|Anforderungen|Stelle|Unternehmen|Mitarbeiter|Bereich|für|mit|nous|vous|votre|pour|avec|des|les|une|dans)\b/g;
|
||||
|
||||
interface Doc {
|
||||
job_id: string;
|
||||
title: string;
|
||||
preview?: string;
|
||||
company_name?: string;
|
||||
place?: string;
|
||||
publication_date?: string;
|
||||
language_skills?: Array<{ language: string; level: number }>;
|
||||
_links?: { detail_en?: { href: string } };
|
||||
}
|
||||
|
||||
const seen = new Map<string, Doc>();
|
||||
|
||||
for (const q of QUERIES) {
|
||||
// rows is capped at 20 by the API (422 above that); sort=date puts fresh postings first.
|
||||
for (let page = 1; page <= 6; page++) {
|
||||
const url = `https://www.jobs.ch/api/v1/public/search?query=${encodeURIComponent(q)}&rows=20&page=${page}&sort=date`;
|
||||
const res = await fetch(url, {
|
||||
headers: { "User-Agent": "Mozilla/5.0", Accept: "application/json" },
|
||||
});
|
||||
if (!res.ok) break;
|
||||
const data = (await res.json()) as { documents: Doc[]; num_pages: number };
|
||||
for (const d of data.documents ?? []) if (!seen.has(d.job_id)) seen.set(d.job_id, d);
|
||||
if (page >= (data.num_pages ?? 1)) break;
|
||||
await Bun.sleep(250);
|
||||
}
|
||||
console.log(`${q}: ${seen.size} unique so far`);
|
||||
}
|
||||
|
||||
console.log(`\n${seen.size} raw postings. Filtering…\n`);
|
||||
|
||||
const out: Array<Doc & { link: string }> = [];
|
||||
let dropped = { title: 0, ms: 0, lang: 0, junior: 0, dup: 0 };
|
||||
|
||||
for (const d of seen.values()) {
|
||||
const t = d.title;
|
||||
if (NON_ENGLISH_RE.test(t) || JUNIOR_TITLE_RE.test(t) || JAVA_TITLE_RE.test(t) || !SOFTWARE_TITLE_RE.test(t)) { dropped.title++; continue; }
|
||||
if (MS_RE.test(t)) { dropped.ms++; continue; }
|
||||
|
||||
// German/French required at a working level → drop
|
||||
const needsDeFr = (d.language_skills ?? []).some(
|
||||
(l) => (l.language === "de" || l.language === "fr") && l.level >= 3
|
||||
);
|
||||
const preview = (d.preview ?? "").replace(/<[^>]+>/g, " ");
|
||||
const deWords = (preview.match(DE_FR_WORDS_RE) ?? []).length;
|
||||
if (needsDeFr || deWords >= 4) { dropped.lang++; continue; }
|
||||
if (JUNIOR_TEXT_RE.test(preview)) { dropped.junior++; continue; }
|
||||
|
||||
const link = d._links?.detail_en?.href ?? `https://www.jobs.ch/en/vacancies/detail/${d.job_id}/`;
|
||||
if (existing.has(link)) { dropped.dup++; continue; }
|
||||
out.push({ ...d, link });
|
||||
}
|
||||
|
||||
out.sort((a, b) => (b.publication_date ?? "").localeCompare(a.publication_date ?? ""));
|
||||
|
||||
console.log(`dropped: ${dropped.title} title, ${dropped.ms} MS-stack, ${dropped.lang} DE/FR, ${dropped.junior} entry-level, ${dropped.dup} already in CSV`);
|
||||
console.log(`\n=== ${out.length} jobs.ch CANDIDATES ===\n`);
|
||||
for (const j of out) {
|
||||
console.log(`${j.title} | ${j.company_name} | ${j.place} | ${(j.publication_date ?? "").slice(0, 10)}`);
|
||||
console.log(` ${j.link}`);
|
||||
}
|
||||
Reference in New Issue
Block a user