Competitor research, price monitoring, and market analysis all require product data. Manually copying product names and prices from competitor stores is tedious and error-prone.

What if you could build a Chrome extension that automatically scrapes product information from any Shopify or WooCommerce store? With Claude AI, you can - even if you have limited coding experience.

This guide walks through how to use Claude to build a complete, production-ready product scraper extension from scratch.


What We Are Building

A Chrome extension that:

By the end, you will have a functional tool for competitive intelligence.


Prerequisites

Before starting, you need:

No prior coding experience is required. Claude will generate all the code.


Step 1: Understanding the Extension Architecture

Before asking Claude to write code, understand what we need.

Chrome Extension Components

Every Manifest V3 Chrome extension needs:

manifest.json: Configuration file that tells Chrome about your extension

popup.html: The user interface that appears when you click the extension icon

popup.js: JavaScript that controls the popup behavior

background.js: A service worker that handles background tasks (like making web requests)

styles.css: Styling for the popup

modules/: Folder containing specialized scraping logic

The Scraping Strategy

Our extension will use multiple methods to extract product data:

  1. API endpoints: Shopify and WooCommerce expose product data via APIs
  2. JSON-LD: Structured data embedded in HTML for SEO
  3. DOM scraping: Parsing the actual HTML elements as a last resort

Each method has trade-offs. APIs are fastest and most reliable but may be restricted. DOM scraping works when APIs fail but is fragile.


Step 2: Ask Claude to Create the Manifest

Start by asking Claude for the extension manifest.

Prompt to Use

“Create a manifest.json file for a Chrome extension (Manifest V3) called ‘Product Scraper’. It should:

Claude’s Response

Claude will generate:

{
  "manifest_version": 3,
  "name": "Product Scraper",
  "version": "1.0.0",
  "description": "Scrape product names and prices from Shopify and WooCommerce stores",
  
  "permissions": [
    "activeTab",
    "storage"
  ],
  
  "host_permissions": [
    "https://*/*",
    "http://*/*"
  ],
  
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

Save this as manifest.json in your extension folder.


Step 3: Ask Claude to Build the Popup Interface

Now create the user interface.

Prompt to Use

“Create popup.html and styles.css for the Product Scraper extension. The popup should include:

Claude’s Response

Claude will provide the HTML:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="container">
    <h1>Product Scraper</h1>
    
    <div class="input-group">
      <input 
        type="url" 
        id="storeUrl" 
        placeholder="https://example-store.com"
      >
      <button id="scrapeBtn">Scrape Products</button>
    </div>
    
    <div id="status" class="status hidden"></div>
    <div id="error" class="error hidden"></div>
    
    <div id="results" class="results hidden">
      <div class="results-header">
        <span id="productCount">0 products found</span>
        <div class="export-buttons">
          <button id="exportCsv" class="export-btn">Export CSV</button>
          <button id="exportJson" class="export-btn">Export JSON</button>
        </div>
      </div>
      
      <div class="table-container">
        <table id="productsTable">
          <thead>
            <tr>
              <th>Product</th>
              <th>Price</th>
              <th>Source</th>
            </tr>
          </thead>
          <tbody id="productsBody"></tbody>
        </table>
      </div>
    </div>
  </div>
  
  <script src="popup.js" type="module"></script>
</body>
</html>

And the CSS:

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  width: 400px;
  max-height: 500px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 14px;
  color: #333;
}

.container {
  padding: 16px;
}

h1 {
  font-size: 18px;
  margin-bottom: 16px;
  color: #1a73e8;
}

.input-group {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
}

input[type="url"] {
  flex: 1;
  padding: 10px 12px;
  border: 1px solid #ddd;
  border-radius: 6px;
  font-size: 14px;
}

input[type="url"]:focus {
  outline: none;
  border-color: #1a73e8;
}

button {
  padding: 10px 16px;
  background: #1a73e8;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
}

button:hover {
  background: #1557b0;
}

button:disabled {
  background: #ccc;
  cursor: not-allowed;
}

