← back to Handbag Authentication
crawler/ebay-japan.js
145 lines
const puppeteer = require('puppeteer');
class EbayJapanScraper {
constructor() {
this.baseUrl = 'https://www.ebay.com/sch/i.html';
this.brands = ['Gucci', 'Louis Vuitton', 'Chanel', 'Hermes', 'Prada', 'Dior', 'Fendi', 'Balenciaga', 'Celine', 'Bottega Veneta'];
this.maxPages = 5;
}
async scrapeSearch(brand, page = 1) {
// Search for items from Japan that ship to US
const searchUrl = `${this.baseUrl}?_nkw=${encodeURIComponent(brand + ' bag')}&_sacat=0&LH_ItemCondition=3000|4000&_sop=10&_pgn=${page}&LH_PrefLoc=2`;
try {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const browserPage = await browser.newPage();
await browserPage.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
console.log(`Scraping eBay Japan: ${searchUrl}`);
await browserPage.goto(searchUrl, { waitUntil: 'networkidle2', timeout: 30000 });
// Wait for search results
await browserPage.waitForSelector('.s-item, .srp-results li', { timeout: 10000 }).catch(() => {});
const listings = await browserPage.evaluate(() => {
const items = [];
const productElements = document.querySelectorAll('.s-item');
productElements.forEach((item) => {
try {
// Skip sponsored items
if (item.classList.contains('s-item--ad')) return;
// Extract image
const imgEl = item.querySelector('img');
const image = imgEl?.src || '';
// Extract title
const titleEl = item.querySelector('.s-item__title');
const title = titleEl?.textContent?.trim() || '';
// Skip "Shop on eBay" banner
if (title.includes('Shop on eBay')) return;
// Extract price
const priceEl = item.querySelector('.s-item__price');
const priceText = priceEl?.textContent?.trim() || '';
// Parse USD price
const priceMatch = priceText.match(/\$?([\d,]+\.?\d*)/);
const priceUSD = priceMatch ? parseFloat(priceMatch[1].replace(/,/g, '')) : 0;
// Estimate JPY price (USD * 150)
const priceJPY = Math.round(priceUSD * 150);
// Extract URL
const linkEl = item.querySelector('.s-item__link');
const url = linkEl?.href || '';
// Extract ID from URL
const idMatch = url.match(/\/itm\/(\d+)/);
const id = idMatch?.[1] || url;
// Extract condition
const conditionEl = item.querySelector('.SECONDARY_INFO');
const condition = conditionEl?.textContent?.trim() || 'Pre-Owned';
// Extract shipping info to verify it's from Japan
const shippingEl = item.querySelector('.s-item__shipping, .s-item__logisticsCost');
const shippingText = shippingEl?.textContent?.trim() || '';
const fromJapan = shippingText.toLowerCase().includes('japan') ||
shippingText.includes('+$') ||
item.textContent.includes('Japan');
// Extract listing type (auction or buy it now)
const listingType = item.querySelector('.s-item__purchase-options-with-icon')?.textContent?.includes('auction') ? 'auction' : 'buy';
if (title && priceUSD > 0 && fromJapan) {
items.push({
external_id: id,
title,
price_jpy: priceJPY,
price_usd: priceUSD,
image_url: image,
thumbnail_url: image,
product_url: url,
condition,
listing_type: listingType
});
}
} catch (e) {
console.error('Error parsing eBay item:', e);
}
});
return items;
});
await browser.close();
return listings;
} catch (error) {
console.error(`Error scraping eBay Japan page ${page}:`, error.message);
return [];
}
}
async scrapeAllBrands() {
const allListings = [];
for (const brand of this.brands) {
console.log(`\nScraping eBay Japan for ${brand}...`);
for (let page = 1; page <= this.maxPages; page++) {
const listings = await this.scrapeSearch(brand, page);
if (listings.length === 0) {
console.log(`No more results for ${brand} at page ${page}`);
break;
}
// Add brand and source info
listings.forEach(listing => {
listing.brand = brand;
listing.source = 'ebay_japan';
});
allListings.push(...listings);
console.log(`Found ${listings.length} items on page ${page}`);
// Rate limiting
await new Promise(resolve => setTimeout(resolve, 3000 + Math.random() * 2000));
}
}
return allListings;
}
}
module.exports = EbayJapanScraper;