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,101 @@
|
||||
// Luzern sweep: LinkedIn + job-room.ch (canton LU)
|
||||
import { KEYWORDS } from "../config.ts";
|
||||
import type { SearchConfig } from "../config.ts";
|
||||
import { fetchJobs, isEnglishContent, NON_ENGLISH_RE, JUNIOR_TITLE_RE, JAVA_TITLE_RE, SOFTWARE_TITLE_RE } from "../fetch.ts";
|
||||
|
||||
const MS_STACK_RE = /\.net\b|\bc#|sharepoint|dynamics 365|power platform|\bsccm\b|intune|successfactors|\bsap\b|\bm365\b/i;
|
||||
const PHARMA_RE = /\b(clinical|pharmaceutical|drug\s+(substance|product)|ha?ematology|oncology|formulation|laborator|chemist|chemistry|biolog|preclinical|regulatory\s+affairs|medical\s+affairs|veterinary|pathologist)\b/i;
|
||||
const DE_FR_RE = /\b(und|oder|wir|Sie|Ihre|Ihrer|Kenntnisse|Erfahrung|Aufgaben|Anforderungen|Stelle|Unternehmen|Mitarbeiter|Bereich|nous|vous|votre|pour|avec|des|les)\b/g;
|
||||
const strip = (s: string) => s.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
||||
|
||||
// ---------- LinkedIn ----------
|
||||
console.log("=== LinkedIn: Lucerne ===");
|
||||
const config: SearchConfig = {
|
||||
label: "Luzern",
|
||||
keywords: KEYWORDS,
|
||||
location: "Lucerne, Switzerland",
|
||||
workType: ["onsite", "hybrid"],
|
||||
timePosted: "month",
|
||||
};
|
||||
|
||||
const liJobs = await fetchJobs(config);
|
||||
console.log(`${liJobs.length} after title filters\n`);
|
||||
|
||||
const liKept: any[] = [];
|
||||
for (const j of liJobs) {
|
||||
if (MS_STACK_RE.test(j.title)) continue;
|
||||
process.stdout.write(` ${j.title} … `);
|
||||
const ok = await isEnglishContent(j.id);
|
||||
console.log(ok ? "ok" : "dropped (DE/FR)");
|
||||
if (ok) liKept.push(j);
|
||||
}
|
||||
|
||||
// ---------- job-room.ch, canton LU ----------
|
||||
console.log("\n=== job-room.ch: canton LU ===");
|
||||
const API = "https://www.job-room.ch/jobadservice/api/jobAdvertisements/_search";
|
||||
const UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
||||
const KWS = ["software", "engineer", "developer", "devops", "data", "python", "cloud", "platform", "backend", "full stack", "ai"];
|
||||
|
||||
const ads = new Map<string, any>();
|
||||
for (const kw of KWS) {
|
||||
for (let page = 0; page < 5; page++) {
|
||||
const res = await fetch(`${API}?page=${page}&size=100`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": UA },
|
||||
body: JSON.stringify({
|
||||
onlineSince: 30, displayRestricted: false, keywords: [kw],
|
||||
cantonCodes: ["LU"], workloadPercentageMin: 0, workloadPercentageMax: 100,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) break;
|
||||
const hits = (await res.json()) as any[];
|
||||
for (const h of hits) if (h.jobAdvertisement?.id) ads.set(h.jobAdvertisement.id, h.jobAdvertisement);
|
||||
if (hits.length < 100) break;
|
||||
await Bun.sleep(200);
|
||||
}
|
||||
await Bun.sleep(200);
|
||||
}
|
||||
console.log(`${ads.size} unique ads in LU`);
|
||||
|
||||
const jrKept: any[] = [];
|
||||
for (const ad of ads.values()) {
|
||||
const c = ad.jobContent;
|
||||
const d = c?.jobDescriptions?.[0];
|
||||
if (!d) continue;
|
||||
const title = strip(d.title);
|
||||
if (NON_ENGLISH_RE.test(title) || JUNIOR_TITLE_RE.test(title) || JAVA_TITLE_RE.test(title)) continue;
|
||||
if (!SOFTWARE_TITLE_RE.test(title)) continue;
|
||||
if (MS_STACK_RE.test(title) || PHARMA_RE.test(title)) continue;
|
||||
|
||||
const body = strip(d.description ?? "");
|
||||
if ((body.match(DE_FR_RE) ?? []).length >= 6) continue;
|
||||
if (MS_STACK_RE.test(body)) continue;
|
||||
|
||||
const langs = (c.languageSkills ?? [])
|
||||
.map((l: any) => `${l.languageIsoCode}:${l.spokenLevel ?? "?"}/${l.writtenLevel ?? "?"}`).join(" ");
|
||||
|
||||
jrKept.push({
|
||||
title,
|
||||
company: c.company?.name ?? "",
|
||||
city: c.location?.city ?? "",
|
||||
workload: `${c.employment?.workloadPercentageMin ?? "?"}-${c.employment?.workloadPercentageMax ?? "?"}%`,
|
||||
posted: ad.createdTime?.slice(0, 10) ?? "",
|
||||
langs,
|
||||
url: c.externalUrl ?? `https://www.job-room.ch/job-search/${ad.id}`,
|
||||
snippet: body.slice(0, 200),
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- output ----------
|
||||
console.log(`\n\n######## LinkedIn survivors: ${liKept.length} ########\n`);
|
||||
for (const j of liKept) console.log(`### ${j.title} [${j.company}]\n ${j.location} · ${j.postedAt}\n ${j.link}\n`);
|
||||
|
||||
console.log(`\n######## job-room survivors: ${jrKept.length} ########\n`);
|
||||
jrKept.sort((a, b) => b.posted.localeCompare(a.posted));
|
||||
for (const j of jrKept) {
|
||||
console.log(`### ${j.title} [${j.company}]`);
|
||||
console.log(` ${j.city} · ${j.workload} · ${j.posted}`);
|
||||
if (j.langs) console.log(` langs: ${j.langs}`);
|
||||
console.log(` ${j.url}`);
|
||||
console.log(` ${j.snippet}\n`);
|
||||
}
|
||||
Reference in New Issue
Block a user