.status {
  padding: 12px;
  background: #e8f0fe;
  border-radius: 6px;
  margin-bottom: 16px;
  color: #1a73e8;
}

.error {
  padding: 12px;
  background: #fce8e6;
  border-radius: 6px;
  margin-bottom: 16px;
  color: #c5221f;
}

.hidden {
  display: none;
}

.results-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

#productCount {
  font-weight: 500;
  color: #5f6368;
}

.export-buttons {
  display: flex;
  gap: 8px;
}

.export-btn {
  padding: 6px 12px;
  font-size: 12px;
  background: #34a853;
}

.export-btn:hover {
  background: #2d8e47;
}

.table-container {
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid #ddd;
  border-radius: 6px;
}

table {
  width: 100%;
  border-collapse: collapse;
}

th, td {
  padding: 10px 12px;
  text-align: left;
  border-bottom: 1px solid #eee;
}

th {
  background: #f8f9fa;
  font-weight: 500;
  position: sticky;
  top: 0;
}

td {
  font-size: 13px;
}

td:first-child {
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

Save these as popup.html and styles.css.


Step 4: Ask Claude for the Popup Controller

The popup needs JavaScript to handle user interactions.

Prompt to Use

“Create popup.js for the Product Scraper extension. It should:

Claude’s Response

Claude will generate the controller logic:

// popup.js

let scrapedProducts = [];

document.addEventListener('DOMContentLoaded', async () => {
  const urlInput = document.getElementById('storeUrl');
  const scrapeBtn = document.getElementById('scrapeBtn');
  const statusDiv = document.getElementById('status');
  const errorDiv = document.getElementById('error');
  const resultsDiv = document.getElementById('results');
  const productCount = document.getElementById('productCount');
  const productsBody = document.getElementById('productsBody');
  const exportCsvBtn = document.getElementById('exportCsv');
  const exportJsonBtn = document.getElementById('exportJson');

  // Load last used URL
  const stored = await chrome.storage.local.get('lastUrl');
  if (stored.lastUrl) {
    urlInput.value = stored.lastUrl;
  }

  // Scrape button click
  scrapeBtn.addEventListener('click', async () => {
    const url = urlInput.value.trim();
    
    if (!url) {
      showError('Please enter a store URL');
      return;
    }

    if (!isValidUrl(url)) {
      showError('Please enter a valid URL');
      return;
    }

    // Save URL
    await chrome.storage.local.set({ lastUrl: url });

    // Reset UI
    hideError();
    hideResults();
    showStatus('Detecting platform...');
    scrapeBtn.disabled = true;

    try {
      // Send message to background script
      const response = await chrome.runtime.sendMessage({
        action: 'scrape',
        url: url
      });

      if (response.error) {
        showError(response.error);
      } else {
        scrapedProducts = response.products;
        displayResults(scrapedProducts);
      }
    } catch (error) {
      showError('Scraping failed: ' + error.message);
    } finally {
      scrapeBtn.disabled = false;
      hideStatus();
    }
  });

  // Listen for progress updates
  chrome.runtime.onMessage.addListener((message) => {
    if (message.type === 'progress') {
      showStatus(message.text);
    }
  });

  // Export CSV
  exportCsvBtn.addEventListener('click', () => {
    if (scrapedProducts.length === 0) return;

    const headers = ['Title', 'Price', 'Currency', 'URL', 'Image', 'Source', 'Scraped At'];
    const rows = scrapedProducts.map(p => [
      escapeCsv(p.title),
      p.price,
      p.currency || '',
      p.url,
      p.image || '',
      p.source_method,
      p.scraped_at
    ]);

    const csv = [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
    downloadFile(csv, 'products.csv', 'text/csv');
  });

  // Export JSON
  exportJsonBtn.addEventListener('click', () => {
    if (scrapedProducts.length === 0) return;
    
    const json = JSON.stringify(scrapedProducts, null, 2);
    downloadFile(json, 'products.json', 'application/json');
  });

  // Helper functions
  function isValidUrl(string) {
    try {
      new URL(string);
      return true;
    } catch (_) {
      return false;
    }
  }

  function showStatus(text) {
    statusDiv.textContent = text;
    statusDiv.classList.remove('hidden');
  }

  function hideStatus() {
    statusDiv.classList.add('hidden');
  }

  function showError(text) {
    errorDiv.textContent = text;
    errorDiv.classList.remove('hidden');
  }

  function hideError() {
    errorDiv.classList.add('hidden');
  }

  function hideResults() {
    resultsDiv.classList.add('hidden');
  }

  function displayResults(products) {
    productCount.textContent = `${products.length} products found`;
    
    // Clear existing rows
    productsBody.innerHTML = '';
    
    // Show first 50 products
    const preview = products.slice(0, 50);
    preview.forEach(product => {
      const row = document.createElement('tr');
      row.innerHTML = `
        <td title="${escapeHtml(product.title)}">${escapeHtml(product.title)}</td>
        <td>${formatPrice(product.price, product.currency)}</td>
        <td>${product.source_method}</td>
      `;
      productsBody.appendChild(row);
    });

    resultsDiv.classList.remove('hidden');
  }

  function formatPrice(price, currency) {
    if (price === null || price === undefined) return 'N/A';
    const symbol = getCurrencySymbol(currency);
    return `${symbol}${price.toFixed(2)}`;
  }

  function getCurrencySymbol(currency) {
    const symbols = {
      'USD': '$', 'EUR': '€', 'GBP': '£', 'CAD': 'C$',
      'AUD': 'A$', 'JPY': '¥', 'CNY': '¥', 'INR': '₹'
    };
    return symbols[currency] || (currency ? currency + ' ' : '$');
  }

  function escapeCsv(str) {
    if (str === null || str === undefined) return '';
    str = String(str);
    if (str.includes(',') || str.includes('"') || str.includes('\n')) {
      return '"' + str.replace(/"/g, '""') + '"';
    }
    return str;
  }

  function escapeHtml(str) {
    const div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
  }

  function downloadFile(content, filename, mimeType) {
    const blob = new Blob([content], { type: mimeType });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.click();
    URL.revokeObjectURL(url);
  }
});

Save this as popup.js.


Step 5: Ask Claude for the Background Service Worker

The background script coordinates the actual scraping.

Prompt to Use

“Create background.js as a service worker for the Product Scraper Chrome extension. It should:

Claude’s Response

// background.js

import { scrapeShopify } from './modules/shopify.js';
import { scrapeWooCommerce } from './modules/woocommerce.js';

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === 'scrape') {
    handleScrape(message.url)
      .then(products => sendResponse({ products }))
      .catch(error => sendResponse({ error: error.message }));
    return true; // Keep message channel open for async response
  }
});

