← 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()