diff --git a/backend/src/tools/koepi/routes.js b/backend/src/tools/koepi/routes.js index 5d1733d..56adc8f 100644 --- a/backend/src/tools/koepi/routes.js +++ b/backend/src/tools/koepi/routes.js @@ -50,58 +50,25 @@ async function scrapeMarktguru() { await new Promise(r => setTimeout(r, 4000)); const offers = await page.evaluate(() => { - const results = []; - // Angebots-Karten selektieren - const cards = document.querySelectorAll('.offer-item, [class*="offer-item"], .search-offer, [class*="searchOffer"]'); - - // Fallback: alle Container die Preis + Händler enthalten - const containers = cards.length > 0 ? cards : - document.querySelectorAll('[class*="offer"]:not([class*="more"]):not([class*="list"]):not([class*="section"])'); - - containers.forEach(card => { - const text = card.innerText || card.textContent || ''; - if (!text.includes('König') && !text.includes('Pilsen')) return; - - const img = card.querySelector('img'); - const lines = text.split('\n').map(l => l.trim()).filter(Boolean); - - // Felder aus Text extrahieren - let name='', brand='', price='', retailer='', validity='', dateRange='', description='', oldPrice=''; - - lines.forEach((line, i) => { - if (line.match(/^€\s*\d/) && !price) price = line; - if (line.match(/^\d+[,\.]\d+\s*€/) && !price) price = line; - if (line.includes('Händler:')) retailer = lines[i+1] || line.replace('Händler:','').trim(); - if (line.includes('Marke:')) brand = lines[i+1] || line.replace('Marke:','').trim(); - if (line.includes('Gültig:') || line.includes('gültig:')) dateRange = lines[i+1] || line.replace(/G.ltig:/i,'').trim(); - if (line.includes('Noch') && line.includes('Tag')) validity = line; - if (line.includes('/ l') || line.includes('/l') || line.includes('Kasten') || line.includes('Flasche')) description = line; - if (line.includes('Brandneu')) validity = 'Neu'; - }); - - // Name ist meist die erste Zeile - name = lines[0] || ''; - - // Direkte DOM-Selektion als Alternative - const priceEl = card.querySelector('[class*="price"],[class*="Price"]'); - const retailEl = card.querySelector('[class*="retailer"],[class*="Retailer"],[class*="store"],[class*="Store"]'); - const nameEl = card.querySelector('[class*="title"],[class*="name"],h3,h2'); - const brandEl = card.querySelector('[class*="brand"],[class*="Brand"]'); - const descEl = card.querySelector('[class*="desc"],[class*="info"],[class*="detail"]'); - - if (priceEl) price = priceEl.textContent.trim(); - if (retailEl) retailer = retailEl.textContent.trim(); - if (nameEl) name = nameEl.textContent.trim(); - if (brandEl) brand = brandEl.textContent.trim(); - if (descEl) description = descEl.textContent.trim(); - - results.push({ - name, brand, price, oldPrice, retailer, validity, dateRange, description, - image: img?.src || null, - }); + // Exakte Selektoren basierend auf marktguru HTML-Struktur + const cards = document.querySelectorAll('li.offer-list-item'); + return Array.from(cards).map(card => { + const name = card.querySelector('h3')?.textContent?.trim() || ''; + const brand = card.querySelector('dd.brand a')?.textContent?.trim() || ''; + const price = card.querySelector('span.price')?.textContent?.trim() || ''; + const oldPrice = card.querySelector('.price-bubble .old-price, .crossed')?.textContent?.trim() || ''; + const retailer = card.querySelector('dd.retailer-name a')?.textContent?.trim() || ''; + const dateRange= card.querySelector('dd.valid')?.textContent?.trim() || ''; + const daysLeft = card.querySelector('dd.time-left span')?.textContent?.trim() || ''; + const daysText = card.querySelector('dd.time-left')?.textContent?.trim() || ''; + const validity = daysLeft ? `Noch ${daysLeft} Tag${daysLeft==='1'?'':'e'}` : + card.querySelector('dt.time-left')?.textContent?.includes('Brandneu') ? 'Neu' : ''; + const descEl = card.querySelector('.info div, .info'); + const description = descEl?.textContent?.trim() || ''; + const img = card.querySelector('img.offer-list-item-img'); + const badge = card.querySelector('.badge, .discount-badge, [class*="badge"]')?.textContent?.trim() || ''; + return { name, brand, price, oldPrice, retailer, dateRange, validity, description, badge, image: img?.src || null }; }); - - return results; }); // Filtern auf Ziel-Händler + nur König Pilsener diff --git a/frontend/src/tools/koepi.jsx b/frontend/src/tools/koepi.jsx index c28844a..5873e2d 100644 --- a/frontend/src/tools/koepi.jsx +++ b/frontend/src/tools/koepi.jsx @@ -35,17 +35,23 @@ function OfferCard({ offer }) { display:'flex', flexDirection:'column', }}> {/* Produktbild */} -