async function handleScrape(url) {
  const baseUrl = new URL(url).origin;
  
  sendProgress('Detecting platform...');
  const platform = await detectPlatform(baseUrl);
  
  if (!platform) {
    throw new Error('Could not detect platform. Supported: Shopify, WooCommerce');
  }
  
  sendProgress(`Detected ${platform}. Starting scrape...`);
  
  let products;
  if (platform === 'shopify') {
    products = await scrapeShopify(url, sendProgress);
  } else if (platform === 'woocommerce') {
    products = await scrapeWooCommerce(url, sendProgress);
  }
  
  // Deduplicate by URL
  const seen = new Set();
  products = products.filter(p => {
    if (seen.has(p.url)) return false;
    seen.add(p.url);
    return true;
  });
  
  sendProgress(`Scraping complete. Found ${products.length} products.`);
  
  return products;
}

async function detectPlatform(baseUrl) {
  try {
    // Try Shopify detection
    const shopifyCheck = await fetch(`${baseUrl}/products.json?limit=1`);
    if (shopifyCheck.ok) {
      return 'shopify';
    }
  } catch (e) {}
  
  try {
    // Try fetching homepage and check for Shopify indicators
    const homeResponse = await fetch(baseUrl);
    const html = await homeResponse.text();
    
    if (html.includes('cdn.shopify.com') || html.includes('Shopify.theme')) {
      return 'shopify';
    }
    
    // Check for WooCommerce indicators
    if (html.includes('woocommerce') || html.includes('wp-content')) {
      return 'woocommerce';
    }
  } catch (e) {}
  
  try {
    // Try WooCommerce Store API
    const wooCheck = await fetch(`${baseUrl}/wp-json/wc/store/products?per_page=1`);
    if (wooCheck.ok) {
      return 'woocommerce';
    }
  } catch (e) {}
  
  return null;
}

