← back to Naturaltextilewallpaper

scripts/pull-from-shopify.js

112 lines

// pull-from-shopify.js — Natural Textile Wallpaper catalog fetcher
// Paginates DW Shopify /products.json, filters to natural-fiber wallcoverings:
// linen, jute, raffia, sisal, hemp, grasscloth, woven, seagrass, abaca, coir
const https = require('https');
const fs = require('fs');
const path = require('path');

const NATURAL_TAGS = /^(linen|jute|raffia|sisal|hemp|grasscloth|seagrass|abaca|coir|woven|natural fiber|natural-fiber|textured|woven natural|natural weave|burlap|wicker|rattan|bamboo|woven texture|eco|biophilic|sustainable|organic|earthy)$/i;
const NATURAL_TITLE = /\b(linen|jute|raffia|sisal|hemp|grasscloth|seagrass|abaca|coir|woven|natural fiber|natural weave|burlap|rattan|bamboo|wicker|biophilic|organic|textured weave)\b/i;
const REJECT_TITLE = /(lamp|rug|pillow|throw|tripod|frame|mirror|vase|candle|sculpture|figurine|visual.{0,3}merchandiser|bh.?90210|image[ _-]?4)/i;

function fetchPage(page, retries = 3) {
  return new Promise((resolve, reject) => {
    const url = `https://designerwallcoverings.com/products.json?limit=250&page=${page}`;
    https.get(url, { headers: { 'User-Agent': 'Mozilla/5.0 naturaltextilewallpaper-builder' } }, (res) => {
      let data = '';
      res.on('data', c => data += c);
      res.on('end', () => {
        try {
          const parsed = JSON.parse(data);
          resolve(parsed.products || []);
        } catch(e) {
          if (retries > 0) {
            console.error(`  page ${page} parse error, retrying in 2s...`);
            setTimeout(() => fetchPage(page, retries - 1).then(resolve).catch(reject), 2000);
          } else {
            reject(e);
          }
        }
      });
    }).on('error', err => {
      if (retries > 0) {
        setTimeout(() => fetchPage(page, retries - 1).then(resolve).catch(reject), 2000);
      } else {
        reject(err);
      }
    });
  });
}

function materialOf(tags, title) {
  const blob = (Array.isArray(tags) ? tags.join(' ') : '') + ' ' + (title || '');
  const b = blob.toLowerCase();
  if (/linen/.test(b)) return 'linen';
  if (/jute/.test(b)) return 'jute';
  if (/raffia/.test(b)) return 'raffia';
  if (/sisal/.test(b)) return 'sisal';
  if (/hemp/.test(b)) return 'hemp';
  if (/seagrass/.test(b)) return 'seagrass';
  if (/abaca/.test(b)) return 'abaca';
  if (/grasscloth/.test(b)) return 'grasscloth';
  if (/bamboo/.test(b)) return 'bamboo';
  if (/coir|burlap|wicker|rattan/.test(b)) return 'woven';
  if (/woven|natural fiber|natural weave/.test(b)) return 'woven';
  return 'natural';
}

(async () => {
  const all = [];
  for (let page = 1; page <= 35; page++) {
    const products = await fetchPage(page);
    if (!products.length) break;
    all.push(...products);
    process.stdout.write(`page ${page}: ${products.length} (total ${all.length})\n`);
    if (products.length < 250) break;
    await new Promise(r => setTimeout(r, 500));
  }
  console.log(`\ntotal fetched: ${all.length}`);

  const natural = all
    .filter(p => p.product_type && /wallcovering|wallpaper/i.test(p.product_type))
    .filter(p => !REJECT_TITLE.test(p.title || ''))
    .filter(p => p.images && p.images.length > 0 && p.images[0].src)
    .filter(p => {
      const hasNaturalTag = (p.tags || []).some(t => NATURAL_TAGS.test(t));
      const hasNaturalTitle = NATURAL_TITLE.test(p.title || '');
      return hasNaturalTag || hasNaturalTitle;
    })
    .map(p => {
      const maxPrice = p.variants
        ? Math.max(...p.variants.map(v => parseFloat(v.price) || 0))
        : 0;
      const minPrice = p.variants
        ? Math.min(...p.variants.filter(v => parseFloat(v.price) > 0).map(v => parseFloat(v.price)))
        : 0;
      return {
        handle: p.handle,
        sku: p.handle,
        title: p.title,
        vendor: (p.vendor || '').trim(),
        product_type: p.product_type,
        tags: p.tags || [],
        material: materialOf(p.tags, p.title),
        image_url: p.images[0].src,
        images: (p.images || []).map(i => i.src).slice(0, 4),
        max_price: maxPrice,
        min_price: minPrice > 0 ? minPrice : maxPrice,
        fetched_at: new Date().toISOString()
      };
    });

  const outPath = path.join(__dirname, '..', 'data', 'products.json');
  fs.writeFileSync(outPath, JSON.stringify(natural, null, 2));
  console.log(`\nSaved ${natural.length} natural textile wallpaper products to ${outPath}`);

  // material breakdown
  const byMaterial = {};
  for (const p of natural) byMaterial[p.material] = (byMaterial[p.material] || 0) + 1;
  console.log('\nMaterial breakdown:');
  Object.entries(byMaterial).sort((a,b) => b[1]-a[1]).forEach(([m,n]) => console.log(`  ${m}: ${n}`));
})().catch(e => { console.error(e); process.exit(1); });