fix: KoePi filtert Angebote anderer Filialen derselben Kette anhand der echten Adresse raus

This commit is contained in:
2026-07-12 14:44:29 +02:00
parent 37b75effb4
commit 238159576a

View File

@@ -41,6 +41,29 @@ function getAddress(retailer) {
return ''; return '';
} }
// Straßennamen robust vergleichbar machen (Groß-/Kleinschreibung, "straße"/"str.",
// Leerzeichen/Punkte egal — Hausnummer bleibt Teil des Vergleichs)
function normalizeAddress(s) {
return (s || '')
.toLowerCase()
.replace(/straße|strasse/g, 'str')
.replace(/[^a-zäöüß0-9]/g, '');
}
// Prüft, ob die tatsächlich gescrapte Adresse zu einer der bekannten LOKALEN
// Filialen für diesen Händler passt. Ohne erkannten Händler oder ohne gescrapte
// Adresse wird NICHT gefiltert (lieber ein Angebot zuviel zeigen als eins zu
// Unrecht verstecken, falls marktguru das Layout mal ändert).
function isLocalBranch(retailer, realAddress) {
const n = (retailer || '').toLowerCase();
const key = Object.keys(RETAILER_ADDRESSES).find(k => n.includes(k));
if (!key) return true;
if (!realAddress) return true;
const expectedParts = RETAILER_ADDRESSES[key].split('/').map(p => normalizeAddress(p.trim()));
const real = normalizeAddress(realAddress);
return expectedParts.some(p => real.includes(p) || (p && p.includes(real)));
}
// ── Angebote via Puppeteer von marktguru ───────────────────────────────────── // ── Angebote via Puppeteer von marktguru ─────────────────────────────────────
async function scrapeMarktguru(forceRefresh = false) { async function scrapeMarktguru(forceRefresh = false) {
const cacheKey = 'koepi:mg'; const cacheKey = 'koepi:mg';
@@ -104,7 +127,10 @@ async function scrapeMarktguru(forceRefresh = false) {
const img = card.querySelector('img.offer-list-item-img'); const img = card.querySelector('img.offer-list-item-img');
const badge = card.querySelector('.badge, .discount-badge')?.textContent?.trim() || ''; const badge = card.querySelector('.badge, .discount-badge')?.textContent?.trim() || '';
const imgSrc = img?.src?.replace('/medium.webp', '/large.webp') || img?.src || null; const imgSrc = img?.src?.replace('/medium.webp', '/large.webp') || img?.src || null;
return { name, brand, price, oldPrice, retailer, dateRange, validity, description, badge, address: '', image: imgSrc }; const retailerDd = card.querySelector('dd.retailer-name');
const addressDd = retailerDd?.nextElementSibling?.tagName === 'DD' ? retailerDd.nextElementSibling : null;
const realAddress = addressDd?.textContent?.trim() || '';
return { name, brand, price, oldPrice, retailer, dateRange, validity, description, badge, address: '', realAddress, image: imgSrc };
}); });
}); });
@@ -138,8 +164,14 @@ async function scrapeMarktguru(forceRefresh = false) {
return hasKP && isTargetRetailer(o.retailer); return hasKP && isTargetRetailer(o.retailer);
}); });
// Nur Angebote der tatsächlich bekannten, lokalen Filialen behalten (marktguru
// zeigt teils auch weiter entfernte Filialen derselben Kette, z.B. "REWE Dortmund"
// oder einen anderen Netto/Kaufland-Standort — anhand der echten gescrapten
// Adresse rausfiltern statt sie fälschlich mit der lokalen Adresse zu überschreiben)
const localOnly = filtered.filter(o => isLocalBranch(o.retailer, o.realAddress));
// Händler normalisieren: E center = EDEKA // Händler normalisieren: E center = EDEKA
for (const o of filtered) { for (const o of localOnly) {
if (o.retailer?.toLowerCase().includes('e center')) o.retailerDisplay = o.retailer; if (o.retailer?.toLowerCase().includes('e center')) o.retailerDisplay = o.retailer;
else o.retailerDisplay = o.retailer; else o.retailerDisplay = o.retailer;
} }
@@ -147,7 +179,7 @@ async function scrapeMarktguru(forceRefresh = false) {
// Deduplizieren: gleicher normalisierter Händler + gleicher Preis → ein Eintrag // Deduplizieren: gleicher normalisierter Händler + gleicher Preis → ein Eintrag
// Kürzeste Beschreibung nehmen (sauberste) // Kürzeste Beschreibung nehmen (sauberste)
const merged = new Map(); const merged = new Map();
for (const o of filtered) { for (const o of localOnly) {
const normalizedRetailer = o.retailer?.toLowerCase().replace('e center','edeka') || ''; const normalizedRetailer = o.retailer?.toLowerCase().replace('e center','edeka') || '';
const key = `${normalizedRetailer}|${o.price}`; const key = `${normalizedRetailer}|${o.price}`;
if (merged.has(key)) { if (merged.has(key)) {