function sendProgress(text) {
  chrome.runtime.sendMessage({ type: 'progress', text });
}

// Utility: Random delay to avoid rate limiting
export function delay(min = 100, max = 500) {
  const ms = Math.floor(Math.random() * (max - min + 1)) + min;
  return new Promise(resolve => setTimeout(resolve, ms));
}

Save this as background.js.


Step 6: Ask Claude for Platform-Specific Modules

Now we need the actual scraping logic for each platform.

Prompt for Shopify Module

“Create modules/shopify.js for the Product Scraper extension. It should:

Claude’s Response

// modules/shopify.js

import { parsePrice, generateId } from './parser.js';

export async function scrapeShopify(url, sendProgress) {
  const baseUrl = new URL(url).origin;
  const urlPath = new URL(url).pathname;
  
  let products = [];
  
  // Method 1: Try products.json API
  sendProgress('Trying Shopify API...');
  products = await tryProductsApi(baseUrl);
  
  if (products.length > 0) {
    return products;
  }
  
  // Method 2: Try collection endpoint if URL is a collection
  if (urlPath.includes('/collections/')) {
    sendProgress('Trying collection API...');
    const collectionHandle = urlPath.split('/collections/')[1].split('/')[0];
    products = await tryCollectionApi(baseUrl, collectionHandle);
    
    if (products.length > 0) {
      return products;
    }
  }
  
  // Method 3: Try JSON-LD from HTML
  sendProgress('Trying JSON-LD extraction...');
  products = await tryJsonLd(url);
  
  if (products.length > 0) {
    return products;
  }
  
  // Method 4: DOM scraping fallback
  sendProgress('Trying DOM scraping...');
  products = await tryDomScraping(url);
  
  return products;
}

async function tryProductsApi(baseUrl) {
  const products = [];
  let page = 1;
  const limit = 250;
  
  try {
    while (true) {
      const response = await fetch(
        `${baseUrl}/products.json?limit=${limit}&page=${page}`
      );
      
      if (!response.ok) break;
      
      const data = await response.json();
      
      if (!data.products || data.products.length === 0) break;
      
      for (const product of data.products) {
        products.push(formatShopifyProduct(product, baseUrl, 'shopify-api'));
      }
      
      if (data.products.length < limit) break;
      
      page++;
      await delay(200, 500);
    }
  } catch (e) {
    console.error('Products API failed:', e);
  }
  
  return products;
}

async function tryCollectionApi(baseUrl, collectionHandle) {
  const products = [];
  let page = 1;
  const limit = 250;
  
  try {
    while (true) {
      const response = await fetch(
        `${baseUrl}/collections/${collectionHandle}/products.json?limit=${limit}&page=${page}`
      );
      
      if (!response.ok) break;
      
      const data = await response.json();
      
      if (!data.products || data.products.length === 0) break;
      
      for (const product of data.products) {
        products.push(formatShopifyProduct(product, baseUrl, 'shopify-collection-json'));
      }
      
      if (data.products.length < limit) break;
      
      page++;
      await delay(200, 500);
    }
  } catch (e) {
    console.error('Collection API failed:', e);
  }
  
  return products;
}

