← back to Dw Scrape Scheduler

adapters/woo.js

62 lines

// WooCommerce Store API adapter — public, no auth:
//   <base>/wp-json/wc/store/v1/products?per_page=100&page=N   (fallback: .../store/products)
// $0 local. Store API prices are in MINOR units (cents) — divide by 10^minor_unit.
const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36';

async function fetchJson(url, attempts = 4) {
  let last = 0;
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(url, { headers: { 'User-Agent': UA, 'Accept': 'application/json' } });
    if (res.ok) return res.json();
    last = res.status;
    if (i < attempts - 1) await new Promise(r => setTimeout(r, 1500 * (i + 1)));
  }
  throw new Error(`HTTP ${last} after ${attempts} attempts`);
}

function priceOf(p) {
  const pr = p.prices || {};
  if (pr.price == null) return null;
  const minor = Number(pr.currency_minor_unit ?? 2);
  const n = Number(pr.price);
  return Number.isFinite(n) ? n / Math.pow(10, minor) : null;
}

async function scrape(base, { maxPages = 60, politeMs = 200 } = {}) {
  const root = base.replace(/\/+$/, '');
  // Probe which endpoint shape this store exposes.
  let path = null;
  for (const cand of ['/wp-json/wc/store/v1/products', '/wp-json/wc/store/products']) {
    try { const test = await fetchJson(`${root}${cand}?per_page=1`); if (Array.isArray(test)) { path = cand; break; } } catch { /* try next */ }
  }
  if (!path) throw new Error('no reachable WooCommerce Store API endpoint');

  const out = [];
  for (let page = 1; page <= maxPages; page++) {
    const items = await fetchJson(`${root}${path}?per_page=100&page=${page}`);
    if (!Array.isArray(items) || items.length === 0) break;
    for (const p of items) {
      const img0 = (p.images || [])[0] || {};
      out.push({
        mfr_sku: p.sku || String(p.id),
        handle: p.slug || (p.permalink || '').split('/').filter(Boolean).pop(),
        title: p.name,
        product_type: (p.type || ''),
        vendor: '',
        price: priceOf(p),
        body_html: p.description || p.short_description || '',
        image_url: img0.src || null,
        all_images: (p.images || []).map(i => i.src),
        product_url: p.permalink || `${root}/?p=${p.id}`,
        tags: (p.tags || []).map(t => t.name).filter(Boolean),
        raw: p,
      });
    }
    if (items.length < 100) break;
    await new Promise(r => setTimeout(r, politeMs));
  }
  return out;
}

module.exports = { scrape, platform: 'woo' };