← back to Wine Finder
services/wineSensedScraper.js
287 lines
const axios = require('axios');
const csv = require('csv-parser');
const { Readable } = require('stream');
/**
* WineSensed Dataset Scraper
*
* Learning to Taste: A Multimodal Wine Dataset
* Source: UC Davis / NeurIPS 2023 / DTU
*
* Dataset Details:
* - 350,000+ unique wine vintages
* - 824,000 wine reviews
* - 897,000 wine label images
* - Data collected from Vivino (May 2023)
*
* Fields: vintage_id, wine name, year, winery_id, country, region,
* price (USD), rating, grape composition, alcohol %, reviews
*
* Download URL: https://data.dtu.dk/articles/dataset/WineSensed_Learning_to_Taste_A_Multimodal_Wine_Dataset/23376560
* License: CC BY-NC-ND 4.0
*/
class WineSensedScraper {
constructor() {
this.datasetUrl = 'https://data.dtu.dk/ndownloader/articles/23376560/versions/1';
this.projectUrl = 'https://thoranna.github.io/learning_to_taste/';
// Cache for dataset (in production, use Redis or file-based cache)
this.dataCache = null;
this.cacheTimestamp = null;
this.cacheDuration = 24 * 60 * 60 * 1000; // 24 hours
this.datasetInfo = {
name: 'WineSensed: Learning to Taste',
source: 'UC Davis / NeurIPS 2023',
uniqueVintages: 350000,
reviews: 824000,
images: 897000,
vintageRange: '1950-2021',
dataCollection: 'May 2023',
license: 'CC BY-NC-ND 4.0'
};
}
/**
* Search WineSensed dataset for wines
* Note: This requires the CSV dataset to be downloaded locally
*/
async search(query, options = {}) {
try {
const {
limit = 50,
minRating = 0,
maxPrice = 10000,
minYear = 1950,
maxYear = 2023,
country = null,
region = null
} = options;
// Try to load sample data from JSON file
const fs = require('fs');
const path = require('path');
const dataPath = path.join(__dirname, '../data/wine_sample_data.json');
if (fs.existsSync(dataPath)) {
const rawData = fs.readFileSync(dataPath, 'utf8');
const wines = JSON.parse(rawData);
// Filter by query
const queryLower = query.toLowerCase();
let filtered = wines.filter(wine => {
const name = (wine.wine || '').toLowerCase();
const winery = (wine.winery || '').toLowerCase();
const region = (wine.region || '').toLowerCase();
const grapes = (wine.grape_composition || '').toLowerCase();
return name.includes(queryLower) ||
winery.includes(queryLower) ||
region.includes(queryLower) ||
grapes.includes(queryLower);
});
// Apply filters
filtered = filtered.filter(wine => {
const price = parseFloat(wine.price) || 0;
const rating = parseFloat(wine.rating) || 0;
const year = parseInt(wine.year) || 0;
return rating >= minRating &&
price <= maxPrice &&
year >= minYear &&
year <= maxYear;
});
// Limit results
filtered = filtered.slice(0, limit);
// Format wines
const formattedWines = filtered.map(wine => ({
name: wine.wine || 'Unknown',
vintage: wine.year || '',
price: parseFloat(wine.price) || null,
rating: parseFloat(wine.rating) || null,
country: wine.country || '',
region: wine.region || '',
winery: wine.winery || '',
grapes: wine.grape_composition || '',
alcohol: wine.alcohol_percentage || '',
ratingsCount: parseInt(wine.reviews) || 0,
url: `https://www.vivino.com/search/wines?q=${encodeURIComponent(wine.wine)}`,
availability: 'Academic Dataset (UC Davis)',
source: 'UC Davis Wine Database',
type: wine.grape_composition ? wine.grape_composition.split(',')[0].trim() : 'Wine',
vintageId: wine.vintage_id || ''
}));
return {
success: true,
wines: formattedWines,
count: formattedWines.length,
source: 'UC Davis Wine Database',
datasetInfo: '350k+ wines from UC Davis/NeurIPS academic research',
timestamp: new Date().toISOString()
};
}
// Fallback if no data file
return {
success: true,
wines: [],
count: 0,
source: 'WineSensed (UC Davis/NeurIPS)',
message: 'WineSensed dataset requires local download. Dataset contains 350k+ vintages with historical pricing.',
datasetInfo: this.datasetInfo,
timestamp: new Date().toISOString()
};
} catch (error) {
console.error('WineSensed scraper error:', error.message);
return {
success: false,
wines: [],
error: error.message,
source: 'WineSensed'
};
}
}
/**
* Load and parse CSV data from local file
* Requires dataset to be downloaded first
*/
async loadDataset(csvPath) {
return new Promise((resolve, reject) => {
const results = [];
const fs = require('fs');
if (!fs.existsSync(csvPath)) {
return reject(new Error(`Dataset file not found: ${csvPath}`));
}
fs.createReadStream(csvPath)
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', () => {
this.dataCache = results;
this.cacheTimestamp = Date.now();
resolve(results);
})
.on('error', reject);
});
}
/**
* Search loaded dataset
*/
async searchLoadedData(query, options = {}) {
if (!this.dataCache) {
return {
success: false,
wines: [],
error: 'Dataset not loaded. Call loadDataset() first.',
source: 'WineSensed'
};
}
const {
limit = 50,
minRating = 0,
maxPrice = 10000
} = options;
const queryLower = query.toLowerCase();
// Filter wines by query
let wines = this.dataCache.filter(wine => {
const name = (wine.wine || '').toLowerCase();
const winery = (wine.winery || '').toLowerCase();
const region = (wine.region || '').toLowerCase();
return name.includes(queryLower) ||
winery.includes(queryLower) ||
region.includes(queryLower);
});
// Apply filters
wines = wines.filter(wine => {
const price = parseFloat(wine.price) || 0;
const rating = parseFloat(wine.rating) || 0;
return rating >= minRating && price <= maxPrice;
});
// Limit results
wines = wines.slice(0, limit);
// Transform to standard wine format
const formattedWines = wines.map(wine => ({
name: wine.wine || 'Unknown',
vintage: wine.year || '',
price: parseFloat(wine.price) || null,
rating: parseFloat(wine.rating) || null,
country: wine.country || '',
region: wine.region || '',
grapes: wine.grape_composition || '',
alcohol: wine.alcohol_percentage || '',
url: `https://www.vivino.com/search/wines?q=${encodeURIComponent(wine.wine)}`,
availability: 'Historical Data (May 2023)',
source: 'WineSensed (UC Davis/NeurIPS)',
vintageId: wine.vintage_id || ''
}));
return {
success: true,
wines: formattedWines,
count: formattedWines.length,
source: 'WineSensed (UC Davis/NeurIPS)',
timestamp: new Date().toISOString()
};
}
/**
* Get dataset statistics
*/
getStats() {
return {
datasetInfo: this.datasetInfo,
projectUrl: this.projectUrl,
downloadUrl: this.datasetUrl,
citation: 'Bender et al. (2023). Learning to Taste: A Multimodal Wine Dataset. NeurIPS 2023. arXiv:2308.16900',
features: [
'350,000+ unique wine vintages',
'824,000 wine reviews from Vivino',
'897,000 wine label images',
'Historical pricing data (May 2023)',
'Grape composition and varietal info',
'Regional and country data',
'Rating and quality scores',
'Alcohol percentage',
'Vintage years 1950-2021'
],
useCases: [
'Historical price trend analysis',
'Vintage quality comparison',
'Regional wine characteristics',
'Price-to-quality correlation',
'Wine recommendation systems',
'Academic research'
]
};
}
/**
* Check if cache is valid
*/
isCacheValid() {
if (!this.dataCache || !this.cacheTimestamp) {
return false;
}
return (Date.now() - this.cacheTimestamp) < this.cacheDuration;
}
}
module.exports = new WineSensedScraper();