/** * Count Jane stores - v2: Try Algolia store search * Usage: npx ts-node scripts/count-jane-stores-v2.ts */ import puppeteer from 'puppeteer-extra'; import StealthPlugin from 'puppeteer-extra-plugin-stealth'; puppeteer.use(StealthPlugin()); const STATES = [ 'AZ', 'CA', 'CO', 'FL', 'IL', 'MA', 'MI', 'NV', 'NJ', 'NY', 'OH', 'PA', 'WA', 'OR' ]; async function main() { console.log('Counting Jane stores by exploring state pages...\n'); const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'], }); const page = await browser.newPage(); const allStores: Map = new Map(); await page.setRequestInterception(true); page.on('request', (req) => { const type = req.resourceType(); if (['image', 'font', 'media', 'stylesheet'].includes(type)) { req.abort(); } else { req.continue(); } }); page.on('response', async (response) => { const url = response.url(); const contentType = response.headers()['content-type'] || ''; if (url.includes('iheartjane.com') && contentType.includes('json')) { try { const json = await response.json(); // Look for stores in any response if (json.stores && Array.isArray(json.stores)) { for (const s of json.stores) { if (s.id) allStores.set(s.id, s); } } // Also check hits (Algolia format) if (json.hits && Array.isArray(json.hits)) { for (const s of json.hits) { if (s.id) allStores.set(s.id, s); } } } catch {} } }); // First visit the main stores page console.log('Visiting main stores page...'); await page.goto('https://www.iheartjane.com/stores', { waitUntil: 'networkidle0', timeout: 60000, }); await new Promise(r => setTimeout(r, 3000)); // Try to scroll to load more stores console.log('Scrolling to load more...'); for (let i = 0; i < 5; i++) { await page.evaluate(() => window.scrollBy(0, 1000)); await new Promise(r => setTimeout(r, 1000)); } // Try clicking "Load More" if it exists try { const loadMore = await page.$('button:has-text("Load More"), [class*="load-more"]'); if (loadMore) { console.log('Clicking Load More...'); await loadMore.click(); await new Promise(r => setTimeout(r, 3000)); } } catch {} // Extract stores from DOM as fallback const domStores = await page.evaluate(() => { const storeElements = document.querySelectorAll('[data-store-id], [class*="StoreCard"], [class*="store-card"]'); return storeElements.length; }); console.log(`\nStores from DOM elements: ${domStores}`); await browser.close(); // Count by state const byState: Record = {}; for (const store of allStores.values()) { const state = store.state || 'Unknown'; byState[state] = (byState[state] || 0) + 1; } console.log('\n=== JANE STORE COUNTS ===\n'); console.log(`Unique stores captured: ${allStores.size}`); if (allStores.size > 0) { console.log('\nBy State:'); const sorted = Object.entries(byState).sort((a, b) => b[1] - a[1]); for (const [state, count] of sorted.slice(0, 20)) { console.log(` ${state}: ${count}`); } // Check Arizona specifically const azStores = Array.from(allStores.values()).filter(s => s.state === 'Arizona' || s.state === 'AZ' ); console.log(`\nArizona stores: ${azStores.length}`); if (azStores.length > 0) { console.log('AZ stores:'); for (const s of azStores.slice(0, 10)) { console.log(` - ${s.name} (ID: ${s.id}) - ${s.city}`); } } } // Note about total console.log('\n--- Note ---'); console.log('Jane uses server-side rendering. To get full store count,'); console.log('you may need to check their public marketing materials or'); console.log('iterate through known store IDs.'); } main().catch(console.error);