diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d29a5ca
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,20 @@
+# Dependencies
+node_modules/
+
+# Logs
+*.log
+npm-debug.log*
+
+# OS files
+.DS_Store
+Thumbs.db
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# Environment variables
+.env
+.env.local
diff --git a/debug-benchmark.js b/debug-benchmark.js
new file mode 100644
index 0000000..2d7e05c
--- /dev/null
+++ b/debug-benchmark.js
@@ -0,0 +1,75 @@
+const puppeteer = require('puppeteer');
+
+async function debugBenchmark() {
+ const browser = await puppeteer.launch({
+ headless: 'new',
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
+ });
+
+ const page = await browser.newPage();
+ await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
+
+ console.log('Loading CPU benchmark page...');
+ await page.goto('https://www.cpubenchmark.net/cpu_list.php', {
+ waitUntil: 'networkidle2',
+ timeout: 60000
+ });
+
+ console.log('\n=== ANALYZING TABLE STRUCTURE ===');
+
+ const tableInfo = await page.evaluate(() => {
+ const tables = document.querySelectorAll('table');
+ const rows = document.querySelectorAll('table tr');
+
+ // Get headers
+ const headerRow = document.querySelector('table tr');
+ const headers = Array.from(headerRow?.querySelectorAll('th, td') || [])
+ .map(h => h.textContent.trim());
+
+ // Get first 5 data rows
+ const samples = [];
+ for (let i = 1; i < Math.min(6, rows.length); i++) {
+ const cells = rows[i].querySelectorAll('td');
+ samples.push(Array.from(cells).map(c => c.textContent.trim()));
+ }
+
+ // Search for Intel Core i7-14700
+ const searchResults = [];
+ rows.forEach(row => {
+ const cells = row.querySelectorAll('td');
+ if (cells.length >= 2) {
+ const name = cells[0]?.textContent?.trim();
+ if (name && name.toLowerCase().includes('14700')) {
+ const score = cells[1]?.textContent?.trim();
+ searchResults.push({ name, score });
+ }
+ }
+ });
+
+ return {
+ tableCount: tables.length,
+ rowCount: rows.length,
+ headers: headers,
+ samples: samples,
+ search14700: searchResults
+ };
+ });
+
+ console.log(`\nTables found: ${tableInfo.tableCount}`);
+ console.log(`Rows found: ${tableInfo.rowCount}`);
+ console.log(`\nHeaders: ${JSON.stringify(tableInfo.headers)}`);
+
+ console.log('\nFirst 5 rows:');
+ tableInfo.samples.forEach((row, i) => {
+ console.log(`Row ${i + 1}: ${JSON.stringify(row)}`);
+ });
+
+ console.log('\n=== SEARCH FOR 14700 CPUs ===');
+ tableInfo.search14700.forEach(cpu => {
+ console.log(`${cpu.name}: ${cpu.score}`);
+ });
+
+ await browser.close();
+}
+
+debugBenchmark().catch(console.error);
diff --git a/debug-benchmark2.js b/debug-benchmark2.js
new file mode 100644
index 0000000..2556bee
--- /dev/null
+++ b/debug-benchmark2.js
@@ -0,0 +1,67 @@
+const puppeteer = require('puppeteer');
+
+async function debugBenchmark() {
+ const browser = await puppeteer.launch({
+ headless: 'new',
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
+ });
+
+ const page = await browser.newPage();
+ await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
+
+ console.log('Loading CPU benchmark page...');
+ await page.goto('https://www.cpubenchmark.net/cpu_list.php', {
+ waitUntil: 'networkidle2',
+ timeout: 60000
+ });
+
+ console.log('\n=== CHECKING MAIN TABLE ===');
+
+ const mainTable = await page.evaluate(() => {
+ // Find the main data table - it's usually the one with the most rows
+ const tables = Array.from(document.querySelectorAll('table'));
+ const mainTable = tables.reduce((largest, table) => {
+ const rowCount = table.querySelectorAll('tr').length;
+ const largestCount = largest?.querySelectorAll('tr').length || 0;
+ return rowCount > largestCount ? table : largest;
+ });
+
+ if (!mainTable) return null;
+
+ // Get first row to understand structure
+ const firstDataRow = mainTable.querySelector('tr:nth-child(2), tbody tr:first-child');
+ const cells = firstDataRow?.querySelectorAll('td, th');
+
+ const firstRowData = cells ? Array.from(cells).map(c => c.textContent.trim()) : [];
+
+ // Try to extract data using different methods
+ const rows = mainTable.querySelectorAll('tr');
+ const method1 = []; // Using td only
+ const method2 = []; // Using specific columns
+
+ for (let i = 0; i < Math.min(5, rows.length); i++) {
+ const tds = rows[i].querySelectorAll('td');
+ if (tds.length >= 2) {
+ method1.push({
+ cpu: tds[0]?.textContent?.trim(),
+ score: tds[1]?.textContent?.trim()
+ });
+ }
+ }
+
+ return {
+ firstRowData,
+ method1
+ };
+ });
+
+ console.log('First row data:', mainTable.firstRowData);
+ console.log('\nMethod 1 (td[0] = name, td[1] = score):');
+ mainTable.method1.forEach((row, i) => {
+ console.log(` ${i}: ${row.cpu} = ${row.score}`);
+ });
+
+ await browser.close();
+}
+
+debugBenchmark().catch(console.error);
diff --git a/debug-pbtech.js b/debug-pbtech.js
new file mode 100644
index 0000000..815752b
--- /dev/null
+++ b/debug-pbtech.js
@@ -0,0 +1,82 @@
+const puppeteer = require('puppeteer');
+
+async function debugPBTech() {
+ const browser = await puppeteer.launch({
+ headless: 'new',
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
+ });
+
+ const page = await browser.newPage();
+ await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
+
+ console.log('Loading PB Tech CPU page...');
+ await page.goto('https://www.pbtech.co.nz/category/components/cpus', {
+ waitUntil: 'networkidle2',
+ timeout: 30000
+ });
+
+ console.log('\n=== PAGE TITLE ===');
+ const title = await page.title();
+ console.log(title);
+
+ console.log('\n=== EXTRACTING FIRST FEW PRODUCTS ===');
+
+ const products = await page.evaluate(() => {
+ const results = [];
+
+ // Try to find all possible product containers
+ const selectors = [
+ '.product-item',
+ '.product-card',
+ '[class*="product"]',
+ '.item',
+ 'article',
+ '[data-product-id]',
+ '.productbox',
+ '.productcard'
+ ];
+
+ for (const selector of selectors) {
+ const elements = document.querySelectorAll(selector);
+ if (elements.length > 0) {
+ results.push({
+ selector: selector,
+ count: elements.length,
+ sample: elements[0]?.outerHTML?.substring(0, 500)
+ });
+ }
+ }
+
+ return results;
+ });
+
+ console.log('\nFound product containers:');
+ products.forEach(p => {
+ console.log(`\nSelector: ${p.selector}`);
+ console.log(`Count: ${p.count}`);
+ console.log(`Sample HTML: ${p.sample}...`);
+ });
+
+ console.log('\n=== EXTRACTING DETAILED PRODUCT INFO ===');
+
+ const detailedInfo = await page.evaluate(() => {
+ // Get first product-like element
+ const product = document.querySelector('.product-item, .product-card, [class*="product"], .productbox, .productcard, article');
+
+ if (!product) return { error: 'No product found' };
+
+ return {
+ outerHTML: product.outerHTML.substring(0, 1500),
+ textContent: product.textContent.substring(0, 500),
+ classes: product.className,
+ id: product.id
+ };
+ });
+
+ console.log('\nFirst product details:');
+ console.log(JSON.stringify(detailedInfo, null, 2));
+
+ await browser.close();
+}
+
+debugPBTech().catch(console.error);
diff --git a/debug-pbtech2.js b/debug-pbtech2.js
new file mode 100644
index 0000000..d00e365
--- /dev/null
+++ b/debug-pbtech2.js
@@ -0,0 +1,69 @@
+const puppeteer = require('puppeteer');
+
+async function debugPBTech() {
+ const browser = await puppeteer.launch({
+ headless: 'new',
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
+ });
+
+ const page = await browser.newPage();
+ await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
+
+ console.log('Loading PB Tech CPU page...');
+ await page.goto('https://www.pbtech.co.nz/category/components/cpus', {
+ waitUntil: 'networkidle2',
+ timeout: 30000
+ });
+
+ console.log('\n=== LOOKING FOR ACTUAL PRODUCT CARDS ===');
+
+ const info = await page.evaluate(() => {
+ // Look for cards/items that have both price and product ID
+ const buttons = document.querySelectorAll('[data-product-id]');
+ const results = [];
+
+ buttons.forEach((btn, index) => {
+ if (index >= 3) return; // Just first 3
+
+ const productId = btn.getAttribute('data-product-id');
+ const price = btn.getAttribute('data-price');
+
+ // Try to find parent card/container
+ let parent = btn.closest('.card, .item, [class*="card"], article, .grid-item, .product-grid-item');
+ if (!parent) {
+ parent = btn.parentElement?.parentElement?.parentElement; // Go up a few levels
+ }
+
+ if (parent) {
+ const nameEl = parent.querySelector('h3, h4, h5, .title, [class*="title"], [class*="name"], a[title]');
+ const priceEl = parent.querySelector('.price, [class*="price"], .cost');
+
+ results.push({
+ productId: productId,
+ buttonPrice: price,
+ name: nameEl?.textContent?.trim() || nameEl?.getAttribute('title'),
+ priceText: priceEl?.textContent?.trim(),
+ parentHTML: parent.outerHTML.substring(0, 800),
+ parentClass: parent.className
+ });
+ }
+ });
+
+ return results;
+ });
+
+ console.log('\nProduct details:');
+ info.forEach((p, i) => {
+ console.log(`\n--- Product ${i + 1} ---`);
+ console.log(`Product ID: ${p.productId}`);
+ console.log(`Button Price: $${p.buttonPrice}`);
+ console.log(`Name: ${p.name}`);
+ console.log(`Price Text: ${p.priceText}`);
+ console.log(`Parent Class: ${p.parentClass}`);
+ console.log(`Parent HTML: ${p.parentHTML}...`);
+ });
+
+ await browser.close();
+}
+
+debugPBTech().catch(console.error);
diff --git a/debug-pbtech3.js b/debug-pbtech3.js
new file mode 100644
index 0000000..c27149c
--- /dev/null
+++ b/debug-pbtech3.js
@@ -0,0 +1,77 @@
+const puppeteer = require('puppeteer');
+
+async function debugPBTech() {
+ const browser = await puppeteer.launch({
+ headless: 'new',
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
+ });
+
+ const page = await browser.newPage();
+ await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
+
+ console.log('Loading PB Tech CPU page...');
+ await page.goto('https://www.pbtech.co.nz/category/components/cpus', {
+ waitUntil: 'networkidle2',
+ timeout: 30000
+ });
+
+ console.log('\n=== EXTRACTING PRODUCT INFO BETTER ===');
+
+ const info = await page.evaluate(() => {
+ const buttons = document.querySelectorAll('[data-product-id]');
+ const results = [];
+
+ buttons.forEach((btn, index) => {
+ if (index >= 3) return;
+
+ const productId = btn.getAttribute('data-product-id');
+ const price = btn.getAttribute('data-price');
+
+ // Go up several levels to find the card
+ let card = btn;
+ for (let i = 0; i < 10; i++) {
+ card = card.parentElement;
+ if (!card) break;
+
+ // Check if this container has product info
+ const links = card.querySelectorAll('a');
+ const hasProductLink = Array.from(links).some(a =>
+ a.href && a.href.includes(productId)
+ );
+
+ if (hasProductLink) {
+ // Found the card!
+ const productLink = Array.from(links).find(a => a.href && a.href.includes(productId));
+
+ results.push({
+ productId: productId,
+ price: price,
+ name: productLink?.textContent?.trim() || productLink?.title,
+ href: productLink?.href,
+ cardHTML: card.outerHTML.substring(0, 1200),
+ cardClass: card.className,
+ level: i
+ });
+ break;
+ }
+ }
+ });
+
+ return results;
+ });
+
+ console.log('\nFound products:');
+ info.forEach((p, i) => {
+ console.log(`\n--- Product ${i + 1} ---`);
+ console.log(`Product ID: ${p.productId}`);
+ console.log(`Price: $${p.price}`);
+ console.log(`Name: ${p.name}`);
+ console.log(`Link: ${p.href}`);
+ console.log(`Card level: ${p.level} parents up`);
+ console.log(`Card class: ${p.cardClass}`);
+ });
+
+ await browser.close();
+}
+
+debugPBTech().catch(console.error);
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..ff3d743
--- /dev/null
+++ b/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "cpu-price-performance",
+ "version": "1.0.0",
+ "description": "Compare CPUs from PB Tech NZ by performance per dollar",
+ "main": "server.js",
+ "scripts": {
+ "start": "node server.js",
+ "dev": "node server.js"
+ },
+ "keywords": ["cpu", "benchmark", "price", "performance"],
+ "author": "",
+ "license": "MIT",
+ "dependencies": {
+ "express": "^4.18.2",
+ "puppeteer": "^24.15.0",
+ "cors": "^2.8.5"
+ }
+}
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..d62d10f
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,291 @@
+
+
+
+
+
+ CPU Performance per Dollar - PB Tech NZ
+
+
+
+
+
🏆 CPU Performance per Dollar
+
Compare CPUs from PB Tech NZ by performance per dollar
+
+
+
+
+
+
📊 Analysis Progress
+
+
+
+
+
🎯 Results: Best Performance per Dollar
+
+
+
+
+
+
+
+
diff --git a/scrapers/cpubenchmark.js b/scrapers/cpubenchmark.js
new file mode 100644
index 0000000..e76000f
--- /dev/null
+++ b/scrapers/cpubenchmark.js
@@ -0,0 +1,114 @@
+const puppeteer = require('puppeteer');
+
+// Extract CPU model identifier with enough context to avoid false matches
+function extractCPUModel(name) {
+ // Intel Core Ultra patterns: Core Ultra 5/7/9 XXXX
+ const ultraMatch = name.match(/\b(Core\s+Ultra\s+[579]\s+\w+[A-Z]?)\b/i);
+ if (ultraMatch) return ultraMatch[1];
+
+ // Intel patterns: Core i3-XXXX, Core i5-XXXX, etc. (include "Core" for specificity)
+ // Handle both "Core i5-14400F" and "Core i5 14400F" (with or without dash)
+ const intelMatch = name.match(/\b(Core\s+i[3579])-?\s*(\w+[A-Z]?)\b/i);
+ if (intelMatch) return `${intelMatch[1]}-${intelMatch[2]}`;
+
+ // Intel Xeon patterns: Xeon Silver/Gold/Platinum XXXX
+ const xeonMatch = name.match(/\b(Xeon\s+(?:Silver|Gold|Platinum|Bronze)?\s*\w+[A-Z]?)\b/i);
+ if (xeonMatch) return xeonMatch[1];
+
+ // AMD Ryzen patterns: Ryzen X XXXXX (include series number for specificity)
+ const ryzenMatch = name.match(/\b(Ryzen\s+[3579]\s+\w+)/i);
+ if (ryzenMatch) return ryzenMatch[1];
+
+ // AMD Threadripper patterns: Threadripper XXXX
+ const threadripperMatch = name.match(/\b(Threadripper\s+\w+)/i);
+ if (threadripperMatch) return threadripperMatch[1];
+
+ return null;
+}
+
+async function fetchBenchmarkData(progressCallback) {
+ let browser;
+ try {
+ progressCallback('Loading CPU benchmark database...');
+ browser = await puppeteer.launch({
+ headless: 'new',
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
+ });
+
+ const page = await browser.newPage();
+ await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
+
+ progressCallback('Fetching benchmark scores...');
+ await page.goto('https://www.cpubenchmark.net/cpu_list.php', {
+ waitUntil: 'networkidle2',
+ timeout: 60000
+ });
+
+ progressCallback('Parsing benchmark data...');
+
+ // Extract benchmark data from the table
+ const benchmarks = await page.evaluate(() => {
+ const data = {};
+ const rows = document.querySelectorAll('table tr');
+
+ rows.forEach(row => {
+ const cells = row.querySelectorAll('td');
+ if (cells.length >= 2) {
+ const cpuName = cells[0]?.textContent?.trim();
+ const cpuMark = cells[1]?.textContent?.trim();
+
+ if (cpuName && cpuMark) {
+ const score = parseInt(cpuMark.replace(/,/g, ''));
+ if (!isNaN(score) && score > 0) {
+ data[cpuName] = score;
+ }
+ }
+ }
+ });
+
+ return data;
+ });
+
+ const count = Object.keys(benchmarks).length;
+ progressCallback(`Loaded ${count} CPU benchmarks`);
+
+ await browser.close();
+ return benchmarks;
+
+ } catch (error) {
+ if (browser) {
+ await browser.close();
+ }
+ progressCallback(`Error fetching benchmarks: ${error.message}`);
+ throw error;
+ }
+}
+
+function findBenchmarkScore(cpuName, benchmarkData) {
+ // Try exact match first
+ if (benchmarkData[cpuName]) {
+ return benchmarkData[cpuName];
+ }
+
+ // Extract the CPU model identifier
+ const model = extractCPUModel(cpuName);
+ if (!model) return null;
+
+ // Search for CPUs containing this model identifier
+ const modelLower = model.toLowerCase();
+
+ for (const [benchName, score] of Object.entries(benchmarkData)) {
+ const benchLower = benchName.toLowerCase();
+
+ // Check if the benchmark name contains the exact model
+ // Use word boundaries to avoid partial matches
+ const modelRegex = new RegExp(`\\b${modelLower.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i');
+ if (modelRegex.test(benchLower)) {
+ return score;
+ }
+ }
+
+ return null;
+}
+
+module.exports = { fetchBenchmarkData, findBenchmarkScore };
diff --git a/scrapers/pbtech.js b/scrapers/pbtech.js
new file mode 100644
index 0000000..2e296f0
--- /dev/null
+++ b/scrapers/pbtech.js
@@ -0,0 +1,88 @@
+const puppeteer = require('puppeteer');
+
+async function scrapePBTechCPUs(progressCallback) {
+ let browser;
+ try {
+ progressCallback('Launching browser...');
+ browser = await puppeteer.launch({
+ headless: 'new',
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
+ });
+
+ progressCallback('Opening PB Tech CPU page...');
+ const page = await browser.newPage();
+
+ // Set a realistic user agent
+ await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
+
+ progressCallback('Loading CPU listings...');
+ await page.goto('https://www.pbtech.co.nz/category/components/cpus', {
+ waitUntil: 'networkidle2',
+ timeout: 30000
+ });
+
+ progressCallback('Extracting CPU data...');
+
+ // Extract CPU data from the page
+ const cpus = await page.evaluate(() => {
+ const products = [];
+
+ // Find all "Add to cart" buttons which have product data
+ const buttons = document.querySelectorAll('[data-product-id]');
+
+ buttons.forEach(btn => {
+ try {
+ const productId = btn.getAttribute('data-product-id');
+ const price = parseFloat(btn.getAttribute('data-price'));
+
+ // Find the product link that contains this product ID
+ let card = btn;
+ let name = null;
+
+ // Go up the DOM tree to find the card containing the product link
+ for (let i = 0; i < 10; i++) {
+ card = card.parentElement;
+ if (!card) break;
+
+ const links = card.querySelectorAll('a');
+ const productLink = Array.from(links).find(a =>
+ a.href && a.href.includes(productId)
+ );
+
+ if (productLink) {
+ name = productLink.textContent?.trim();
+ break;
+ }
+ }
+
+ if (name && price && !isNaN(price)) {
+ products.push({
+ name: name,
+ price: price,
+ inStock: true, // If there's an "Add to cart" button, it's in stock
+ source: 'pbtech'
+ });
+ }
+ } catch (err) {
+ // Skip problematic elements
+ }
+ });
+
+ return products;
+ });
+
+ progressCallback(`Found ${cpus.length} CPUs from PB Tech`);
+
+ await browser.close();
+ return cpus;
+
+ } catch (error) {
+ if (browser) {
+ await browser.close();
+ }
+ progressCallback(`Error scraping PB Tech: ${error.message}`);
+ throw error;
+ }
+}
+
+module.exports = { scrapePBTechCPUs };
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..df53f0c
--- /dev/null
+++ b/server.js
@@ -0,0 +1,87 @@
+const express = require('express');
+const cors = require('cors');
+const path = require('path');
+const { scrapePBTechCPUs } = require('./scrapers/pbtech');
+const { fetchBenchmarkData, findBenchmarkScore } = require('./scrapers/cpubenchmark');
+
+const app = express();
+const PORT = process.env.PORT || 3000;
+
+app.use(cors());
+app.use(express.static('public'));
+
+// Server-Sent Events endpoint for real-time progress
+app.get('/api/scrape', async (req, res) => {
+ // Set headers for SSE
+ res.setHeader('Content-Type', 'text/event-stream');
+ res.setHeader('Cache-Control', 'no-cache');
+ res.setHeader('Connection', 'keep-alive');
+
+ // Helper function to send progress updates
+ const sendProgress = (message) => {
+ res.write(`data: ${JSON.stringify({ type: 'progress', message })}\n\n`);
+ };
+
+ try {
+ sendProgress('Starting CPU comparison...');
+
+ // Fetch CPUs from PB Tech
+ sendProgress('Step 1/3: Fetching CPUs from PB Tech...');
+ const cpus = await scrapePBTechCPUs(sendProgress);
+
+ if (cpus.length === 0) {
+ sendProgress('No CPUs found on PB Tech. Please check the website.');
+ res.write(`data: ${JSON.stringify({ type: 'error', message: 'No CPUs found' })}\n\n`);
+ res.end();
+ return;
+ }
+
+ // Fetch benchmark data
+ sendProgress('Step 2/3: Fetching CPU benchmarks...');
+ const benchmarkData = await fetchBenchmarkData(sendProgress);
+
+ // Match CPUs with benchmarks and calculate performance per dollar
+ sendProgress('Step 3/3: Calculating performance per dollar...');
+
+ const results = [];
+ for (let i = 0; i < cpus.length; i++) {
+ const cpu = cpus[i];
+ const benchmark = findBenchmarkScore(cpu.name, benchmarkData);
+
+ if (benchmark) {
+ const performancePerDollar = benchmark / cpu.price;
+ results.push({
+ name: cpu.name,
+ price: cpu.price,
+ benchmark: benchmark,
+ performancePerDollar: performancePerDollar,
+ inStock: cpu.inStock
+ });
+ }
+
+ if ((i + 1) % 5 === 0) {
+ sendProgress(`Processed ${i + 1}/${cpus.length} CPUs...`);
+ }
+ }
+
+ // Sort by performance per dollar (descending)
+ results.sort((a, b) => b.performancePerDollar - a.performancePerDollar);
+
+ sendProgress(`Found ${results.length} CPUs with benchmark data!`);
+ sendProgress('Analysis complete!');
+
+ // Send final results
+ res.write(`data: ${JSON.stringify({ type: 'complete', data: results })}\n\n`);
+ res.end();
+
+ } catch (error) {
+ console.error('Scraping error:', error);
+ sendProgress(`Error: ${error.message}`);
+ res.write(`data: ${JSON.stringify({ type: 'error', message: error.message })}\n\n`);
+ res.end();
+ }
+});
+
+app.listen(PORT, () => {
+ console.log(`Server running on http://localhost:${PORT}`);
+});
diff --git a/test-14400f.js b/test-14400f.js
new file mode 100644
index 0000000..089695a
--- /dev/null
+++ b/test-14400f.js
@@ -0,0 +1,21 @@
+const { fetchBenchmarkData } = require('./scrapers/cpubenchmark');
+
+async function test() {
+ const benchmarkData = await fetchBenchmarkData(console.log);
+
+ console.log('\n=== Searching for 14400F ===');
+ Object.keys(benchmarkData)
+ .filter(name => name.includes('14400'))
+ .forEach(name => {
+ console.log(`${name}: ${benchmarkData[name]}`);
+ });
+
+ console.log('\n=== Searching for 14700F ===');
+ Object.keys(benchmarkData)
+ .filter(name => name.includes('14700'))
+ .forEach(name => {
+ console.log(`${name}: ${benchmarkData[name]}`);
+ });
+}
+
+test().catch(console.error);
diff --git a/test-5600.js b/test-5600.js
new file mode 100644
index 0000000..d20140b
--- /dev/null
+++ b/test-5600.js
@@ -0,0 +1,31 @@
+const { fetchBenchmarkData, findBenchmarkScore } = require('./scrapers/cpubenchmark');
+
+async function test5600() {
+ console.log('Fetching benchmark data...');
+ const benchmarkData = await fetchBenchmarkData(console.log);
+
+ const testCPU = "AMD Ryzen 5 5600 CPU 6 Core / 12 Thread - Max Boost 4.4GHz - 35MB Cache - AM4 Socket - 65W TDP - Heatsink Required";
+
+ console.log(`\nTesting: ${testCPU.substring(0, 50)}...`);
+
+ const score = findBenchmarkScore(testCPU, benchmarkData);
+ console.log(`Found Score: ${score}`);
+
+ // Check what CPUs have "5600" in them
+ console.log('\nAll CPUs with "5600" in name:');
+ Object.keys(benchmarkData)
+ .filter(name => name.includes('5600'))
+ .forEach(name => {
+ console.log(` ${name}: ${benchmarkData[name]}`);
+ });
+
+ // Check specifically for Ryzen 5 5600
+ console.log('\nCPUs with "Ryzen 5 5600":');
+ Object.keys(benchmarkData)
+ .filter(name => name.includes('Ryzen 5 5600'))
+ .forEach(name => {
+ console.log(` ${name}: ${benchmarkData[name]}`);
+ });
+}
+
+test5600().catch(console.error);
diff --git a/test-all-matches.js b/test-all-matches.js
new file mode 100644
index 0000000..19b4298
--- /dev/null
+++ b/test-all-matches.js
@@ -0,0 +1,53 @@
+const { scrapePBTechCPUs } = require('./scrapers/pbtech');
+const { fetchBenchmarkData, findBenchmarkScore } = require('./scrapers/cpubenchmark');
+
+async function testAllMatches() {
+ console.log('Fetching CPUs from PB Tech...');
+ const cpus = await scrapePBTechCPUs(console.log);
+
+ console.log('\nFetching benchmark data...');
+ const benchmarkData = await fetchBenchmarkData(console.log);
+
+ console.log(`\n=== Testing ${cpus.length} CPUs ===\n`);
+
+ const matched = [];
+ const unmatched = [];
+
+ cpus.forEach(cpu => {
+ const score = findBenchmarkScore(cpu.name, benchmarkData);
+ if (score) {
+ matched.push({ ...cpu, score });
+ } else {
+ unmatched.push(cpu);
+ }
+ });
+
+ console.log(`✅ Matched: ${matched.length}/${cpus.length}`);
+ console.log(`❌ Unmatched: ${unmatched.length}/${cpus.length}\n`);
+
+ if (unmatched.length > 0) {
+ console.log('=== UNMATCHED CPUs (first 10) ===');
+ unmatched.slice(0, 10).forEach(cpu => {
+ console.log(`\nName: ${cpu.name.substring(0, 80)}`);
+ console.log(`Price: $${cpu.price}`);
+
+ // Try to find similar CPUs in the benchmark database
+ const searchTerms = cpu.name.match(/\b(Ryzen|Core|Threadripper|Pentium|Celeron|Athlon)\s+\w+\s+\w+/i);
+ if (searchTerms) {
+ const similar = Object.keys(benchmarkData)
+ .filter(name => name.toLowerCase().includes(searchTerms[0].toLowerCase().split(' ')[0]))
+ .slice(0, 3);
+ if (similar.length > 0) {
+ console.log(`Similar in DB: ${similar.join(', ')}`);
+ }
+ }
+ });
+ }
+
+ console.log('\n=== SUCCESSFULLY MATCHED (first 10) ===');
+ matched.slice(0, 10).forEach(cpu => {
+ console.log(`${cpu.name.substring(0, 60)}... → ${cpu.score} (${(cpu.score / cpu.price).toFixed(2)} perf/$)`);
+ });
+}
+
+testAllMatches().catch(console.error);
diff --git a/test-core-ultra.js b/test-core-ultra.js
new file mode 100644
index 0000000..757e38a
--- /dev/null
+++ b/test-core-ultra.js
@@ -0,0 +1,26 @@
+const { fetchBenchmarkData } = require('./scrapers/cpubenchmark');
+
+async function testCoreUltra() {
+ console.log('Fetching benchmark data...');
+ const benchmarkData = await fetchBenchmarkData(console.log);
+
+ console.log('\n=== Searching for Core Ultra CPUs ===\n');
+
+ // Search for different variations
+ const searches = ['Core Ultra', 'Ultra 5', 'Ultra 7', 'Ultra 9', '265K', '285K', '245K'];
+
+ searches.forEach(search => {
+ const results = Object.keys(benchmarkData)
+ .filter(name => name.includes(search))
+ .slice(0, 5);
+
+ if (results.length > 0) {
+ console.log(`\n"${search}" found in:`);
+ results.forEach(name => {
+ console.log(` ${name}: ${benchmarkData[name]}`);
+ });
+ }
+ });
+}
+
+testCoreUltra().catch(console.error);
diff --git a/test-matching.js b/test-matching.js
new file mode 100644
index 0000000..523f1fb
--- /dev/null
+++ b/test-matching.js
@@ -0,0 +1,38 @@
+const { fetchBenchmarkData, findBenchmarkScore } = require('./scrapers/cpubenchmark');
+
+async function testMatching() {
+ console.log('Fetching benchmark data...');
+
+ const benchmarkData = await fetchBenchmarkData(console.log);
+
+ console.log(`\nLoaded ${Object.keys(benchmarkData).length} benchmarks\n`);
+
+ // Test with sample CPU names from PB Tech
+ const testCPUs = [
+ "AMD Ryzen 7 9800X3D CPU 8 Core / 16 Thread - 104MB Total Cache - AM5 Socket - 120W TDP - AMD Radeon Graphics - Heatsink Required",
+ "Intel Core i7-14700F CPU 20 Core (8P 12E) / 28 Thread - Max Boost 5.4GHz - 33MB Cache - LGA1700 Socket - 65W TDP - Heatsink Required",
+ "AMD Ryzen 5 9600X CPU 6 Core / 12 Thread - Max Boost 5.4GHz - 38MB Cache - AM5 Socket - 65W TDP - AMD Radeon Graphics - Heatsink Required",
+ "Intel Core i9-14900K CPU 24 Core (8P 16E) / 32 Thread - Max Boost 6.0GHz - 36MB Cache - LGA1700 Socket - 125W TDP - Intel UHD Graphics 770 - Heatsink Required"
+ ];
+
+ testCPUs.forEach(cpu => {
+ console.log(`\nPB Tech Name: ${cpu.substring(0, 60)}...`);
+ const score = findBenchmarkScore(cpu, benchmarkData);
+ console.log(`Found Score: ${score}`);
+
+ // Also check what it should be
+ const cpuModel = cpu.match(/(?:Ryzen [579] \w+|Core i[3579]-\w+)/)?.[0];
+ console.log(`Expected Model: ${cpuModel}`);
+ if (cpuModel) {
+ const exactMatches = Object.keys(benchmarkData)
+ .filter(name => name.includes(cpuModel))
+ .slice(0, 3);
+ console.log(`Similar in database: ${exactMatches.join(', ')}`);
+ exactMatches.forEach(match => {
+ console.log(` - ${match}: ${benchmarkData[match]}`);
+ });
+ }
+ });
+}
+
+testMatching().catch(console.error);
diff --git a/test-xeon.js b/test-xeon.js
new file mode 100644
index 0000000..5a90dfa
--- /dev/null
+++ b/test-xeon.js
@@ -0,0 +1,22 @@
+const { fetchBenchmarkData } = require('./scrapers/cpubenchmark');
+
+async function test() {
+ const benchmarkData = await fetchBenchmarkData(console.log);
+
+ console.log('\n=== Searching for Xeon Silver 4510 ===');
+ Object.keys(benchmarkData)
+ .filter(name => name.includes('4510'))
+ .forEach(name => {
+ console.log(`${name}: ${benchmarkData[name]}`);
+ });
+
+ console.log('\n=== Searching for "Xeon Silver" ===');
+ Object.keys(benchmarkData)
+ .filter(name => name.includes('Xeon Silver'))
+ .slice(0, 10)
+ .forEach(name => {
+ console.log(`${name}: ${benchmarkData[name]}`);
+ });
+}
+
+test().catch(console.error);