-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathginsbergs_scraper.js
More file actions
145 lines (131 loc) · 5.81 KB
/
Copy pathginsbergs_scraper.js
File metadata and controls
145 lines (131 loc) · 5.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// ============================================================
// GINSBERG'S CATALOG SCRAPER — Olive & Oregano
// ============================================================
// HOW TO USE:
// 1. Open https://ginsbergsfoods.pepr.app/ in your browser
// 2. Log in to your account
// 3. Press F12 to open DevTools → click the "Console" tab
// 4. Paste this ENTIRE script and press Enter
// 5. Then click the "Catalog" tab in the Ginsberg's app
// 6. Scroll through ALL categories to load every item
// 7. When done scrolling, type in console: window._downloadGinsbergs()
// 8. A file called ginsbergs_catalog.csv will download
// 9. Upload that file to your Pricelist Comparator app
// ============================================================
(function () {
const catalog = [];
const seen = new Set();
const origFetch = window.fetch;
const origXHR = window.XMLHttpRequest.prototype.open;
window._ginsbergsCatalog = catalog;
function processData(data, sourceUrl) {
let items = [];
if (Array.isArray(data)) items = data;
else if (data?.items) items = data.items;
else if (data?.products) items = data.products;
else if (data?.data && Array.isArray(data.data)) items = data.data;
else if (data?.results) items = data.results;
else if (data?.catalog) items = data.catalog;
let added = 0;
items.forEach(item => {
const key = JSON.stringify(item);
if (seen.has(key)) return;
seen.add(key);
catalog.push(item);
added++;
});
if (added > 0) {
console.log(`✅ Captured ${added} items from: ${sourceUrl} (total: ${catalog.length})`);
}
}
// Intercept fetch
window.fetch = async function (...args) {
const response = await origFetch.apply(this, args);
try {
const url = typeof args[0] === 'string' ? args[0] : (args[0]?.url || '');
const keywords = ['catalog', 'product', 'item', 'inventory', 'price', 'order-guide'];
if (keywords.some(k => url.toLowerCase().includes(k))) {
response.clone().json().then(data => processData(data, url)).catch(() => { });
}
} catch (e) { }
return response;
};
// Intercept XHR as fallback
window.XMLHttpRequest.prototype.open = function (method, url, ...rest) {
this._url = url;
this.addEventListener('load', function () {
try {
const keywords = ['catalog', 'product', 'item', 'inventory', 'price', 'order-guide'];
if (keywords.some(k => this._url?.toLowerCase().includes(k))) {
const data = JSON.parse(this.responseText);
processData(data, this._url);
}
} catch (e) { }
});
return origXHR.apply(this, [method, url, ...rest]);
};
// DOM scraper fallback — reads visible cards on the page
window._scrapeDom = function () {
const results = [];
// Try common price/name patterns in the DOM
document.querySelectorAll('[class*="item"], [class*="product"], [class*="card"]').forEach(el => {
const text = el.innerText || '';
const priceMatch = text.match(/\$[\d,]+\.?\d*/);
const skuMatch = text.match(/Case\s*[•·]?\s*(\d{4,})/i) || text.match(/SKU:?\s*(\d{4,})/i) || text.match(/Item #?:?\s*(\d{4,})/i);
const lines = text.split('\n').map(l => l.trim()).filter(Boolean);
if (priceMatch && lines.length >= 1) {
results.push({
name: lines[0],
sku: skuMatch ? skuMatch[1] : '',
price: priceMatch[0].replace('$', '').replace(',', ''),
pack: lines.find(l => /case|lb|oz|pk|ct/i.test(l) && l !== lines[0]) || ''
});
}
});
return results;
};
window._downloadGinsbergs = function () {
let items = catalog;
// Fall back to DOM scraping if API capture got nothing
if (items.length === 0) {
console.log('No API data captured — trying DOM scrape...');
items = window._scrapeDom();
}
if (items.length === 0) {
console.warn('❌ No items found. Make sure you scrolled through the full Catalog after pasting this script.');
return;
}
// Normalize fields — Pepr uses various field names
const fieldGuess = (item, ...keys) => {
for (const k of keys) {
if (item[k] !== undefined && item[k] !== null && item[k] !== '') return String(item[k]);
}
return '';
};
const rows = items.map(item => {
const name = fieldGuess(item, 'name', 'description', 'itemDescription', 'item_name', 'productName', 'product_name', 'title');
const sku = fieldGuess(item, 'id', 'sku', 'itemNumber', 'item_number', 'code', 'itemCode', 'item_code', 'productId', 'product_id');
const price = fieldGuess(item, 'price', 'unitPrice', 'unit_price', 'casePrice', 'case_price', 'cost', 'listPrice', 'list_price');
const pack = fieldGuess(item, 'pack', 'packSize', 'pack_size', 'unitSize', 'unit_size', 'size', 'uom', 'unitOfMeasure');
const category = fieldGuess(item, 'category', 'categoryName', 'category_name', 'department', 'group');
return [name, sku, price, pack, category].map(v => `"${v.replace(/"/g, '""')}"`).join(',');
});
const csv = 'Item Name,SKU,Price,Pack Size,Category\n' + rows.join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'ginsbergs_catalog.csv';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log(`✅ Downloaded ${items.length} items as ginsbergs_catalog.csv`);
console.log('Upload this file to your Pricelist Comparator at http://localhost:8080');
};
console.log('');
console.log('✅ Ginsberg\'s scraper is ACTIVE.');
console.log('👉 Now click the CATALOG tab and scroll through all categories.');
console.log('👉 When done, type: window._downloadGinsbergs()');
console.log('');
})();