← back to Handbag Auth Nextjs

CORRECT_SELECTORS.md

565 lines

# CORRECT CSS SELECTORS AND DATA EXTRACTION METHODS

**CRITICAL:** Most sites don't use traditional CSS selectors because they render with JavaScript frameworks. The "selectors" below are actually JSON paths and API endpoints.

---

## 1. Fashionphile - USE JSON, NOT CSS

**Wrong Approach:** CSS selectors on HTML
**Correct Approach:** Parse `__NEXT_DATA__` script tag

### Method 1: Extract JSON (RECOMMENDED)

```javascript
// Selector for the data
const selector = 'script#__NEXT_DATA__';

// How to use it
const html = await page.content();
const $ = cheerio.load(html);
const jsonText = $('script#__NEXT_DATA__').html();
const data = JSON.parse(jsonText);

// Path to products
const products = data.props.pageProps.serverState
  .initialResults.prod_ecom_products_date_desc.results;
```

### JSON Structure

```json
{
  "props": {
    "pageProps": {
      "serverState": {
        "initialResults": {
          "prod_ecom_products_date_desc": {
            "results": [
              {
                "objectID": "12345",
                "name": "Chanel Classic Flap Bag",
                "price": 5200,
                "original_price": 8500,
                "brand": "Chanel",
                "condition": "Excellent",
                "image_url": "https://...",
                "url": "/products/chanel-classic-flap-bag-12345",
                "category": "Handbags",
                "color": "Black",
                "material": "Caviar Leather"
              }
            ],
            "nbPages": 42
          }
        }
      }
    }
  }
}
```

### Fallback CSS Selectors (if JSON fails)

Only use these if `__NEXT_DATA__` is not available:

| Element | Selector | Notes |
|---------|----------|-------|
| Container | `article[data-product-id]` | May not exist until JS renders |
| Title | `h2.product-name` | |
| Price | `span[data-testid='product-price']` | |
| Image | `img[data-testid='product-image']` | Check for lazy loading |
| Link | `a[data-testid='product-link']` | |

**Pagination:** `?page=N` in URL

---

## 2. Rebag - USE SHOPIFY API, NOT CSS

**Wrong Approach:** CSS selectors on HTML
**Correct Approach:** Call `/products.json` API endpoint

### API Endpoint

```
https://shop.rebag.com/products.json?limit=250&page=1
```

### No Puppeteer Needed!

```javascript
const axios = require('axios');

const response = await axios.get('https://shop.rebag.com/products.json?limit=250&page=1');
const products = response.data.products;

products.forEach(product => {
  console.log({
    id: product.id,
    title: product.title,
    price: product.variants[0].price,
    vendor: product.vendor,
    image: product.images[0].src,
    url: `https://shop.rebag.com/products/${product.handle}`
  });
});
```

### JSON Response Structure

```json
{
  "products": [
    {
      "id": 8137413689521,
      "title": "Chanel So Black Reissue 2.55 Flap Bag",
      "vendor": "Chanel",
      "product_type": "Handbag",
      "handle": "handbags-chanel-so-black-reissue",
      "tags": ["Chanel", "Black", "Leather"],
      "variants": [
        {
          "id": 44670519640241,
          "price": "3115.00",
          "compare_at_price": null,
          "sku": "297714/3"
        }
      ],
      "images": [
        {
          "src": "//shop.rebag.com/cdn/shop/files/image.jpg"
        }
      ]
    }
  ]
}
```

### Alternative: Extract from Page JSON

If the API is disabled, extract from embedded data:

```javascript
// Find script tag with product data
const scriptContent = $('script:contains("productVariants")').html();
const match = scriptContent.match(/"productVariants":\s*(\[.*?\])/s);
const variants = JSON.parse(match[1]);
```

### Pagination

```javascript
let page = 1;
while (true) {
  const url = `https://shop.rebag.com/products.json?limit=250&page=${page}`;
  const response = await axios.get(url);

  if (response.data.products.length === 0) break;

  // Process products...

  page++;
  await sleep(1000); // Rate limiting
}
```

---

## 3. The RealReal - REQUIRES ADVANCED BOT BYPASS

**Status:** BLOCKED by Cloudflare (403 errors)
**Approach:** puppeteer-real-browser with Turnstile auto-solve

### Likely Selectors (Cannot Verify Due to Blocking)

| Element | Likely Selector | Alternatives |
|---------|----------------|--------------|
| Container | `article[data-testid="product-card"]` | `.product-tile`, `article[class*="product"]` |
| Title | `h3[data-testid="product-title"]` | `h3`, `a.product-link` |
| Price | `span[data-testid="product-price"]` | `.price`, `[class*="price"]` |
| Image | `img[data-testid="product-image"]` | `img` (first in container) |
| Link | `a[data-testid="product-link"]` | `a` (first in container) |

### Required Configuration

```javascript
const { connect } = require('puppeteer-real-browser');

