← back to Hollywood Import
momentum-recon3.mjs
52 lines
// Recon #3: clean structured price+sku extraction for N detail pages, reusing saved auth.
// Prints labeled price fields + item number + pattern/color + unit so we can map Momentum's
// price tiers onto our stored list_price (calibrate the ×1.448 model). Read-only.
import { chromium } from 'playwright';
import fs from 'node:fs';
const STATE = new URL('momentum-auth.json', import.meta.url).pathname;
const BASE = 'https://momentumco.com';
// color numbers to sample (first from grid + a few more discovered)
const seeds = process.argv.slice(2);
const browser = await chromium.launch({ channel: 'chrome', headless: true });
const ctx = await browser.newContext({ storageState: STATE, viewport: { width: 1400, height: 1000 } });
const page = await ctx.newPage();
// Pull color numbers off the grid if none passed
let nums = seeds;
if (!nums.length) {
await page.goto(BASE + '/products?forceSync=1', { waitUntil: 'domcontentloaded', timeout: 60000 }).catch(()=>{});
await page.waitForTimeout(5000);
for (let i=0;i<2;i++){ await page.mouse.wheel(0,5000); await page.waitForTimeout(2000); }
nums = await page.evaluate(() => [...new Set([...document.querySelectorAll('a[href*="/products/"]')]
.map(a => (a.getAttribute('href')||'').split('/products/')[1]).filter(Boolean))].slice(0, 6));
}
const results = [];
for (const num of nums) {
await page.goto(`${BASE}/products/${num}`, { waitUntil: 'domcontentloaded', timeout: 60000 }).catch(()=>{});
await page.waitForTimeout(3000);
const r = await page.evaluate(() => {
const grab = (re) => { const m = document.body.innerText.match(re); return m ? m[0] : null; };
// labeled price fields
const labels = {};
document.body.innerText.split('\n').forEach(line => {
const m = line.match(/^\s*(List Price|Net Price|Price|Cut Price|Retail|Memo|Sample|Your Price)\s*:?\s*\$?\s*([\d,]+\.\d{2})\s*(\/\s*\w+|per\s+\w+)?/i);
if (m) labels[m[1].trim()] = { value: m[2], unit: (m[3]||'').trim() };
});
// item number — look for "Item", "SKU", "Pattern #", or codes near title
const itemNo = grab(/(Item\s*#?|SKU|Pattern\s*#?|Product\s*#?)\s*:?\s*[A-Za-z0-9-]+/i);
const allCodes = [...new Set((document.body.innerText.match(/\b[A-Z0-9]{2,5}-?\d{3,6}\b/g)||[]))].slice(0,10);
return { url: location.href, title: document.title,
pattern: (document.title.split(' - ')[0]||'').trim(),
color: (document.title.split(' - ')[1]||'').trim(),
labels, itemNo, allCodes,
// capture the price widget outerHTML for one product to see exact structure
};
});
results.push(r);
}
fs.writeFileSync(new URL('recon3-out.json', import.meta.url), JSON.stringify(results, null, 2));
console.log(JSON.stringify(results, null, 2));
await browser.close();