// Run in the browser console on nssfasteners.co.za after login. // Fetches all Grade 8 (101 items) + A2 (21 items) hex nut DIN934 product pages, // parses the hidden unit prices out of the HTML, builds PosDescription strings, // and triggers a CSV download. (async () => { const BASE = 'https://nssfasteners.co.za/nuts/hex-nuts/hex-nut-din934.html'; const FILTERS = [ { grade: '8', gradeId: '771', expectedCount: 101 }, { grade: 'A2', gradeId: '773', expectedCount: 21 }, ]; // ---- helpers ---- const fetchHtml = async (url) => { const res = await fetch(url, { credentials: 'include' }); if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`); return await res.text(); }; const parsePage = (html) => { const doc = new DOMParser().parseFromString(html, 'text/html'); const items = Array.from(doc.querySelectorAll('.product-item')); return items.map(item => { const titleEl = item.querySelector('.items-center.justify-center.text-primary'); const title = titleEl ? titleEl.textContent.trim() : ''; const sku = title.includes(' - ') ? title.split(' - ').pop().trim() : ''; const linkEl = item.querySelector('a.product-item-photo, a.product.photo'); const url = linkEl ? linkEl.getAttribute('href') : ''; // pull the description block (Material / Diameter / Thread / Grade / Finish / Pack) const block = (item.textContent || '').replace(/\s+/g, ' '); const get = (label) => { const m = block.match(new RegExp(label + '\\s*:?\\s*([^\\s].*?)(?=\\s+(?:Diameter|Thread|Grade|Finish|Sold|Metric|Hi-Tensile|Stainless|Mild|Brass|$))', 'i')); return m ? m[1].trim() : ''; }; const material = /Hi-Tensile/i.test(block) ? 'Hi-Tensile' : /Stainless Steel/i.test(block) ? 'Stainless Steel' : /Mild Steel/i.test(block) ? 'Mild Steel' : /Brass/i.test(block) ? 'Brass' : ''; const diameter = get('Diameter'); const thread = get('Thread'); const grade = get('Grade'); const finish = get('Finish'); const pack = /Sold as a Single Piece/i.test(block) ? 'Single Piece' : /Sold as a Box/i.test(block) ? 'Box' : /Sold as a Pack/i.test(block) ? 'Pack' : ''; // Unit price is in the HTML but hidden with display:none const unitPriceEl = item.querySelector('.unit-price'); const priceMatch = unitPriceEl ? unitPriceEl.textContent.match(/R\s*([\d.]+)/) : null; const unitPrice = priceMatch ? priceMatch[1] : ''; // Description is the parent product type (e.g. "Hex Nut DIN934") const desc = title.includes(' - ') ? title.split(' - ')[0].trim() : ''; // Build PosDescription like: "M39 NUT 8 PLN" // diameter -> "M{value}", normalised const sizeMatch = diameter.match(/(\d+(?:\.\d+)?)\s*mm/i); const size = sizeMatch ? 'M' + sizeMatch[1].replace(/\.0$/, '') : diameter; const gradeShort = grade === 'Stainless' ? '' : grade; // "8" or "A2" const posDescription = [size, 'NUT', gradeShort, finish.toUpperCase() === 'PLAIN' ? 'PLN' : finish.toUpperCase()] .filter(Boolean).join(' '); return { sku, posDescription, description: desc, material, grade, diameter, thread, finish, pack, unitPrice: unitPrice ? 'R ' + unitPrice : '', url, }; }); }; const allRows = []; for (const f of FILTERS) { // First page const firstUrl = `${BASE}?gradematerial=${f.gradeId}`; const firstHtml = await fetchHtml(firstUrl); const firstRows = parsePage(firstHtml); allRows.push(...firstRows); // Discover total page count from toolbar const doc = new DOMParser().parseFromString(firstHtml, 'text/html'); const m = (doc.querySelector('.toolbar-amount')?.textContent || '').match(/of\s+(\d+)/); const total = m ? parseInt(m[1], 10) : f.expectedCount; const pages = Math.ceil(total / 10); console.log(`Grade ${f.grade}: ${firstRows.length} items on page 1, ${pages} pages total, expected ${total}`); // Remaining pages for (let p = 2; p <= pages; p++) { const html = await fetchHtml(`${BASE}?gradematerial=${f.gradeId}&p=${p}`); const rows = parsePage(html); allRows.push(...rows); console.log(`Grade ${f.grade}: page ${p}/${pages} -> ${rows.length} items`); } } console.log(`TOTAL ROWS: ${allRows.length}`); // Build CSV const headers = ['SKU','PosDescription','Description','Material','Grade','Diameter','Thread','Finish','Pack','UnitPrice','SourceURL']; const esc = (v) => { const s = (v ?? '').toString().replace(/"/g, '""'); return /[",\n]/.test(s) ? `"${s}"` : s; }; const csv = [headers.join(',')] .concat(allRows.map(r => headers.map(h => esc(r[h.toLowerCase()])).join(','))) .join('\n'); // Trigger download const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'nss-hex-nut-din934-grade8-a2.csv'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); return { count: allRows.length, sample: allRows.slice(0, 3) }; })();