async function tryJsonLd(url) {
  const products = [];
  
  try {
    const response = await fetch(url);
    const html = await response.text();
    
    const jsonLdMatches = html.match(
      /<script type="application\/ld\+json">([\s\S]*?)<\/script>/gi
    );
    
    if (!jsonLdMatches) return products;
    
    for (const match of jsonLdMatches) {
      try {
        const jsonStr = match
          .replace(/<script type="application\/ld\+json">/i, '')
          .replace(/<\/script>/i, '');
        const data = JSON.parse(jsonStr);
        
        if (data['@type'] === 'Product') {
          products.push(formatJsonLdProduct(data, 'shopify-jsonld'));
        } else if (Array.isArray(data)) {
          for (const item of data) {
            if (item['@type'] === 'Product') {
              products.push(formatJsonLdProduct(item, 'shopify-jsonld'));
            }
          }
        }
      } catch (e) {}
    }
  } catch (e) {
    console.error('JSON-LD extraction failed:', e);
  }
  
  return products;
}

async function tryDomScraping(url) {
  const products = [];
  
  try {
    const response = await fetch(url);
    const html = await response.text();
    
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    
    // Common Shopify product selectors
    const selectors = [
      '.product-card',
      '.product-item',
      '.grid__item[data-product-id]',
      '.product-grid-item',
      '[data-product-card]'
    ];
    
    let productElements = [];
    for (const selector of selectors) {
      productElements = doc.querySelectorAll(selector);
      if (productElements.length > 0) break;
    }
    
    productElements.forEach(el => {
      const titleEl = el.querySelector(
        '.product-card__title, .product-item__title, .product__title, h2, h3'
      );
      const priceEl = el.querySelector(
        '.price, .product-price, .money, [data-product-price]'
      );
      const linkEl = el.querySelector('a[href*="/products/"]');
      const imgEl = el.querySelector('img');
      
      if (titleEl && linkEl) {
        const priceText = priceEl ? priceEl.textContent : null;
        const { price, currency } = parsePrice(priceText);
        
        products.push({
          id: generateId(linkEl.href),
          title: titleEl.textContent.trim(),
          price: price,
          currency: currency,
          url: new URL(linkEl.href, url).href,
          image: imgEl ? imgEl.src : null,
          source_method: 'shopify-dom',
          scraped_at: new Date().toISOString()
        });
      }
    });
  } catch (e) {
    console.error('DOM scraping failed:', e);
  }
  
  return products;
}

function formatShopifyProduct(product, baseUrl, sourceMethod) {
  // Get minimum variant price
  let minPrice = null;
  let currency = null;
  
  if (product.variants && product.variants.length > 0) {
    const prices = product.variants
      .map(v => parseFloat(v.price))
      .filter(p => !isNaN(p));
    
    if (prices.length > 0) {
      minPrice = Math.min(...prices);
    }
  }
  
  return {
    id: String(product.id),
    title: product.title,
    price: minPrice,
    currency: currency,
    url: `${baseUrl}/products/${product.handle}`,
    image: product.images?.[0]?.src || null,
    source_method: sourceMethod,
    scraped_at: new Date().toISOString()
  };
}

function formatJsonLdProduct(data, sourceMethod) {
  let price = null;
  let currency = null;
  
  if (data.offers) {
    const offer = Array.isArray(data.offers) ? data.offers[0] : data.offers;
    price = parseFloat(offer.price) || null;
    currency = offer.priceCurrency || null;
  }
  
  return {
    id: generateId(data.url || data.name),
    title: data.name,
    price: price,
    currency: currency,
    url: data.url || null,
    image: Array.isArray(data.image) ? data.image[0] : data.image,
    source_method: sourceMethod,
    scraped_at: new Date().toISOString()
  };
}

function delay(min, max) {
  const ms = Math.floor(Math.random() * (max - min + 1)) + min;
  return new Promise(resolve => setTimeout(resolve, ms));
}

Save this as modules/shopify.js.

Prompt for WooCommerce Module

“Create modules/woocommerce.js for the Product Scraper extension. It should:

Claude’s Response

// modules/woocommerce.js

import { parsePrice, generateId } from './parser.js';

