config: Add 34 files
This commit is contained in:
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal 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
75
debug-benchmark.js
Normal 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
67
debug-benchmark2.js
Normal 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
82
debug-pbtech.js
Normal 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
69
debug-pbtech2.js
Normal 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
77
debug-pbtech3.js
Normal 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
18
package.json
Normal 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
291
public/index.html
Normal 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
114
scrapers/cpubenchmark.js
Normal 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
88
scrapers/pbtech.js
Normal 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
87
server.js
Normal 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
21
test-14400f.js
Normal 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
31
test-5600.js
Normal 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
53
test-all-matches.js
Normal 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
26
test-core-ultra.js
Normal 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
38
test-matching.js
Normal 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
22
test-xeon.js
Normal 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);
|
Reference in New Issue
Block a user