config: Add 34 files

This commit is contained in:
2025-10-13 08:21:26 +13:00
parent ee7f11fce6
commit 9cc06ca37b
17 changed files with 1179 additions and 0 deletions

20
.gitignore vendored Normal file
View File

@@ -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

75
debug-benchmark.js Normal file
View File

@@ -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);

67
debug-benchmark2.js Normal file
View File

@@ -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);

82
debug-pbtech.js Normal file
View File

@@ -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);

69
debug-pbtech2.js Normal file
View File

@@ -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);

77
debug-pbtech3.js Normal file
View File

@@ -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);

18
package.json Normal file
View File

@@ -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"
}
}

291
public/index.html Normal file
View File

@@ -0,0 +1,291 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CPU Performance per Dollar - PB Tech NZ</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
color: white;
text-align: center;
margin-bottom: 10px;
font-size: 2.5em;
}
.subtitle {
color: rgba(255, 255, 255, 0.9);
text-align: center;
margin-bottom: 30px;
}
.card {
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
padding: 30px;
margin-bottom: 20px;
}
.btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 15px 40px;
font-size: 18px;
border-radius: 8px;
cursor: pointer;
width: 100%;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.progress-section {
display: none;
margin-top: 20px;
}
.progress-section.active {
display: block;
}
.progress-log {
background: #f5f5f5;
border-radius: 8px;
padding: 15px;
max-height: 300px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 14px;
}
.progress-item {
padding: 5px 0;
color: #333;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 3px solid rgba(102, 126, 234, 0.3);
border-radius: 50%;
border-top-color: #667eea;
animation: spin 1s linear infinite;
margin-right: 10px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.results-section {
display: none;
margin-top: 20px;
}
.results-section.active {
display: block;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
th {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px;
text-align: left;
font-weight: 600;
}
td {
padding: 15px;
border-bottom: 1px solid #eee;
}
tr:hover {
background: #f9f9f9;
}
.rank {
font-weight: 600;
color: #667eea;
font-size: 18px;
}
.metric {
font-weight: 600;
color: #28a745;
}
.price {
color: #666;
}
.benchmark {
color: #764ba2;
}
.medal {
font-size: 20px;
margin-right: 5px;
}
.error {
background: #dc3545;
color: white;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
}
</style>
</head>
<body>
<div class="container">
<h1>🏆 CPU Performance per Dollar</h1>
<p class="subtitle">Compare CPUs from PB Tech NZ by performance per dollar</p>
<div class="card">
<button id="startBtn" class="btn">🚀 Start Analysis</button>
<div id="progressSection" class="progress-section">
<h3 style="margin-bottom: 15px;">📊 Analysis Progress</h3>
<div id="progressLog" class="progress-log"></div>
</div>
<div id="resultsSection" class="results-section">
<h3 style="margin-bottom: 15px;">🎯 Results: Best Performance per Dollar</h3>
<div id="resultsTable"></div>
</div>
</div>
</div>
<script>
const startBtn = document.getElementById('startBtn');
const progressSection = document.getElementById('progressSection');
const progressLog = document.getElementById('progressLog');
const resultsSection = document.getElementById('resultsSection');
const resultsTable = document.getElementById('resultsTable');
function addProgress(message) {
const item = document.createElement('div');
item.className = 'progress-item';
item.innerHTML = `<span class="spinner"></span>${message}`;
progressLog.appendChild(item);
progressLog.scrollTop = progressLog.scrollHeight;
}
function displayResults(data) {
if (!data || data.length === 0) {
resultsTable.innerHTML = '<p>No results found.</p>';
return;
}
let html = `
<table>
<thead>
<tr>
<th>Rank</th>
<th>CPU</th>
<th>Price (NZD)</th>
<th>Benchmark</th>
<th>Performance/$</th>
</tr>
</thead>
<tbody>
`;
data.forEach((cpu, index) => {
const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '';
html += `
<tr>
<td class="rank">${medal} #${index + 1}</td>
<td>${cpu.name}</td>
<td class="price">$${cpu.price.toFixed(2)}</td>
<td class="benchmark">${cpu.benchmark.toLocaleString()}</td>
<td class="metric">${cpu.performancePerDollar.toFixed(2)}</td>
</tr>
`;
});
html += '</tbody></table>';
resultsTable.innerHTML = html;
}
startBtn.addEventListener('click', async () => {
startBtn.disabled = true;
startBtn.textContent = '⏳ Analyzing...';
progressSection.classList.add('active');
resultsSection.classList.remove('active');
progressLog.innerHTML = '';
const eventSource = new EventSource('/api/scrape');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'progress') {
addProgress(data.message);
} else if (data.type === 'complete') {
progressLog.querySelector('.spinner')?.remove();
resultsSection.classList.add('active');
displayResults(data.data);
eventSource.close();
startBtn.disabled = false;
startBtn.textContent = '🔄 Run Again';
} else if (data.type === 'error') {
progressLog.innerHTML += `<div class="error">❌ ${data.message}</div>`;
eventSource.close();
startBtn.disabled = false;
startBtn.textContent = '🔄 Try Again';
}
};
eventSource.onerror = () => {
progressLog.innerHTML += '<div class="error">❌ Connection error</div>';
eventSource.close();
startBtn.disabled = false;
startBtn.textContent = '🔄 Try Again';
};
});
</script>
</body>
</html>

114
scrapers/cpubenchmark.js Normal file
View File

@@ -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 };

88
scrapers/pbtech.js Normal file
View File

@@ -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 };

87
server.js Normal file
View File

@@ -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}`);
});

21
test-14400f.js Normal file
View File

@@ -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);

31
test-5600.js Normal file
View File

@@ -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);

53
test-all-matches.js Normal file
View File

@@ -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);

26
test-core-ultra.js Normal file
View File

@@ -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);

38
test-matching.js Normal file
View File

@@ -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);

22
test-xeon.js Normal file
View File

@@ -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);