Luxury Asset Price Extraction & Database Management

Job ID: 40595956

Budget: $30 – $250 USD

# Data Engineer / Web Scraping Specialist — Luxury Asset Price Index (Watches, Handbags, Prints) — 10-Year Historical Dataset + Ongoing Pipeline

## Overview
I run a luxury-asset price index project. I need help sourcing **transaction-level historical price data** across **three independent categories**:

Few exemples :
1. **Watches** — Rolex, Patek Philippe, Audemars Piguet, Omega, Cartier, Vacheron Constantin, Richard Mille, etc.
2. **Handbags** — Hermès (Birkin / Kelly), Chanel (Classic Flap), Louis Vuitton, Dior, etc.
3. **Prints / editions** — Picasso, Warhol, Chagall, Miró, Basquiat, Hockney, Banksy, etc.

Each category is a **separate dataset** feeding a separate database table. I also need a **system that keeps pulling new completed sales automatically** going forward.

This is a data-quality-critical project. The single most important rule is below — please read it before applying.

---

## #1 RULE — completed transactions ONLY (non-negotiable)
Every row must be a **confirmed, completed sale** (hammer price paid / transaction closed).

**Exclude:** active/current listings, asking prices, estimates without a realized result, unsold/withdrawn/passed/"bought-in" lots, and upcoming auction lots. If a source mixes sold and unsold items, **you filter to sold-only before delivery.** I will not accept raw scraped listings for me to filter myself.

*(One narrow exception for handbag resale platforms — see the note under section B.)*

---

## Deliverable 1 — Historical dataset (2016–2026), one-time

Each category maps directly to one of our database tables — **please use these exact field names** so your delivery can be imported with no remapping.

**Currency — important:** always deliver each sale in its **original transaction currency** and do **not** pre-convert to EUR/USD/etc. Provide `sale_price` + `currency` as sold. We handle all FX conversion on our side.

### A. Watches → table `auction_results`

