From 40c45b49c80660376b0bec2e50d595561c55014d Mon Sep 17 00:00:00 2001 From: clucraft Date: Sat, 24 Jan 2026 04:01:16 -0500 Subject: [PATCH] Fix Best Buy scraper picking up payment plan prices - Add global filter in priceParser to reject monthly/payment plan prices - Filters: "/mo", "per month", "payments starting", "4 payments", etc. - Update Best Buy scraper to check each price element for payment terms - Prevents scraping "$25/mo" instead of actual "$229.99" price Co-Authored-By: Claude Opus 4.5 --- backend/src/services/scraper.ts | 25 ++++++++++++++++++++----- backend/src/utils/priceParser.ts | 14 ++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/backend/src/services/scraper.ts b/backend/src/services/scraper.ts index 1099b2a..b0f937a 100644 --- a/backend/src/services/scraper.ts +++ b/backend/src/services/scraper.ts @@ -381,11 +381,26 @@ const siteScrapers: SiteScraper[] = [ let price: ParsedPrice | null = null; for (const selector of priceSelectors) { - const el = $(selector).first(); - if (el.length) { - price = parsePrice(el.text().trim()); - if (price) break; - } + const elements = $(selector); + // Check each element, skip payment plan prices (contain "/mo", "per month", etc.) + elements.each((_, el) => { + if (price) return false; // Already found a valid price + const text = $(el).text().trim(); + const lowerText = text.toLowerCase(); + // Skip if it looks like a monthly payment plan + if (lowerText.includes('/mo') || + lowerText.includes('per month') || + lowerText.includes('monthly') || + lowerText.includes('financing')) { + return true; // Continue to next element + } + const parsed = parsePrice(text); + if (parsed) { + price = parsed; + return false; // Break the loop + } + }); + if (price) break; } const name = $('h1.heading-5').text().trim() || diff --git a/backend/src/utils/priceParser.ts b/backend/src/utils/priceParser.ts index f34f7d5..d645bf4 100644 --- a/backend/src/utils/priceParser.ts +++ b/backend/src/utils/priceParser.ts @@ -37,6 +37,20 @@ export function parsePrice(text: string): ParsedPrice | null { // Clean up the text const cleanText = text.trim().replace(/\s+/g, ' '); + // Reject monthly payment/financing prices (e.g., "$25/mo", "per month", "4 payments", etc.) + const lowerText = cleanText.toLowerCase(); + if (lowerText.includes('/mo') || + lowerText.includes('per month') || + lowerText.includes('monthly payment') || + lowerText.includes('a month') || + lowerText.includes('payments starting') || + lowerText.includes('payment of') || + lowerText.includes('payments of') || + /\d+\s*payments?\b/.test(lowerText) || + /\d+\s*mo\b/.test(lowerText)) { + return null; + } + for (const pattern of pricePatterns) { const match = cleanText.match(pattern); if (match && match.groups) {