# Scaev - Architecture & Data Flow ## System Overview The scraper follows a **3-phase hierarchical crawling pattern** to extract auction and lot data from Troostwijk Auctions website. ## Architecture Diagram ```mariadb ┌─────────────────────────────────────────────────────────────────┐ │ TROOSTWIJK SCRAPER │ └─────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────┐ │ PHASE 1: COLLECT AUCTION URLs │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ Listing Page │────────▶│ Extract /a/ │ │ │ │ /auctions? │ │ auction URLs │ │ │ │ page=1..N │ └──────────────┘ │ │ └──────────────┘ │ │ │ ▼ │ │ [ List of Auction URLs ] │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ PHASE 2: EXTRACT LOT URLs FROM AUCTIONS │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ Auction Page │────────▶│ Parse │ │ │ │ /a/... │ │ __NEXT_DATA__│ │ │ └──────────────┘ │ JSON │ │ │ │ └──────────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ Save Auction │ │ Extract /l/ │ │ │ │ Metadata │ │ lot URLs │ │ │ │ to DB │ └──────────────┘ │ │ └──────────────┘ │ │ │ ▼ │ │ [ List of Lot URLs ] │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ PHASE 3: SCRAPE LOT DETAILS + API ENRICHMENT │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ Lot Page │────────▶│ Parse │ │ │ │ /l/... │ │ __NEXT_DATA__│ │ │ └──────────────┘ │ JSON │ │ │ └──────────────┘ │ │ │ │ │ ┌─────────────────────────┼─────────────────┐ │ │ ▼ ▼ ▼ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ GraphQL API │ │ Bid History │ │ Save Images │ │ │ │ (Bidding + │ │ REST API │ │ URLs to DB │ │ │ │ Enrichment) │ │ (per lot) │ └──────────────┘ │ │ └──────────────┘ └──────────────┘ │ │ │ │ │ ▼ │ │ └──────────┬────────────┘ [Optional Download │ │ ▼ Concurrent per Lot] │ │ ┌──────────────┐ │ │ │ Save to DB: │ │ │ │ - Lot data │ │ │ │ - Bid data │ │ │ │ - Enrichment │ │ │ └──────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` ## Database Schema ```mariadb ┌──────────────────────────────────────────────────────────────────┐ │ CACHE TABLE (HTML Storage with Compression) │ ├──────────────────────────────────────────────────────────────────┤ │ cache │ │ ├── url (TEXT, PRIMARY KEY) │ │ ├── content (BLOB) -- Compressed HTML (zlib) │ │ ├── timestamp (REAL) │ │ ├── status_code (INTEGER) │ │ └── compressed (INTEGER) -- 1=compressed, 0=plain │ └──────────────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────────┐ │ AUCTIONS TABLE │ ├──────────────────────────────────────────────────────────────────┤ │ auctions │ │ ├── auction_id (TEXT, PRIMARY KEY) -- e.g. "A7-39813" │ │ ├── url (TEXT, UNIQUE) │ │ ├── title (TEXT) │ │ ├── location (TEXT) -- e.g. "Cluj-Napoca, RO" │ │ ├── lots_count (INTEGER) │ │ ├── first_lot_closing_time (TEXT) │ │ └── scraped_at (TEXT) │ └──────────────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────────┐ │ LOTS TABLE (Core + Enriched Intelligence) │ ├──────────────────────────────────────────────────────────────────┤ │ lots │ │ ├── lot_id (TEXT, PRIMARY KEY) -- e.g. "A1-28505-5" │ │ ├── auction_id (TEXT) -- FK to auctions │ │ ├── url (TEXT, UNIQUE) │ │ ├── title (TEXT) │ │ │ │ │ ├─ BIDDING DATA (GraphQL API) ──────────────────────────────────┤ │ ├── current_bid (TEXT) -- Current bid amount │ │ ├── starting_bid (TEXT) -- Initial/opening bid │ │ ├── minimum_bid (TEXT) -- Next minimum bid │ │ ├── bid_count (INTEGER) -- Number of bids │ │ ├── bid_increment (REAL) -- Bid step size │ │ ├── closing_time (TEXT) -- Lot end date │ │ ├── status (TEXT) -- Minimum bid status │ │ │ │ │ ├─ BID INTELLIGENCE (Calculated from bid_history) ──────────────┤ │ ├── first_bid_time (TEXT) -- First bid timestamp │ │ ├── last_bid_time (TEXT) -- Latest bid timestamp │ │ ├── bid_velocity (REAL) -- Bids per hour │ │ │ │ │ ├─ VALUATION & ATTRIBUTES (from __NEXT_DATA__) ─────────────────┤ │ ├── brand (TEXT) -- Brand from attributes │ │ ├── model (TEXT) -- Model from attributes │ │ ├── manufacturer (TEXT) -- Manufacturer name │ │ ├── year_manufactured (INTEGER) -- Year extracted │ │ ├── condition_score (REAL) -- 0-10 condition rating │ │ ├── condition_description (TEXT) -- Condition text │ │ ├── serial_number (TEXT) -- Serial/VIN number │ │ ├── damage_description (TEXT) -- Damage notes │ │ ├── attributes_json (TEXT) -- Full attributes JSON │ │ │ │ │ ├─ LEGACY/OTHER ─────────────────────────────────────────────────┤ │ ├── viewing_time (TEXT) -- Viewing schedule │ │ ├── pickup_date (TEXT) -- Pickup schedule │ │ ├── location (TEXT) -- e.g. "Dongen, NL" │ │ ├── description (TEXT) -- Lot description │ │ ├── category (TEXT) -- Lot category │ │ ├── sale_id (INTEGER) -- Legacy field │ │ ├── type (TEXT) -- Legacy field │ │ ├── year (INTEGER) -- Legacy field │ │ ├── currency (TEXT) -- Currency code │ │ ├── closing_notified (INTEGER) -- Notification flag │ │ └── scraped_at (TEXT) -- Scrape timestamp │ │ FOREIGN KEY (auction_id) → auctions(auction_id) │ └──────────────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────────┐ │ IMAGES TABLE (Image URLs & Download Status) │ ├──────────────────────────────────────────────────────────────────┤ │ images ◀── THIS TABLE HOLDS IMAGE LINKS│ │ ├── id (INTEGER, PRIMARY KEY AUTOINCREMENT) │ │ ├── lot_id (TEXT) -- FK to lots │ │ ├── url (TEXT) -- Image URL │ │ ├── local_path (TEXT) -- Path after download │ │ └── downloaded (INTEGER) -- 0=pending, 1=downloaded │ │ FOREIGN KEY (lot_id) → lots(lot_id) │ │ UNIQUE INDEX idx_unique_lot_url ON (lot_id, url) │ └──────────────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────────┐ │ BID_HISTORY TABLE (Complete Bid Tracking for Intelligence) │ ├──────────────────────────────────────────────────────────────────┤ │ bid_history ◀── REST API: /bidding-history │ │ ├── id (INTEGER, PRIMARY KEY AUTOINCREMENT) │ │ ├── lot_id (TEXT) -- FK to lots │ │ ├── bid_amount (REAL) -- Bid in EUR │ │ ├── bid_time (TEXT) -- ISO 8601 timestamp │ │ ├── is_autobid (INTEGER) -- 0=manual, 1=autobid │ │ ├── bidder_id (TEXT) -- Anonymized bidder UUID │ │ ├── bidder_number (INTEGER) -- Bidder display number │ │ └── created_at (TEXT) -- Record creation timestamp │ │ FOREIGN KEY (lot_id) → lots(lot_id) │ │ INDEX idx_bid_history_lot ON (lot_id) │ │ INDEX idx_bid_history_time ON (bid_time) │ └──────────────────────────────────────────────────────────────────┘ ``` ## Sequence Diagram ``` User Scraper Playwright Cache DB Data Tables │ │ │ │ │ │ Run │ │ │ │ ├──────────────▶│ │ │ │ │ │ │ │ │ │ │ Phase 1: Listing Pages │ │ │ ├───────────────▶│ │ │ │ │ goto() │ │ │ │ │◀───────────────┤ │ │ │ │ HTML │ │ │ │ ├───────────────────────────────▶│ │ │ │ compress & cache │ │ │ │ │ │ │ │ │ Phase 2: Auction Pages │ │ │ ├───────────────▶│ │ │ │ │◀───────────────┤ │ │ │ │ HTML │ │ │ │ │ │ │ │ │ │ Parse __NEXT_DATA__ JSON │ │ │ │────────────────────────────────────────────────▶│ │ │ │ │ INSERT auctions │ │ │ │ │ │ │ Phase 3: Lot Pages │ │ │ ├───────────────▶│ │ │ │ │◀───────────────┤ │ │ │ │ HTML │ │ │ │ │ │ │ │ │ │ Parse __NEXT_DATA__ JSON │ │ │ │────────────────────────────────────────────────▶│ │ │ │ │ INSERT lots │ │ │────────────────────────────────────────────────▶│ │ │ │ │ INSERT images│ │ │ │ │ │ │ │ Export to CSV/JSON │ │ │ │◀────────────────────────────────────────────────┤ │ │ Query all data │ │ │◀──────────────┤ │ │ │ │ Results │ │ │ │ ``` ## Data Flow Details ### 1. **Page Retrieval & Caching** ``` Request URL │ ├──▶ Check cache DB (with timestamp validation) │ │ │ ├─[HIT]──▶ Decompress (if compressed=1) │ │ └──▶ Return HTML │ │ │ └─[MISS]─▶ Fetch via Playwright │ │ │ ├──▶ Compress HTML (zlib level 9) │ │ ~70-90% size reduction │ │ │ └──▶ Store in cache DB (compressed=1) │ └──▶ Return HTML for parsing ``` ### 2. **JSON Parsing Strategy** ``` HTML Content │ └──▶ Extract