Dev.to WebDev 🛠 Dev 👁 0 📖 4 min read

Normalizing Luxury Resale Attributes Across 7 Inconsistent Departments

Secondary luxury resale markets do not follow uniform product schemas. If you write scrapers for authenticated resale platforms like The RealReal, you immediately hit schema fragmentation across departments. Clothing use

Secondary luxury resale markets do not follow uniform product schemas. If you write scrapers for authenticated resale platforms like The RealReal, you immediately hit schema fragmentation across departments. Clothing uses standard apparel sizes (CLOTHING_SIZE or FOREIGN_SIZE), footwear uses shoe sizing (SHOE_SIZE), jewelry stores dimensions as RING_SIZE alongside METAL_TYPE, and watches store physical dimensions under CASE_DIAMETER.

Extracting these disparate properties into a clean analytics pipeline usually requires writing custom normalization logic for each product category. If you try to track resale margins or build pricing models across multiple luxury departments, raw API responses leave critical fields empty unless your scraper resolves department-specific attribute mappings.

The therealreal-scraper Actor solves this by consolidating department-specific attributes across seven top-level departments (women, men, jewelry, watches, art, home, and kids) into unified output fields like size and material, while surfacing specialized luxury metadata when present.

Resolving Department-Specific Attributes

When crawling authenticated listings, general-purpose scrapers often drop department-level metadata. The RealReal's internal facet architecture separates fields based on category context. For example, a collector tracking luxury timepieces needs movement mechanisms and reference numbers, while a jewelry analyst needs clarity and gemstone details.

The scraper maps these underlying attributes into concrete top-level fields:

  • Watches: Populates watchModel, referenceNumber, watchCollection, movementType, caseMaterial, bandMaterial, watchStyles[], and complications[].
  • Jewelry: Populates caratWeight (summed total carat weight across stones of that type), clarityGrade, colorGrade, gemstone, stoneType, and stoneShapes[].
  • Apparel: Populates category-specific attributes such as dressSilhouette (e.g., "Wrap Dress", "Evening Gown") for dresses and chestSize (e.g., "42", "48 +") for men's garments.
  • Condition: Separates the structured condition grade (As Is, Fair, Good, Very Good, Excellent, Pristine) from the free-text conditionNotes field available when fetching complete detail.

Because empty fields are omitted from emitted records, items only contain the attributes disclosed by the source listing.

Discovering Live Facets Before Extraction

Targeting specific categories or designers often fails when scrapers rely on hardcoded URL slugs. If a brand changes its catalog naming or a taxonomy node shifts, scrapers return zero results.

To prevent blind querying, the Actor provides two discovery modes:

  1. discoverCategories: Reads The RealReal's live category tree straight from facet data, returning records with name, department, categoryPath, depth, and current productCount.
  2. discoverDesigners: Emits up to 200 designer names for a department or category, returning designerName, designerSlug, designerId, and productCount.

Using discovery runs first ensures that downstream extraction runs target valid categoryPath and designerSlug inputs with active inventory.

Execution Walkthrough: Building a Filtered Watch Dataset

To collect listings for luxury watches matching specific physical attributes, configure the scraper to target the watches department with precise attribute filtering.

1. Configure the Run Input

Define the run parameters using the Actor's filtering fields. The configuration below queries the watches department, applies price boundaries, filters for automatic movements in stainless steel cases, and caps the volume using maxItems.

{
  "mode": "byCategory",
  "gender": "watches",
  "minPrice": 2000,
  "maxPrice": 15000,
  "movementType": "Automatic",
  "caseMaterial": "Stainless Steel",
  "condition": ["Very Good", "Excellent", "Pristine"],
  "sortBy": "price_asc",
  "maxItems": 100
}

2. Execute via the Apify Python SDK

Run the Actor and process the dataset records programmatically:

from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")

run_input = {
    "mode": "byCategory",
    "gender": "watches",
    "minPrice": 2000,
    "maxPrice": 15000,
    "movementType": "Automatic",
    "caseMaterial": "Stainless Steel",
    "condition": ["Very Good", "Excellent", "Pristine"],
    "sortBy": "price_asc",
    "maxItems": 100,
}

run = client.actor("crawlerbros/therealreal-scraper").call(run_input=run_input)

dataset_items = client.dataset(run["defaultDatasetId"]).list_items().items

for item in dataset_items:
    print(
        f"{item.get('brand')} {item.get('watchModel')} "
        f"| Ref: {item.get('referenceNumber')} "
        f"| Price: ${item.get('priceUsd')} "
        f"| Condition: {item.get('condition')}"
    )

3. Handle Product Detail Payloads

When running in byUrl mode with explicit productUrls, the returned dataset items contain deep product data, including measurements[], authenticationInfo, carbonSavedKg, waterSavedLiters, and valuationReportUrl:

{
  "recordType": "product",
  "productId": "10984211",
  "sku": "WCH12345",
  "brand": "Rolex",
  "name": "Submariner Date 116610LN",
  "department": "watches",
  "condition": "Excellent",
  "conditionNotes": "Minor scratches throughout metal.",
  "movementType": "Automatic",
  "caseMaterial": "Stainless Steel",
  "priceUsd": 11500,
  "estRetailPriceUsd": 10250,
  "productUrl": "https://www.therealreal.com/products/watches/rolex-submariner-date-116610ln",
  "scrapedAt": "2026-07-27T10:15:30.000Z"
}

Actor Billing Mechanics

This Actor operates on a pay-per-event pricing model rather than standard compute runtimes. Charges are calculated directly from two event types:

  1. Actor Start (apify-actor-start): A flat charge of $0.005 per GB of memory allocated to the run, incurred once when execution begins.
  2. Dataset Result (apify-default-dataset-item): Billed per emitted item in the default dataset. Pricing follows volume tiers:
    • FREE: $0.005 per event
    • BRONZE: $0.00433 per event
    • SILVER: $0.00367 per event
    • GOLD: $0.003 per event
    • PLATINUM: $0.003 per event
    • DIAMOND: $0.003 per event

For example, on the standard FREE tier, a run allocated 1 GB of memory that extracts 500 watch listings incurs $0.005 for the start event and $2.50 for the 500 emitted results ($0.005 × 500).

Pipeline Boundaries

This Actor is designed for extraction and facet discovery; it does not monitor real-time shopping cart state or automate the checkout process. Additionally, while byUrl mode retrieves full condition descriptions and measurement arrays, markdown indicators like discountPercent are rarely present because the underlying browse listing API does not reliably expose markdown signals. Filtering pipelines requiring strict markdown verification must calculate discounts downstream by comparing priceUsd against originalPriceUsd or estRetailPriceUsd.

The RealReal Scraper is the Actor behind these examples. If a selector in your own version breaks, compare your output against the fields listed in its README first.

Prices quoted above are this Actor's published pay-per-event rates on the Apify Store, read from the Apify platform API on 2026-09-21. Check the Actor page for the current rates.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.