export async function scrapeWooCommerce(url, sendProgress) {
  const baseUrl = new URL(url).origin;
  
  let products = [];
  
  // Method 1: Try WooCommerce Store API
  sendProgress('Trying WooCommerce Store API...');
  products = await tryStoreApi(baseUrl);
  
  if (products.length > 0) {
    return products;
  }
  
  // Method 2: Try REST API v3
  sendProgress('Trying WooCommerce REST API...');
  products = await tryRestApi(baseUrl);
  
  if (products.length > 0) {
    return products;
  }
  
  // Method 3: Try JSON-LD
  sendProgress('Trying JSON-LD extraction...');
  products = await tryJsonLd(url);
  
  if (products.length > 0) {
    return products;
  }
  
  // Method 4: DOM scraping
  sendProgress('Trying DOM scraping...');
  products = await tryDomScraping(url);
  
  return products;
}

async function tryStoreApi(baseUrl) {
  const products = [];
  let page = 1;
  const perPage = 100;
  
  try {
    while (true) {
      const response = await fetch(
        `${baseUrl}/wp-json/wc/store/products?per_page=${perPage}&page=${page}`
      );
      
      if (!response.ok) break;
      
      const data = await response.json();
      
      if (!data || data.length === 0) break;
      
      for (const product of data) {
        products.push(formatStoreApiProduct(product, baseUrl));
      }
      
      if (data.length < perPage) break;
      
      page++;
      await delay(200, 500);
    }
  } catch (e) {
    console.error('Store API failed:', e);
  }
  
  return products;
}

async function tryRestApi(baseUrl) {
  const products = [];
  let page = 1;
  const perPage = 100;
  
  try {
    while (true) {
      const response = await fetch(
        `${baseUrl}/wp-json/wc/v3/products?per_page=${perPage}&page=${page}`
      );
      
      if (!response.ok) break;
      
      const data = await response.json();
      
      if (!data || data.length === 0) break;
      
      for (const product of data) {
        products.push(formatRestApiProduct(product, baseUrl));
      }
      
      if (data.length < perPage) break;
      
      page++;
      await delay(200, 500);
    }
  } catch (e) {
    console.error('REST API failed:', e);
  }
  
  return products;
}

async function tryJsonLd(url) {
  const products = [];
  
  try {
    const response = await fetch(url);
    const html = await response.text();
    
    const jsonLdMatches = html.match(
      /<script type="application\/ld\+json">([\s\S]*?)<\/script>/gi
    );
    
    if (!jsonLdMatches) return products;
    
    for (const match of jsonLdMatches) {
      try {
        const jsonStr = match
          .replace(/<script type="application\/ld\+json">/i, '')
          .replace(/<\/script>/i, '');
        const data = JSON.parse(jsonStr);
        
        if (data['@type'] === 'Product') {
          products.push(formatJsonLdProduct(data));
        } else if (data['@graph']) {
          for (const item of data['@graph']) {
            if (item['@type'] === 'Product') {
              products.push(formatJsonLdProduct(item));
            }
          }
        }
      } catch (e) {}
    }
  } catch (e) {
    console.error('JSON-LD extraction failed:', e);
  }
  
  return products;
}

async function tryDomScraping(url) {
  const products = [];
  
  try {
    const response = await fetch(url);
    const html = await response.text();
    
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    
    // Common WooCommerce product selectors
    const productElements = doc.querySelectorAll(
      '.product, .wc-block-grid__product, li.product'
    );
    
    productElements.forEach(el => {
      const titleEl = el.querySelector(
        '.woocommerce-loop-product__title, .product-title, h2, h3'
      );
      const priceEl = el.querySelector(
        '.price, .woocommerce-Price-amount'
      );
      const linkEl = el.querySelector('a.woocommerce-LoopProduct-link, a[href*="/product/"]');
      const imgEl = el.querySelector('img');
      
      if (titleEl) {
        const priceText = priceEl ? priceEl.textContent : null;
        const { price, currency } = parsePrice(priceText);
        const productUrl = linkEl ? linkEl.href : null;
        
        products.push({
          id: generateId(productUrl || titleEl.textContent),
          title: titleEl.textContent.trim(),
          price: price,
          currency: currency,
          url: productUrl,
          image: imgEl ? imgEl.src : null,
          source_method: 'woo-dom',
          scraped_at: new Date().toISOString()
        });
      }
    });
  } catch (e) {
    console.error('DOM scraping failed:', e);
  }
  
  return products;
}

