fix: KöPi - Bilder via Node+Cookies, NEU Badge weg, Prospekt URLs

This commit is contained in:
2026-07-09 09:02:47 +02:00
parent d37e4e768b
commit eed658ccc4
2 changed files with 32 additions and 26 deletions

View File

@@ -89,27 +89,32 @@ async function scrapeMarktguru() {
}); });
}); });
// Bilder als Base64 laden (cdn.marktguru.de blockt externe Requests) // Bilder über Node https laden mit Cookies aus dem Browser
for (const offer of offers) { const cookies = await page.cookies('https://cdn.marktguru.de');
if (!offer.image) continue; const cookieStr = cookies.map(c => `${c.name}=${c.value}`).join('; ');
try {
const imgData = await page.evaluate(async (url) => { await Promise.all(offers.map(offer => new Promise(resolve => {
try { if (!offer.image) return resolve();
const resp = await fetch(url, {credentials:'include'}); const req = require('https').get(offer.image, {
if (!resp.ok) return null; headers: {
const blob = await resp.blob(); 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
return new Promise(resolve => { 'Referer': 'https://www.marktguru.de/',
const reader = new FileReader(); 'Cookie': cookieStr,
reader.onloadend = () => resolve(reader.result); }
reader.onerror = () => resolve(null); }, res => {
reader.readAsDataURL(blob); if (res.statusCode !== 200) { offer.image = null; return resolve(); }
}); const chunks = [];
} catch { return null; } res.on('data', c => chunks.push(c));
}, offer.image); res.on('end', () => {
if (imgData) offer.image = imgData; const b64 = Buffer.concat(chunks).toString('base64');
else offer.image = null; const mime = res.headers['content-type'] || 'image/webp';
} catch { offer.image = null; } offer.image = `data:${mime};base64,${b64}`;
} resolve();
});
});
req.on('error', () => { offer.image = null; resolve(); });
req.setTimeout(8000, () => { req.destroy(); offer.image = null; resolve(); });
})));
// Filtern auf Ziel-Händler + nur König Pilsener // Filtern auf Ziel-Händler + nur König Pilsener
const filtered = offers.filter(o => { const filtered = offers.filter(o => {
@@ -209,7 +214,7 @@ async function scrapeProspekte() {
validFrom: c.validFrom || null, validFrom: c.validFrom || null,
validTo: c.validUntil || null, validTo: c.validUntil || null,
pageCount: c.pageCount || null, pageCount: c.pageCount || null,
url: `https://www.kaufda.de/webapp/brochure/${id}`, url: `https://www.kaufda.de/Prospekte/${encodeURIComponent(pub.replace(/\s+/g,'-'))}?brochureId=${id}`,
isTarget: isTargetRetailer(pub), isTarget: isTargetRetailer(pub),
badges: c.contentBadges?.map(b => b.name) || [], badges: c.contentBadges?.map(b => b.name) || [],
}); });

View File

@@ -128,10 +128,11 @@ function ProspektCard({ b }) {
onError={e => { e.target.style.display='none'; }} onError={e => { e.target.style.display='none'; }}
/> />
)} )}
<div style={{ position:'absolute', top:6, left:6, display:'flex', gap:4 }}> {expiring && (
{isNew && <span style={{ background:'#4ecdc4', color:'#000', borderRadius:4, padding:'2px 6px', fontSize:9, fontFamily:'monospace', fontWeight:700 }}>NEU</span>} <div style={{ position:'absolute', top:6, left:6 }}>
{expiring && <span style={{ background:'#ef4444', color:'#fff', borderRadius:4, padding:'2px 6px', fontSize:9, fontFamily:'monospace', fontWeight:700 }}>ENDET BALD</span>} <span style={{ background:'#ef4444', color:'#fff', borderRadius:4, padding:'2px 6px', fontSize:9, fontFamily:'monospace', fontWeight:700 }}>ENDET BALD</span>
</div> </div>
)}
</div> </div>
<div style={{ padding:'10px 12px' }}> <div style={{ padding:'10px 12px' }}>
<div style={{ display:'inline-block', background:`${color}22`, border:`1px solid ${color}44`, <div style={{ display:'inline-block', background:`${color}22`, border:`1px solid ${color}44`,