v3: eigener Scraper (OBI live verifiziert) statt braucheklima-Daten; Elektromärkte (MediaMarkt/Saturn/expert/Euronics)+Amazon; Startseite reframed 'wo gibt es Klimageräte'; SaaS-Design; Monitor any-AC + Modell-Filter
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
// Kühlfinder-Scraper-Runner. Läuft als Coolify Scheduled Task (z.B. alle 30 Min).
|
||||
// Holt aktuelle Bestände direkt von den Händlern, schreibt availability.json +
|
||||
// hängt Restock-Events an history.json (Basis für die Prognose).
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import * as obi from './adapters/obi.mjs';
|
||||
|
||||
const ADAPTERS = [obi /*, toom, bauhaus, hornbach, mediamarkt, saturn, expert, euronics, amazon */];
|
||||
const OUT = new URL('../public/data/', import.meta.url);
|
||||
|
||||
async function loadJson(name, fallback) {
|
||||
try { return JSON.parse(await readFile(new URL(name, OUT))); } catch { return fallback; }
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const stores = await loadJson('stores.json', []);
|
||||
const prev = await loadJson('availability.json', { offers: [] });
|
||||
const prevMap = new Map(prev.offers.map(o => [o.storeId + '|' + o.productId, o]));
|
||||
const history = await loadJson('history.json', { events: [] });
|
||||
|
||||
const offers = [];
|
||||
for (const ad of ADAPTERS) {
|
||||
for (const productId of Object.keys(ad.products || {})) {
|
||||
// Online-/Default-Markt-Check (Per-Markt-Loop sobald marketId-Mapping steht)
|
||||
try {
|
||||
const r = await ad.fetchProduct(productId, {});
|
||||
if (r) {
|
||||
offers.push(r);
|
||||
const key = (r.marketId || ad.meta.id) + '|' + productId;
|
||||
const before = prevMap.get(key);
|
||||
// Restock-Event: war nicht verfügbar -> jetzt verfügbar
|
||||
if (before && before.availability === 'out_of_stock' && r.availability !== 'out_of_stock') {
|
||||
history.events.push({ storeId: r.marketId || ad.meta.id, productId, chain: ad.meta.name, at: r.checkedAt });
|
||||
}
|
||||
}
|
||||
} catch (e) { console.error(ad.meta.id, productId, e.message); }
|
||||
await new Promise(r => setTimeout(r, 800 + Math.random() * 1200)); // höflich: Jitter
|
||||
}
|
||||
}
|
||||
const snapshot = { generatedAt: new Date().toISOString(), source: 'kuehlfinder-scraper', offers };
|
||||
await writeFile(new URL('availability.json', OUT), JSON.stringify(snapshot));
|
||||
await writeFile(new URL('history.json', OUT), JSON.stringify(history));
|
||||
console.log('scraped offers:', offers.length, 'history events:', history.events.length);
|
||||
}
|
||||
main();
|
||||
Reference in New Issue
Block a user