const { page, browser } = await connect({
  headless: 'auto',
  fingerprint: true,      // Generate unique fingerprint
  turnstile: true,        // Auto-solve Cloudflare Turnstile
  tf: true,
  args: [
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--disable-blink-features=AutomationControlled'
  ]
});

await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
await page.setViewport({ width: 1920, height: 1080 });

await page.goto(url, {
  waitUntil: 'networkidle2',
  timeout: 120000  // 2 minutes for Cloudflare
});

// Wait extra time for challenge
await sleep(10000);
```

### Detection Strategy

Since selectors are unknown, try multiple:

```javascript
const possibleSelectors = [
  'article[data-testid="product-card"]',
  '.product-tile',
  '[data-testid="product"]',
  'article[class*="product"]',
  'div[class*="ProductCard"]',
  '[class*="ProductGrid"] > div'
];

let foundSelector = null;
for (const selector of possibleSelectors) {
  try {
    await page.waitForSelector(selector, { timeout: 5000 });
    foundSelector = selector;
    break;
  } catch (e) {
    continue;
  }
}
```

### Extraction

```javascript
const products = await page.evaluate((selector) => {
  const items = document.querySelectorAll(selector);

  return Array.from(items).map(item => {
    const title = item.querySelector('h3')?.textContent.trim() ||
                  item.querySelector('[data-testid="product-title"]')?.textContent.trim();

    const price = item.querySelector('[data-testid="product-price"]')?.textContent.trim() ||
                  item.querySelector('.price')?.textContent.trim();

    const image = item.querySelector('img')?.src ||
                  item.querySelector('img')?.getAttribute('data-src');

    const url = item.querySelector('a')?.href;

    return { title, price, image, url };
  });
}, foundSelector);
```

### Additional Requirements

- Residential proxies (datacenter IPs often blocked)
- 5-10 second delays between requests
- Random mouse movements and scrolling
- Success rate: 60-70% even with perfect setup

---

## 4. Vestiaire Collective - USE JSON + PROXIES

**Status:** BLOCKED by Cloudflare (403 errors)
**Approach:** Parse `__NEXT_DATA__` with HTTP/2 client and residential proxies

### JSON Selector

```
script#__NEXT_DATA__::text
```

### JSON Path (Product Page)

```
data.props.pageProps.product
```

### Product Fields

```json
{
  "productId": "12345",
  "name": "Chanel Classic Flap Bag",
  "brand": "Chanel",
  "pricing": {
    "price": 3500,
    "currency": "USD"
  },
  "seller": {
    "username": "luxuryseller",
    "rating": 4.8
  },
  "condition": "Very Good",
  "size": "Medium",
  "material": "Lambskin Leather",
  "color": "Black",
  "images": [
    {
      "url": "https://...",
      "position": 1
    }
  ]
}
```

### Required Setup (Python is better for this)

```python
import httpx
import json
from parsel import Selector

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',
    'Connection': 'keep-alive'
}

# MUST use residential proxy
proxy = "http://user:pass@residential-proxy.com:8080"

async with httpx.AsyncClient(
    headers=headers,
    http2=True,  # Required for Cloudflare
    proxies=proxy
) as client:
    response = await client.get(product_url)

    selector = Selector(response.text)
    json_text = selector.css('script#__NEXT_DATA__::text').get()
    data = json.loads(json_text)

    product = data['props']['pageProps']['product']
```

### Better Approach: Sitemap Scraping

1. Get product URLs from sitemap: `https://us.vestiairecollective.com/sitemaps/https_sitemap-en.xml`
2. Extract URLs with CSS selector: `url > loc::text`
3. Scrape each product page individually
4. Handle 308 redirects (sold items)

### JavaScript Alternative (Less Reliable)

```javascript
const axios = require('axios');
const cheerio = require('cheerio');

// Requires HTTP/2 proxy with residential IPs
const response = await axios.get(url, {
  headers: {
    'User-Agent': 'Mozilla/5.0...',
    'Accept-Language': 'en-US,en;q=0.9',
  },
  proxy: {
    host: 'residential-proxy.com',
    port: 8080,
    auth: { username: 'user', password: 'pass' }
  },
  httpAgent: new require('http2-wrapper').Agent()
});

const $ = cheerio.load(response.data);
const jsonData = JSON.parse($('script#__NEXT_DATA__').html());
```

---

## 5. Farfetch - NO RELIABLE SELECTORS (Use API Interception or Commercial API)

**Status:** CONNECTION TIMEOUT (Akamai Bot Manager)
**Success Rate DIY:** 15-20%
**Recommended:** Commercial API

### Approach 1: Find Internal API

Farfetch likely uses GraphQL or REST API. Intercept network requests:

