← back to Handbag Authentication
scripts/fetch_museum_data.js
295 lines
#!/usr/bin/env node
/**
* Museum Handbag Data Fetcher
* Fetches handbag and purse data from museum APIs
*
* Sources:
* 1. Harvard Art Museums API
* 2. Victoria & Albert Museum API
* 3. Met Museum (CSV-based, already cloned)
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
const BASE_DIR = path.join(__dirname, '..');
const DATA_DIR = path.join(BASE_DIR, 'handbag_data');
const MUSEUM_DIR = path.join(DATA_DIR, 'museum_collections');
const OUTPUT_DIR = path.join(DATA_DIR, 'processed');
// Museum API configurations
const MUSEUMS = {
harvard: {
name: 'Harvard Art Museums',
baseUrl: 'https://api.harvardartmuseums.org/v1',
needsApiKey: true,
apiKeyEnv: 'HARVARD_API_KEY',
description: 'Requires free API key from https://harvardartmuseums.org/collections/api'
},
vam: {
name: 'Victoria & Albert Museum',
baseUrl: 'https://api.vam.ac.uk/v2',
needsApiKey: false,
description: 'Open API, no key required'
}
};
/**
* Make HTTPS request
*/
function httpsRequest(url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (e) {
reject(new Error(`Failed to parse JSON from ${url}: ${e.message}`));
}
});
}).on('error', reject);
});
}
/**
* Fetch from Harvard Art Museums
*/
async function fetchHarvardData(apiKey) {
console.log('\n🏛️ Fetching from Harvard Art Museums...');
if (!apiKey) {
console.log(' ⚠️ No API key provided. Skipping Harvard.');
console.log(' ℹ️ Get a free API key at: https://harvardartmuseums.org/collections/api');
return [];
}
const searchTerms = ['handbag', 'purse', 'bag', 'clutch'];
const allResults = [];
for (const term of searchTerms) {
try {
const url = `https://api.harvardartmuseums.org/v1/object?apikey=${apiKey}&keyword=${term}&size=100&classification=Accessories`;
console.log(` Searching for: "${term}"...`);
const data = await httpsRequest(url);
if (data.records) {
console.log(` ✓ Found ${data.records.length} items`);
allResults.push(...data.records);
}
} catch (error) {
console.error(` ✗ Error fetching "${term}":`, error.message);
}
}
// Save raw data
const outputFile = path.join(MUSEUM_DIR, 'harvard_handbags.json');
fs.writeFileSync(outputFile, JSON.stringify(allResults, null, 2));
console.log(` ✓ Saved ${allResults.length} items to: ${outputFile}`);
return allResults;
}
/**
* Fetch from Victoria & Albert Museum
*/
async function fetchVAMData() {
console.log('\n🏛️ Fetching from Victoria & Albert Museum...');
const searchTerms = ['handbag', 'purse', 'bag'];
const allResults = [];
for (const term of searchTerms) {
try {
const url = `https://api.vam.ac.uk/v2/objects/search?q=${term}&page_size=100`;
console.log(` Searching for: "${term}"...`);
const data = await httpsRequest(url);
if (data.records) {
console.log(` ✓ Found ${data.records.length} items`);
allResults.push(...data.records);
} else if (data.info && data.info.record_count) {
console.log(` ℹ️ Total available: ${data.info.record_count} items`);
}
} catch (error) {
console.error(` ✗ Error fetching "${term}":`, error.message);
}
}
// Save raw data
const outputFile = path.join(MUSEUM_DIR, 'vam_handbags.json');
fs.writeFileSync(outputFile, JSON.stringify(allResults, null, 2));
console.log(` ✓ Saved ${allResults.length} items to: ${outputFile}`);
return allResults;
}
/**
* Process and normalize museum data
*/
function normalizeMuseumData(harvardData, vamData) {
console.log('\n📊 Normalizing museum data...');
const normalized = [];
// Process Harvard data
harvardData.forEach(item => {
normalized.push({
source: 'harvard_art_museums',
museum_id: item.id,
title: item.title,
dated: item.dated,
period: item.period,
culture: item.culture,
classification: item.classification,
medium: item.medium,
dimensions: item.dimensions,
description: item.description,
url: item.url,
image_url: item.primaryimageurl,
century: item.century,
division: item.division,
department: item.department
});
});
// Process V&A data
vamData.forEach(item => {
normalized.push({
source: 'victoria_albert_museum',
museum_id: item.systemNumber || item.id,
title: item._primaryTitle || item.title,
dated: item._primaryDate || item.productionDates,
culture: item._primaryPlace || '',
classification: item.objectType,
medium: item._primaryMaterialTechnique || '',
dimensions: '',
description: item._primaryDescription || '',
url: `https://collections.vam.ac.uk/item/${item.systemNumber}/`,
image_url: item._primaryImageId ? `https://framemark.vam.ac.uk/collections/${item._primaryImageId}/full/735,/0/default.jpg` : '',
artist_maker: item._primaryMaker?.name || ''
});
});
console.log(` ✓ Normalized ${normalized.length} museum records`);
return normalized;
}
/**
* Generate museum data report
*/
function generateReport(harvardData, vamData, normalizedData) {
const report = {
generated_at: new Date().toISOString(),
sources: {
harvard_art_museums: {
total_items: harvardData.length,
api_url: MUSEUMS.harvard.baseUrl,
documentation: 'https://harvardartmuseums.org/collections/api'
},
victoria_albert_museum: {
total_items: vamData.length,
api_url: MUSEUMS.vam.baseUrl,
documentation: 'https://developers.vam.ac.uk/'
},
met_museum: {
note: 'CSV data requires Git LFS. Download from: https://github.com/metmuseum/openaccess',
total_objects: '492,000+',
costume_institute: '33,000+ fashion items'
}
},
summary: {
total_museum_records: normalizedData.length,
harvard_items: harvardData.length,
vam_items: vamData.length
},
output_files: {
harvard_raw: 'handbag_data/museum_collections/harvard_handbags.json',
vam_raw: 'handbag_data/museum_collections/vam_handbags.json',
normalized: 'handbag_data/processed/museum_handbags_normalized.json'
},
notes: {
met_museum: 'Met Museum CSV (317MB) requires Git LFS installation to download',
harvard_api_key: 'Get free API key at https://harvardartmuseums.org/collections/api',
vam_pagination: 'V&A API returns max 100 items per request. Use pagination for more.',
historical_data: 'Museum collections include handbags from multiple centuries'
}
};
return report;
}
/**
* Main execution
*/
async function main() {
console.log('🏛️ Museum Handbag Data Fetcher');
console.log('='.repeat(60));
// Ensure directories exist
if (!fs.existsSync(MUSEUM_DIR)) {
fs.mkdirSync(MUSEUM_DIR, { recursive: true });
}
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
try {
// Get API keys from environment
const harvardApiKey = process.env.HARVARD_API_KEY || '';
// Fetch data
const harvardData = await fetchHarvardData(harvardApiKey);
const vamData = await fetchVAMData();
// Normalize data
const normalizedData = normalizeMuseumData(harvardData, vamData);
// Save normalized data
const normalizedFile = path.join(OUTPUT_DIR, 'museum_handbags_normalized.json');
fs.writeFileSync(normalizedFile, JSON.stringify(normalizedData, null, 2));
console.log(`\n💾 Saved normalized data to: ${normalizedFile}`);
// Generate and save report
const report = generateReport(harvardData, vamData, normalizedData);
const reportFile = path.join(OUTPUT_DIR, 'museum_data_report.json');
fs.writeFileSync(reportFile, JSON.stringify(report, null, 2));
console.log(`💾 Saved report to: ${reportFile}`);
// Print summary
console.log('\n📊 Museum Data Summary:');
console.log(` Harvard Art Museums: ${harvardData.length} items`);
console.log(` Victoria & Albert: ${vamData.length} items`);
console.log(` Total normalized records: ${normalizedData.length}`);
console.log('\n✅ Museum data fetch complete!');
// Print instructions
console.log('\n📝 Next Steps:');
console.log(' 1. For Met Museum data, install Git LFS:');
console.log(' apt-get install git-lfs');
console.log(' cd handbag_data/museum_collections/openaccess && git lfs pull');
console.log('\n 2. For more Harvard data, get API key:');
console.log(' https://harvardartmuseums.org/collections/api');
console.log(' Then: export HARVARD_API_KEY=your_key');
} catch (error) {
console.error('\n❌ Error during fetch:', error.message);
console.error(error.stack);
process.exit(1);
}
}
// Run if called directly
if (require.main === module) {
main();
}
module.exports = { main, fetchHarvardData, fetchVAMData };