| Column | Description | Status |
|---|---|---|
| source | Platform/auction house name (e.g. "Christie's", "Chrono24") | Mandatory |
| auction_house | Same as source if it's an auction; leave empty for resale platforms | If applicable |
| brand | e.g. Rolex, Patek Philippe | Mandatory |
| model | e.g. Daytona, Nautilus | Mandatory |
| reference | Exact factory reference, full raw string, never abbreviated (e.g. `116500LN`, `5711/1A-010`) | **Mandatory — our #1 matching key** |
| sale_price | Realized / hammer price | Mandatory |
| currency | Original transaction currency — do not pre-convert (see note above) | Mandatory |
| auction_date | Exact sale date | Mandatory |
| source_link | Direct link to the specific lot/listing | Mandatory |
| condition | As stated by source | If available |
| case_material | Steel, gold, platinum, two-tone… | If available |
| case_size_mm | Case diameter | If available |
| box_and_papers | Full set / watch only / papers only | Nice to have (not yet in our schema — we'll add the column if you can supply it) |
| year_of_production | Manufacture year, distinct from sale date | Nice to have (same as above) |
| dial_color | — | Nice to have (same as above) |

> **`brand` + `reference` are how we match a sale to our index basket** (prefix-match on the reference string). A wrong or truncated reference silently drops the row from the index — it matters more than the price itself.

### B. Handbags → table `bag_prices`

| Column | Description | Status |
|---|---|---|
| source | Platform/auction house name | Mandatory |
| auction_house | Leave empty for resale platforms (Fashionphile, Rebag, etc.) | If applicable |
| brand | Hermès, Chanel, Louis Vuitton, Dior… | Mandatory |
| model | Must contain the recognizable model name as a substring, e.g. "Birkin 30", "Classic Flap" | **Mandatory — our matching key** |
| size | e.g. Birkin 25/30/35, Kelly 28 | If available |
| sale_price | Realized price | Mandatory |
| currency | Original transaction currency — do not pre-convert | Mandatory |
| snapshot_date | Exact date of the sale (or, for resale marketplaces, the date the listing/price was recorded) | Mandatory |
| source_link | Direct link to the item | Mandatory |
| condition | As stated by source | If available |
| material | Togo, Epsom, caviar, canvas… | If available |
| full_name | Full listing title, as a fallback description | If available |
| color, hardware, box_and_authenticity, year_or_datestamp | — | Nice to have (not yet in our schema — flag if consistently available and we'll add columns) |

> **Note on resale marketplaces (Fashionphile, Rebag, Collector Square, etc.):** if the platform doesn't publish a "sold" archive, the **current listed price of a specific, already-authenticated, uniquely identified item** is acceptable — that's how we currently treat Collector Square. What's **not** acceptable is a generic "starting from" or aggregated price. If the platform has an actual completed-sales feed (Vestiaire Collective "sold", The RealReal), prefer that.

### C. Prints / editions → table `art_prices`

| Column | Description | Status |
|---|---|---|
| source | Auction house name | Mandatory |
| artist_name | Full recognizable name (e.g. "Pablo Picasso") | **Mandatory — our matching key** |
| artwork_title | Title of the work/edition | Mandatory |
| sale_price + currency | Realized price in original currency — do not pre-convert (we store this as `sold_price_eur` after conversion, but you deliver raw `sale_price` + `currency`) | Mandatory |
| is_sold | Should always be True in your delivery — see Rule #1 | Mandatory |
| sale_date | Exact auction date | Mandatory |
| url | Direct link to the lot | Mandatory |
| lot_number, sale_title, sale_id | Auction lot reference info | If available |
| technique | Lithograph, screenprint, etching, giclée… | If available |
| edition | e.g. `12/100`, or "AP" (artist's proof) | If available |
| signed | e.g. "Signed", "Unsigned", "Stamp" (free text, not just True/False) | If available |
| dimensions | Sheet / image size | If available |
| year_created | Year the work/edition was created | If available |
| image_url | — | Nice to have |
| condition, provenance | — | Nice to have |

---

## Deliverable 2 — Ongoing extraction system
A maintainable pipeline (Python preferred, to fit our existing stack) that periodically pulls **newly completed sales** from the same sources and appends them to the correct table, **without creating duplicates**. Ideally runnable on a schedule. Clean, documented, handover-friendly code.

*You may quote for Deliverable 1 only, or both — tell me in your proposal.*

---

## Where to look (examples, not exhaustive)

**Auction houses (highest quality — public "prices realized" archives, all 3 categories):**
Christie's, Sotheby's, Phillips (watches: Bacs & Russo), Bonhams, Antiquorum (watches), Artcurial, Drouot.

- **Watches — resale / secondary:** Chrono24, WatchBox, Bob's Watches, Crown & Caliber, WatchFinder, eBay "sold" filter. Aggregators: WatchCharts, Subdial.
- **Handbags — resale (see the section B note on what counts):** Fashionphile, Rebag, Collector Square, The RealReal, Vestiaire Collective (sold), plus auction "Handbags & Accessories" departments.
- **Prints — auction results only:** the auction houses above (Prints & Multiples departments); aggregators like Artnet, MutualArt, Artprice may offer paid historical result databases.

If any aggregator offers a **paid historical sold-price API or dataset**, **flag it** with the cost — I may buy access instead of scraping.

## Data quality standards
- One row = one unique, verifiable transaction (no duplicates).
- Every row traceable to its **exact source URL** (`source_link` / `url`) — I need to audit any row.
- Reference numbers (watches) and model names (bags) in raw format, never truncated — they are the matching keys.
- Deliver original currency, never pre-converted.
- 10-year coverage as continuous as sources allow — **flag any gaps**.
- Structured output only (CSV / Excel / JSON). No PDFs or screenshots.

## Skills I'm looking for
- Python web scraping (requests / BeautifulSoup / Playwright or similar), handling JS-rendered pages and embedded JSON.
- Structured data cleaning and de-duplication.
- Bonus: familiarity with Supabase / PostgreSQL, and with luxury / auction data.

## Before you start — confirm back to me
1. Which sources you'll use per category, and expected row count / coverage per source.
2. Whether any source needs a paid API / subscription (I'll decide buy vs. scrape).
3. Your timeline and delivery approach (incremental vs. one batch).

## How to apply
In your proposal, please include:
- A one-line answer to: *how do you make sure only completed/sold transactions are captured?*
- One example of a similar scraping / data project you've done.
- Your quote for **Deliverable 1**, and separately for **Deliverable 2** if you want it.

Proposals that ignore the #1 rule or the "confirm back to me" points will not be considered.