```javascript
const puppeteer = require('puppeteer');

const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();

await page.setRequestInterception(true);

page.on('request', request => {
  const url = request.url();
  if (url.includes('api') || url.includes('graphql')) {
    console.log('API FOUND:', url);
    console.log('Method:', request.method());
    console.log('POST:', request.postData());
  }
  request.continue();
});

await page.goto('https://www.farfetch.com/shopping/women/bags-1/items.aspx');
```

### Possible API Patterns

- `/api/products`
- `/graphql` (with POST query)
- `/api/search?query=chanel`
- `/api/catalog/products`

### Approach 2: Commercial APIs

**Recommended Options:**

1. **Apify Farfetch Scraper**
   - URL: https://apify.com/autofacts/farfetch
   - Pricing: Pay per use
   - Handles all anti-bot measures

2. **Retailed.io**
   - URL: https://www.retailed.io/datasources/api/farfetch-product
   - 50 free requests
   - JSON/CSV output

3. **Bright Data Scraping Browser**
   - Full browser automation with Akamai bypass
   - $100-500/month

### Why DIY is Not Worth It

- Akamai Bot Manager is military-grade
- Requires advanced fingerprinting evasion
- Even with perfect setup: <20% success rate
- Time spent debugging > API cost

---

## Summary Table: Best Extraction Method by Site

| Site | Best Method | Tools Needed | Difficulty | Success Rate |
|------|-------------|--------------|-----------|--------------|
| **Fashionphile** | Parse `__NEXT_DATA__` JSON | cheerio + puppeteer-real-browser | Easy | 85-90% |
| **Rebag** | `/products.json` API | axios only | Easiest | 95%+ |
| **The RealReal** | HTML scraping with bot bypass | puppeteer-real-browser + proxies | Hard | 60-70% |
| **Vestiaire** | Parse `__NEXT_DATA__` or sitemap | httpx (Python) + proxies | Hard | 70-80% |
| **Farfetch** | Commercial API | Apify/Retailed.io | N/A | 90%+ |

---

## Installation Requirements

### For Rebag + Fashionphile

```bash
npm install axios cheerio puppeteer-real-browser
```

### For The RealReal

```bash
npm install puppeteer-real-browser
sudo apt-get install xvfb  # Linux only
```

### For Vestiaire (Python recommended)

```bash
pip install httpx parsel
```

### Proxy Services (Residential IPs Required)

- Bright Data: https://brightdata.com
- Smartproxy: https://smartproxy.com
- Oxylabs: https://oxylabs.io

Cost: $50-200/month for residential proxies

---

## Testing Your Selectors

Use this script to test if selectors work:

```javascript
const { connect } = require('puppeteer-real-browser');

async function testSelectors(url, selectors) {
  const { page, browser } = await connect({
    headless: 'auto',
    fingerprint: true,
    turnstile: true
  });

  await page.goto(url, { waitUntil: 'networkidle2' });
  await sleep(5000);

  for (const [name, selector] of Object.entries(selectors)) {
    try {
      const count = await page.$$eval(selector, els => els.length);
      const sample = await page.$eval(selector, el => el.textContent?.trim());

      console.log(`✓ ${name}: ${selector}`);
      console.log(`  Count: ${count}`);
      console.log(`  Sample: ${sample}`);
    } catch (e) {
      console.log(`✗ ${name}: ${selector} NOT FOUND`);
    }
  }

  await browser.close();
}

// Test it
testSelectors('https://www.fashionphile.com/shop/bags?designers=CHANEL', {
  'JSON Data': 'script#__NEXT_DATA__',
  'Container': 'article[data-product-id]',
  'Title': 'h2.product-name',
  'Price': 'span[data-testid="product-price"]'
});
```

---

## Common Mistakes to Avoid

1. **Using CSS selectors on JavaScript-rendered content**
   - Solution: Always check for `__NEXT_DATA__` or JSON APIs first

2. **Not waiting for JavaScript to render**
   - Solution: Use `waitUntil: 'networkidle2'` and add 3-5 second delays

3. **Using datacenter proxy IPs**
   - Solution: Only residential proxies work on luxury sites

4. **Ignoring Cloudflare challenges**
   - Solution: Use puppeteer-real-browser with `turnstile: true`

5. **Scraping too fast**
   - Solution: 5-15 second delays between requests

6. **Not handling lazy-loaded images**
   - Solution: Scroll page before extracting data

7. **Using standard Puppeteer**
   - Solution: Use puppeteer-real-browser or puppeteer-extra-stealth

---

## Need Help?

1. Start with **Rebag** (easiest, no anti-bot issues)
2. Then **Fashionphile** (easy JSON extraction)
3. Skip or use commercial API for **Farfetch**
4. Only tackle **The RealReal** if you have time to set up proxies
5. **Vestiaire** is best scraped via sitemap