function formatStoreApiProduct(product, baseUrl) {
  let price = null;
  let currency = null;
  
  if (product.prices) {
    // Use sale price if available, otherwise regular price
    const priceValue = product.prices.sale_price || product.prices.price;
    price = parseInt(priceValue) / 100; // Store API returns cents
    currency = product.prices.currency_code;
  }
  
  return {
    id: String(product.id),
    title: product.name,
    price: price,
    currency: currency,
    url: product.permalink,
    image: product.images?.[0]?.src || null,
    source_method: 'woo-store-api',
    scraped_at: new Date().toISOString()
  };
}

function formatRestApiProduct(product, baseUrl) {
  return {
    id: String(product.id),
    title: product.name,
    price: parseFloat(product.price) || null,
    currency: null,
    url: product.permalink,
    image: product.images?.[0]?.src || null,
    source_method: 'woo-rest-api',
    scraped_at: new Date().toISOString()
  };
}

function formatJsonLdProduct(data) {
  let price = null;
  let currency = null;
  
  if (data.offers) {
    const offer = Array.isArray(data.offers) ? data.offers[0] : data.offers;
    price = parseFloat(offer.price) || null;
    currency = offer.priceCurrency || null;
  }
  
  return {
    id: generateId(data.url || data.name),
    title: data.name,
    price: price,
    currency: currency,
    url: data.url || null,
    image: Array.isArray(data.image) ? data.image[0] : data.image,
    source_method: 'woo-jsonld',
    scraped_at: new Date().toISOString()
  };
}

function delay(min, max) {
  const ms = Math.floor(Math.random() * (max - min + 1)) + min;
  return new Promise(resolve => setTimeout(resolve, ms));
}

Save this as modules/woocommerce.js.

Prompt for Parser Utilities

“Create modules/parser.js with shared utility functions for:

Claude’s Response

// modules/parser.js

export function parsePrice(priceString) {
  if (!priceString) {
    return { price: null, currency: null };
  }
  
  const text = priceString.trim();
  
  // Detect currency
  let currency = null;
  const currencyPatterns = [
    { symbol: '$', code: 'USD' },
    { symbol: '€', code: 'EUR' },
    { symbol: '£', code: 'GBP' },
    { symbol: '¥', code: 'JPY' },
    { symbol: '₹', code: 'INR' },
    { symbol: 'C$', code: 'CAD' },
    { symbol: 'A$', code: 'AUD' },
    { symbol: 'CHF', code: 'CHF' },
    { symbol: 'kr', code: 'SEK' },
    { symbol: 'zł', code: 'PLN' }
  ];
  
  for (const { symbol, code } of currencyPatterns) {
    if (text.includes(symbol)) {
      currency = code;
      break;
    }
  }
  
  // Also check for ISO codes
  const isoMatch = text.match(/\b(USD|EUR|GBP|CAD|AUD|JPY|CNY|INR|CHF|SEK|NOK|DKK|PLN|BRL|MXN)\b/i);
  if (isoMatch) {
    currency = isoMatch[1].toUpperCase();
  }
  
  // Extract numeric value
  // Handle formats like: $1,234.56 | 1.234,56 € | £1,234
  let priceText = text
    .replace(/[^\d.,]/g, '') // Remove non-numeric except . and ,
    .trim();
  
  // Handle European format (1.234,56 -> 1234.56)
  if (priceText.match(/^\d{1,3}(\.\d{3})*(,\d{2})?$/)) {
    priceText = priceText.replace(/\./g, '').replace(',', '.');
  }
  // Handle standard format (1,234.56 -> 1234.56)
  else if (priceText.match(/^\d{1,3}(,\d{3})*(\.\d{2})?$/)) {
    priceText = priceText.replace(/,/g, '');
  }
  // Handle simple comma decimal (123,45 -> 123.45)
  else if (priceText.match(/^\d+,\d{2}$/)) {
    priceText = priceText.replace(',', '.');
  }
  
  const price = parseFloat(priceText);
  
  return {
    price: isNaN(price) ? null : price,
    currency: currency
  };
}

