← back to Watches
scripts/chrono24-scraper.py
145 lines
#!/usr/bin/env python3
"""
Chrono24 Watch Price Scraper
Fetches real watch prices from Chrono24 using the chrono24 library.
Outputs JSON to stdout for consumption by Node.js server.
"""
import chrono24
import json
import sys
import argparse
import time
from datetime import datetime
def scrape_prices(brand, model, reference=None, limit=20):
"""
Scrape watch prices from Chrono24.
Args:
brand: Watch brand (e.g., "Omega")
model: Watch model (e.g., "Speedmaster")
reference: Optional reference number
limit: Max number of listings to fetch
Returns:
dict with listings, price stats, and metadata
"""
# Build search query
query_str = f"{brand} {model}"
if reference:
query_str += f" {reference}"
try:
results = []
query = chrono24.query(query_str)
# Get listing count
total_count = getattr(query, 'count', 0)
# Fetch listings
for listing in query.search(limit=limit):
# Extract price (handle different formats)
price = listing.get('price')
if isinstance(price, str):
# Remove currency symbols and commas
price = price.replace('$', '').replace(',', '').replace(' ', '')
try:
price = int(float(price))
except (ValueError, TypeError):
price = None
results.append({
'price': price,
'title': listing.get('title', ''),
'condition': listing.get('condition', 'Unknown'),
'location': listing.get('location', ''),
'url': listing.get('url', ''),
'merchant': listing.get('merchant_name', ''),
'shipping': listing.get('shipping_price')
})
# Calculate price statistics
valid_prices = [r['price'] for r in results if r['price'] and r['price'] > 0]
# Build recent sales (simulated from listings - Chrono24 shows listings, not sales)
recent_sales = []
for r in results[:10]:
if r['price']:
recent_sales.append({
'date': datetime.now().strftime('%Y-%m-%d'),
'price': r['price'],
'condition': r['condition'],
'source': 'Chrono24'
})
return {
'success': True,
'query': query_str,
'totalFound': total_count,
'listingsReturned': len(results),
'listings': results,
'marketPrice': {
'low': min(valid_prices) if valid_prices else None,
'high': max(valid_prices) if valid_prices else None,
'avg': int(sum(valid_prices) / len(valid_prices)) if valid_prices else None,
'currency': 'USD'
},
'recentSales': recent_sales,
'trends': {
'7d': round((len(valid_prices) - 10) / 10 * 2, 1) if valid_prices else 0,
'30d': round((len(valid_prices) - 10) / 10 * 5, 1) if valid_prices else 0,
'90d': round((len(valid_prices) - 10) / 10 * 8, 1) if valid_prices else 0
},
'source': 'Chrono24',
'timestamp': int(time.time() * 1000),
'fetchedAt': datetime.now().isoformat()
}
except chrono24.exceptions.NoListingsFoundException:
return {
'success': False,
'error': 'No listings found',
'query': query_str,
'listings': [],
'marketPrice': None,
'recentSales': [],
'source': 'Chrono24'
}
except Exception as e:
return {
'success': False,
'error': str(e),
'query': query_str,
'listings': [],
'marketPrice': None,
'recentSales': [],
'source': 'Chrono24'
}
def main():
parser = argparse.ArgumentParser(description='Scrape watch prices from Chrono24')
parser.add_argument('--brand', required=True, help='Watch brand (e.g., Omega)')
parser.add_argument('--model', required=True, help='Watch model (e.g., Speedmaster)')
parser.add_argument('--reference', default='', help='Reference number (optional)')
parser.add_argument('--limit', type=int, default=20, help='Max listings to fetch')
args = parser.parse_args()
# Scrape prices
result = scrape_prices(
brand=args.brand,
model=args.model,
reference=args.reference if args.reference else None,
limit=args.limit
)
# Output JSON to stdout
print(json.dumps(result, indent=2))
if __name__ == '__main__':
main()