# Sephora Scraper: Prices, Sales & Ratings (`reventadata/sephora-scraper`) Actor

Scrape Sephora US category pages. One row per product: price, list price range, sale price and sale type, rating, review count, number of shades or sizes, New and Bestseller flags. Sort, filter by brand, price, sale and rating. Export JSON, CSV or Excel.

- **URL**: https://apify.com/reventadata/sephora-scraper.md
- **Developed by:** [reventadata](https://apify.com/reventadata) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Sephora Scraper: Prices, Sales, Ratings and Shades by Category

> **Unofficial.** This Actor is not affiliated with, authorised by, endorsed by, or in any way
> officially connected to Sephora. "Sephora" is a trademark of its respective owner and is
> used here only to describe what this tool reads. It reads the same public category pages a browser does.

Give it Sephora US category pages and get one clean row per product: price in US dollars, sale price and
sale type, list-price range, set value, rating, review count, number of sizes or shades, and Sephora's own New,
Bestseller, Limited Edition, Online Only and Exclusive flags.

### What does it do?

For each start URL it walks the result pages of that Sephora US category (60 products per page), reads the product
data each page embeds, and saves one row per product.

It reads **category pages** (`https://www.sephora.com/shop/<category>`, for example `moisturizing-cream-oils-mists`,
`lipstick`, `mascara`, `foundation-makeup`, `skincare`). It does not read search result pages, `/browse/` pages or
product pages: Sephora's robots.txt disallows search and browse, and this Actor stays on the page type it leaves open.
Only the US storefront (`www.sephora.com`) is supported.

### Why use it?

- **Prices as shoppers see them.** `price` is the lowest current price of the product in USD; `salePrice` is set when Sephora marks the shown size or shade down.
- **List price range kept whole.** Many products list a range ("$20.00 - $32.00"): `priceMin`, `priceMax` and the original text `listPriceText` are all in the row.
- **Sale flags that tell the cases apart.** `onSale` and `saleType`: `FULL` when every size or shade is marked down, `PARTIAL` when only some are.
- **Filters the site applies.** Brands, price band and sort order (best selling, top rated, price low to high or high to low, new) are applied by Sephora before the Actor reads the page, so a small `maxItems` returns the top of the right list.
- **Filters the Actor applies.** Only products on sale, a minimum rating, words the name, brand or category must or must not contain.
- **Polite by design.** The Actor waits 5 seconds between page requests, the `Crawl-delay` in Sephora's robots.txt.

### How to use it

1. Put one or more Sephora category URLs in `startUrls`.
2. Set `maxItems` (rows to save) and, if you want, a sort order, brands or filters.
3. Run the Actor and download the dataset as JSON, CSV or Excel.

#### Input

| Field | Type | Description |
|---|---|---|
| `startUrls` | array | Category pages `https://www.sephora.com/shop/<category>`. Query strings are ignored: the Actor sets page, sort and filters itself. |
| `maxItems` | integer | Total products to save across all start URLs, counted after the filters. Default 100. |
| `maxPages` | integer | Result pages per start URL, 60 products each. Default 5. 17 pages (about 1,000 products) take about 3 minutes: the 5-second delay plus the fetch (measured 2026-10-21: 1,000 products from 19 pages in 200 seconds). |
| `sort` | string | `RELEVANCE` (the site's default), `BEST_SELLING`, `TOP_RATED`, `PRICE_LOW_TO_HIGH`, `PRICE_HIGH_TO_LOW` or `NEW`. |
| `brands` | array | Brand names as Sephora shows them (for example `Tatcha`). Several brands are OR. |
| `minPrice`, `maxPrice` | number | Price band in USD, applied by the site to the whole list-price range: a product listed at $29.00 - $50.00 is dropped when `maxPrice` is 40. |
| `onlyOnSale` | boolean | Keep only products Sephora flags as on sale (FULL or PARTIAL). |
| `minRating` | number | Minimum average rating, 0 to 5. Unrated products are dropped when set. |
| `includeKeywords`, `excludeKeywords` | array | Words the name, brand or category must / must not contain, ignoring case and accents. |
| `proxyConfiguration` | object | Apify residential proxy in the United States (the default, and the transport measured to work). No proxy and datacenter proxies get an "Access Denied" page. |

#### Sample input

```json
{
    "startUrls": [{"url": "https://www.sephora.com/shop/moisturizing-cream-oils-mists"}],
    "maxItems": 20,
    "maxPages": 1,
    "sort": "RELEVANCE"
}
```

#### Output

One row per product:

| Field | Type | Description |
|---|---|---|
| `id` | string | Sephora product id (P followed by digits). |
| `skuId` | string / null | Id of the size or shade the tile shows. |
| `title`, `brand` | string / null | Product name and brand. |
| `url` | string | Product page URL with the shown skuId. |
| `imageUrl` | string / null | Main image of the shown size or shade. |
| `price` | number / null | Lowest current price in USD: the sale price when on sale, else the low end of the list price. |
| `priceMin`, `priceMax` | number / null | Low and high end of the list price. |
| `listPriceText` | string / null | The list price as Sephora prints it. |
| `salePrice` | number / null | Sale price of the shown size or shade. |
| `valuePrice` | number / null | Value Sephora states for the item, shown on sets and on some jumbo, mini and travel sizes. |
| `onSale`, `saleType` | boolean, string / null | Sale flag; `FULL`, `PARTIAL` or null. |
| `currency` | string | Always `USD`. |
| `rating`, `reviewCount` | number / null, integer / null | Average rating and review count. Unrated products have `rating` null. |
| `variantCount`, `variationType` | integer / null, string / null | Number of sizes or shades and what they differ by. |
| `isNew`, `isBestseller`, `isLimitedEdition`, `isOnlineOnly`, `isSephoraExclusive` | boolean | Sephora's flags. |
| `badge` | string / null | Award or programme badge key, for example `clean-at-sephora`. |
| `category`, `categoryUrl`, `categoryTotal` | string / null, string / null, integer / null | The category page read and the product count it holds under the sort and filters used. |
| `page`, `position`, `sourceUrl`, `scrapedAt` | integer, integer, string, string | Where the row came from and when. |

#### Sample output

One row from the 2026-10-21 test run. The start URL had moved, so `sourceUrl` is the old address and `categoryUrl` the category it led to:

```json
{
    "id": "P519160",
    "skuId": "2898419",
    "title": "Glazing Milk Hydrating Ceramide Facial Essence",
    "brand": "rhode",
    "url": "https://www.sephora.com/product/glazing-milk-P519160?skuId=2898419",
    "imageUrl": "https://www.sephora.com/productimages/sku/s2898419-main-zoom.jpg?imwidth=270&pb=clean-at-sephora",
    "price": 20,
    "priceMin": 20,
    "priceMax": 32,
    "listPriceText": "$20.00 - $32.00",
    "salePrice": null,
    "valuePrice": null,
    "onSale": false,
    "saleType": null,
    "currency": "USD",
    "rating": 3.94,
    "reviewCount": 2921,
    "variantCount": 1,
    "variationType": "Size",
    "isNew": false,
    "isBestseller": true,
    "isLimitedEdition": false,
    "isOnlineOnly": false,
    "isSephoraExclusive": true,
    "badge": "clean-at-sephora",
    "category": "Moisturizers",
    "categoryUrl": "https://www.sephora.com/shop/moisturizing-cream-oils-mists",
    "categoryTotal": 727,
    "page": 1,
    "position": 1,
    "sourceUrl": "https://www.sephora.com/shop/moisturizer-skincare",
    "scrapedAt": "2026-10-21T18:08:30.068019+00:00"
}
```

#### What a row does not hold

A category page carries the product tile only. There is no description, ingredient list, size, full image gallery or
per-shade price table: those live on product pages, which this Actor does not read.

### Good to know

- A product that belongs to several categories appears once per run (rows are de-duplicated by `id` across start URLs).
- The Actor stops at the first empty page, or when the page count reaches the category total.
- A start URL that Sephora has moved is followed (up to two hops) when it points to another `/shop/` category; a category that does not exist is reported and skipped.
- Rows are read from the page as Sephora serves it to a US visitor. Prices and sales change during the day.

### How much does it cost to scrape Sephora?

Pay-per-event: **$0.004 per result** saved to the dataset (**$4.00 per 1,000 results**), plus
**$0.00005** charged once when the run starts. No subscription. The sale, rating and keyword
filters run before a row is saved, so products they drop are not charged.

### Privacy & Compliance

This Actor reads only publicly accessible category pages. Rows carry public product data: names, brands, prices and ratings.
No account, login or personal data is involved. You are responsible for using the data in line with Sephora's terms and the law that applies to you.

# Actor input Schema

## `startUrls` (type: `array`):

Sephora US category pages, for example https://www.sephora.com/shop/moisturizing-cream-oils-mists or https://www.sephora.com/shop/lipstick. Only www.sephora.com/shop/<category> URLs are read: search pages, /browse/ pages and product pages are refused. Query strings are ignored: the Actor sets page, sort and filters itself.

## `maxItems` (type: `integer`):

Total number of products to save across all start URLs, counted after the filters. A category page holds 60 products.

## `maxPages` (type: `integer`):

Result pages to walk per start URL, 60 products each. The Actor waits 5 seconds between page requests (the site's robots.txt Crawl-delay), so 17 pages, about 1,000 products, take about 3 minutes (measured 2026-10-21: 1,000 products from 19 pages in 200 seconds).

## `sort` (type: `string`):

Order of the products, applied by the site before the Actor reads them. Price orders let a small maxItems return the cheapest or dearest products first.

## `brands` (type: `array`):

Keep only these brands, spelled as Sephora shows them (for example Tatcha, LANEIGE, Charlotte Tilbury). Several brands are OR: a product of any of them is kept. The site applies the filter. A brand with no products in the category returns no rows.

## `minPrice` (type: `number`):

Keep only products priced at or above this amount in US dollars. The site applies it to the whole list-price range: a product listed at $42.00 - $59.00 is dropped when this is 45. Leave empty for no minimum.

## `maxPrice` (type: `number`):

Keep only products priced at or below this amount in US dollars. The site applies it to the whole list-price range: a product listed at $29.00 - $50.00 is dropped when this is 40. Leave empty for no maximum.

## `onlyOnSale` (type: `boolean`):

Keep only products Sephora flags as on sale: every size or shade marked down, or only some of them (saleType FULL or PARTIAL in the row).

## `minRating` (type: `number`):

Keep only products with an average customer rating at or above this value (0 to 5). Unrated products are dropped when this is set.

## `includeKeywords` (type: `array`):

Keep only products whose name, brand or category contains every one of these words, ignoring case and accents.

## `excludeKeywords` (type: `array`):

Drop products whose name, brand or category contains any of these words, ignoring case and accents.

## `proxyConfiguration` (type: `object`):

Apify residential proxy in the United States is required: no proxy and datacenter proxies get an 'Access Denied' page from sephora.com.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.sephora.com/shop/moisturizing-cream-oils-mists"
    }
  ],
  "maxItems": 20,
  "maxPages": 1,
  "sort": "RELEVANCE",
  "onlyOnSale": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Sephora US products scraped from category pages.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        {
            "url": "https://www.sephora.com/shop/moisturizing-cream-oils-mists"
        }
    ],
    "maxItems": 20,
    "maxPages": 1,
    "sort": "RELEVANCE",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("reventadata/sephora-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": [{ "url": "https://www.sephora.com/shop/moisturizing-cream-oils-mists" }],
    "maxItems": 20,
    "maxPages": 1,
    "sort": "RELEVANCE",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("reventadata/sephora-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://www.sephora.com/shop/moisturizing-cream-oils-mists"
    }
  ],
  "maxItems": 20,
  "maxPages": 1,
  "sort": "RELEVANCE",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call reventadata/sephora-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,reventadata/sephora-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/mG6kUjdbJDcDUiAS7/builds/p2MCcgDBqOGXUWQkR/openapi.json