export function generateId(input) {
  if (!input) {
    return 'unknown-' + Date.now();
  }
  
  const str = String(input).toLowerCase();
  
  // Simple hash function
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    const char = str.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;
    hash = hash & hash;
  }
  
  return Math.abs(hash).toString(36);
}

export function normalizeUrl(url, baseUrl) {
  if (!url) return null;
  
  try {
    return new URL(url, baseUrl).href;
  } catch (e) {
    return url;
  }
}

export function cleanTitle(title) {
  if (!title) return '';
  
  return title
    .replace(/\s+/g, ' ')
    .replace(/[\n\r\t]/g, '')
    .trim();
}

Save this as modules/parser.js.


Step 7: Create Icons

Ask Claude to help create simple icons or provide guidance.

Prompt

“I need simple icons for my Chrome extension at 16x16, 48x48, and 128x128 pixels. Can you provide SVG code for a simple product/shopping icon that I can convert to PNG?”

Claude will provide SVG code you can convert using online tools or image editors.

For quick testing, you can create placeholder colored squares or use any icon temporarily.


Step 8: Load and Test the Extension

Now assemble everything and test.

Folder Structure

product-scraper-extension/
├── manifest.json
├── popup.html
├── popup.js
├── styles.css
├── background.js
├── modules/
│   ├── parser.js
│   ├── shopify.js
│   └── woocommerce.js
└── icons/
    ├── icon16.png
    ├── icon48.png
    └── icon128.png

Load in Chrome

  1. Open chrome://extensions/
  2. Enable “Developer mode”
  3. Click “Load unpacked”
  4. Select your extension folder
  5. Pin the extension to your toolbar

Test It

  1. Click the extension icon
  2. Enter a Shopify store URL (e.g., https://www.allbirds.com)
  3. Click “Scrape Products”
  4. Watch the progress updates
  5. View results and export

Step 9: Troubleshooting with Claude

When issues arise, Claude can help debug.

Common Issues and Prompts

Extension not loading:

“My Chrome extension shows an error when loading. The error message is: [paste error]. Here’s my manifest.json: [paste content]. What’s wrong?”

Scraping returns no products:

“My product scraper returns zero products for this URL: [URL]. The platform is detected as Shopify. How can I debug why the API calls are failing?”

Export not working:

“The CSV export downloads but the file is empty or corrupted. Here’s my export function: [paste code]. What could cause this?”


Step 10: Enhance the Extension

Once basic functionality works, ask Claude for enhancements:


What You Built

You now have a functional Chrome extension that:

This tool enables:


Key Takeaway

Claude AI makes complex development projects accessible. By breaking the project into components and asking specific questions, you can build production-quality tools without deep programming expertise.

The key is iterative development:

  1. Ask Claude for one component at a time
  2. Test each piece before moving on
  3. Use Claude to debug issues as they arise
  4. Enhance incrementally based on your needs

With this approach, Claude becomes a collaborative development partner, turning your ideas into functional software.

Related Posts

How to Connect Claude to Your Google Ads Account via MCP (No Coding Required)

16 min read

Claude AIMCPGoogle AdsZapierGAQLAutomationClaude AI Series

How Claude AI Can Help You Set Up Google Ads Tracking in Google Tag Manager

11 min read

Claude AIGoogle Tag ManagerGoogle AdsConversion TrackingAI ToolsClaude AI Series

How to Use Claude to Build a Better Google Shopping Feed

Merchant CenterGoogle ShoppingProduct FeedClaude AIMerchant Center Intro Series
Adnan Agic

Adnan Agic

Google Ads Strategist & Technical Marketing Expert with 5+ years experience managing $10M+ in ad spend across 100+ accounts.

Need Help With Your Google Ads?

I help e-commerce brands scale profitably with data-driven PPC strategies.

Get In Touch
Back to Blog