# SHEIN Scraper - Products, Prices, Discounts & Ratings (`abotapi/shein-product-scraper`) Actor

Scrape SHEIN product listings by keyword or from any category, sale or search link. Returns product ID, SKU, title, link, category, current price, genuine was-price and discount, star rating, review count, stock, flash-sale and clearance flags, and images. Nine regional storefronts.

- **URL**: https://apify.com/abotapi/shein-product-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 10 total users, 3 monthly users, 42.2% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 product results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## SHEIN Scraper: Products, Prices, Discounts and Ratings

SHEIN Scraper turns SHEIN's own curated storefront listings into clean, structured product data. Paste a SHEIN RecommendSelection listing link and get back product names, links, categories, current price, a was-price and discount that are only shown when the item is genuinely marked down, star rating, review count, stock and promotion flags, for the Australian, Singapore and Canadian storefronts. Export to JSON, CSV or Excel, pull it through the API, or pipe results into your own apps.

### Why This Scraper?

- **Genuine was-price and discount.** `originalPrice`, `discountPercent` and `discountAmount` are only filled in when a product is actually marked down, never invented from the current price alone.
- **Three ways in, with SHEIN's own sorting.** Paste RecommendSelection, search or category links (from any SHEIN address, desktop or mobile) or just type keywords; sort order and price range are applied by SHEIN itself, and rating, discount and category filters trim what comes back.
- **Three storefronts, one shape.** Australia, Singapore and Canada each keep their own catalogue and currency, and every record uses the same fields regardless of storefront. Other storefronts (US, UK and the rest) were re-tried on 2026-10-12 with the same browser session, direct and through a residential exit, and stayed on SHEIN's verification page on 4 of 4 attempts (the Australian control link was served in the same test), so they are not offered; each storefront's currency is fixed by the storefront, so there is no separate country or currency input.
- **Ratings, reviews and stock, straight from the listing.** No separate step is needed to see how a product is rated or whether it is in stock.
- **Resume without repaying.** Point `resumeFromRunId` at an interrupted run and the next run skips everything it already returned.
- **Optional delivery to your own apps.** Pipe results into Notion, Linear, Airtable or another Apify-connected app as an extra step; the dataset itself is never changed by it.

### Use Cases

- **Price trackers and deal sites:** monitor genuine markdowns across the Australian, Singapore and Canadian storefronts.
- **Competitor and marketplace research:** compare SHEIN's own pricing, ratings and stock against other retailers you track.
- **Assortment and trend analysis:** watch which products rotate through SHEIN's curated selections over time.
- **Review monitoring:** every record carries the listing's current star rating and review count, so comparing two runs' datasets shows how they moved. (If you also turn on **Incremental mode**, note that a rating/review-count change by itself never produces an `UPDATED` row there, see "Incremental mode" below, so for pure review-trend tracking, run without incremental suppression, or with `emitUnchanged` on.)

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `productId` / `sku` / `spu` | `"48213097"` / `"sw2412089988771"` / `"mp2412089988771"` |
| `title` / `url` | `"Fictional Wrapstone Print Midi Dress"` / `"https://au.shein.com/fictional-wrapstone-print-midi-dress-p-48213097-cat-1745822.html"` |
| `category` / `categoryId` / `parentCategoryIds` | `"Women Dresses"` / `"1745822"` / `["4436", "2030", "1766"]` |
| `price` / `currency` / `currencyCode` / `country` | `15.0` / `"AU$"` / `"AUD"` / `"AU"` |
| `priceFormatted` / `originalPriceFormatted` | `"AU$15.00"` / `"AU$20.00"` (the was-price string follows the `originalPrice` rule) |
| `originalPrice` | `20.0` (`null` unless the item is genuinely reduced) |
| `discountPercent` / `discountAmount` / `isOnSale` | `25` / `5.0` / `true` (`null` unless `originalPrice` is also set) |
| `priceUsd` / `estimatedPrice` | `9.9` / `13.5` (SHEIN's coupon-estimated price, only when the card shows one) |
| `rating` / `reviewsCount` / `reviewsCountText` | `4.6` / `812` / `"100+"` |
| `soldCountText` / `fit` | `"1.1k+ sold"` / `{"trueSize": "91%", "large": "3%", "small": "6%"}` (shopper-reported fit) |
| `inStock` / `stock` | `true` / `143` |
| `isFlashSale` / `isClearance` / `quickShip` / `isCustomizable` | `false` / `false` / `false` / `false` |
| `hasVideo` / `videoUrl` | `true` / `null` (SHEIN's listing feeds only flag videos, so the link is `null` unless a feed row carries one) |
| `image` / `images` | `"https://img.ltwebstatic.com/images3_pi/2026/09/29/fictional-9988776-1.jpg"` / every listing picture as a list |
| `imageFullSize` / `imagesFullSize` | the same pictures at full size (about 1340×1785 instead of the 405×552 thumbnail) |
| `colorImage` / `colorVariants` | `"https://img.ltwebstatic.com/...square.png"` / `[{"productId": "48213098", "image": "https://img.ltwebstatic.com/...square.png", "sku": null}]` |
| `brand` / `storeName` | `"EMERY ROSE"` / `"Fictional Trends Store"` (the names behind `brandCode` and `storeCode`; `storeName` only when the card shows the store's label) |
| `storeCode` / `mallCode` / `brandCode` / `businessModel` / `priorityMallType` | `"12345"` / `"1"` / `"10001"` / `"0"` / `"1"` (SHEIN's own codes) |
| `gradeEnums` | `["clearance"]` |
| `rankLabel` / `badges` | `"#1 Bestseller in Women Midi Dresses"` / `[{"text": "Only 9 left", "type": "attribute"}]` (every card label, with its kind) |
| `promotions` / `dealEndsAt` | `[{"id": "77346380", "typeId": "31", "endsAt": "2026-10-31T12:59:59+00:00", "isReturn": true, "isFullShop": false, "note": "Valid before 31 Oct 23:59"}]` / `"2026-10-08T13:59:59+00:00"` |
| `scrapedAt` / `sourceUrl` / `searchQuery` | `"2026-09-29T02:15:40+00:00"` / `"https://au.shein.com/pdsearch/summer%20dress/"` / `"summer dress"` (the link or keyword that delivered the product; `searchQuery` only for keywords and search links) |
| `changeType` | `"NEW"` / `"UPDATED"` / `"UNCHANGED"` (incremental mode only; see "Incremental mode" below) |
| `changedFields` / `firstSeenAt` / `lastSeenAt` | `["price"]` / `"2026-09-01T00:00:00+00:00"` / `"2026-09-29T02:15:40+00:00"` (incremental mode only) |

There is a single record type: one row per product, up to about 120 products per link or keyword (SHEIN's first listing page). The last two rows above only appear once **Incremental mode** is turned on; see "Incremental mode" below for which of the five change-type values actually occur on this actor's run path.

### How to Use

1. Paste one or more SHEIN listing links (RecommendSelection, search results or category pages, see "Supported URL shapes" below), or type keywords into **Search keywords**, and pick the storefront, or leave **Storefront** on its default and let it be detected from the first link's own host.
2. Optionally pick a **Sort by** order and a price range: these are passed to SHEIN itself, so the listing returns e.g. its cheapest products in your range. **Minimum rating**, **Discounted products only** and **Categories** (plain names like `dresses`, or a category number) then filter what came back.
3. Set **Maximum products** if you need fewer than everything the links return.
4. Set **Resume from run ID** to continue an interrupted run without repaying for products it already returned.
5. Click **Start**, then download the dataset as JSON, CSV or Excel, or read it through the API.

**Scrape a listing on the Australian storefront:**

```json
{
  "urls": ["https://au.shein.com/RecommendSelection/Women-Clothing-sc-017172961.html"],
  "storefront": "au",
  "maxItems": 100
}
```

**Singapore storefront, in-stock products only:**

```json
{
  "urls": ["https://sg.shein.com/RecommendSelection/Home-Kitchen-sc-017185546.html"],
  "storefront": "sg",
  "includeOutOfStock": false,
  "maxItems": 50
}
```

**Search by keyword, dresses only, cheapest first:**

```json
{
  "searchTerms": ["summer dress"],
  "storefront": "au",
  "categories": ["dresses"],
  "sortBy": "price_asc",
  "maxItems": 0
}
```

**Continue an interrupted run:**

```json
{
  "urls": ["https://au.shein.com/RecommendSelection/Home-Kitchen-sc-017185546.html"],
  "storefront": "au",
  "resumeFromRunId": "<PASTE_THE_INTERRUPTED_RUN_ID>"
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/shein-product-scraper").call(run_input={
    "urls": ["https://au.shein.com/RecommendSelection/Women-Clothing-sc-017172961.html"],
    "storefront": "au",
})
for product in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(product["title"], product["price"], product["currency"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/shein-product-scraper').call({
  urls: ['https://au.shein.com/RecommendSelection/Women-Clothing-sc-017172961.html'],
  storefront: 'au',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Multiple listing links per run

Paste several links in **Listing URLs** and every usable one is scraped in the same run, in one browser session, with results deduplicated by product ID across all of them, so the same product showing up on two of your links is only ever returned once. Each product's `sourceUrl` names the link (or keyword search page) that delivered it first, and `searchQuery` the keyword. An invalid link (see the shape table below) is skipped with a warning naming it and what it looked like, rather than failing the whole run; only a run where **every** pasted link is unsupported fails.

#### Default links (what an empty run does)

If you start the actor without **Listing URLs** and without **Search keywords**, it scrapes three example links, one per supported shape, instead of failing: a RecommendSelection listing (`https://au.shein.com/RecommendSelection/Women-Clothing-sc-017172961.html`), a search page (`https://au.shein.com/pdsearch/summer%20dress/`) and a category page (`https://au.shein.com/Women-Dresses-c-1727.html`). With the default **Maximum products** (20) the first link that is served fills the run, so you are billed for at most 20 products. A run that sets **Search keywords** and leaves **Listing URLs** alone scrapes only the keywords, never the examples. The Console form shows the three examples plus a second RecommendSelection example (`https://au.shein.com/RecommendSelection/Home-Kitchen-sc-017185546.html`); delete the ones you do not want.

#### Supported URL shapes

| Shape | Example | Status |
| --- | --- | --- |
| RecommendSelection listing | `https://au.shein.com/RecommendSelection/Home-Kitchen-sc-017185546.html`, `https://au.shein.com/RecommendSelection/Women-Clothing-sc-017172961.html` | **Supported.** SHEIN's own curated listings, also its deepest readable data source. Both examples re-measured 2026-10-12: 10 of 10 products each, unique IDs. |
| Any storefront host of the above | `https://sg.shein.com/...`, `https://ca.shein.com/...` | **Supported and verified.** The storefront is inferred from the host when **Storefront** is left on its default. |
| Mobile / `www.shein.com` host of any shape above | `https://m.shein.com/au/RecommendSelection/...`, `https://www.shein.com/RecommendSelection/...` | **Supported.** SHEIN picks a storefront for these addresses from the visitor's location (measured 2026-10-06: from an Australian connection, `www.shein.com/<path>` redirects to `au.shein.com/<path>` and `m.shein.com/<path>` to `m.shein.com/au/<path>`), so the actor opens the same path on the selected storefront's own site instead, and says so in the log. The mobile site's `/au/`, `/sg/` or `/ca/` segment picks the storefront when **Storefront** is left on its default. |
| Any link with tracking params | `...sc-017185546.html?ici=page_RecommendSelection&src_module=1` | **Supported.** The tracking query string rides along; the page is identical. A link copied out of a web page or email with `&amp;` in it is decoded first. |
| Category page | `https://au.shein.com/Women-Dresses-c-1727.html` | **Supported** since 2026-10-06 (measured: 120-140 products per page; re-measured 2026-10-12: served after the first connection was refused). Only the category's own product feed is read; the "recommended for you" and ranking strips on the same page are left out. |
| Search page | `https://au.shein.com/pdsearch/dress/` | **Supported** since 2026-10-06 (measured: 120 products per page; re-measured 2026-10-12: 120 products when it was a later page, but refused three times in a row when it was the only link, so for keywords-only runs retry if the run reports a connection problem). Typing the words into **Search keywords** opens the same page. |
| Product-detail page | `https://au.shein.com/some-product-p-12345678-cat-1727.html` | **Not supported.** Measured 2026-10-06: SHEIN answered 6 of 7 product-page visits with its verification page, so product pages (and the reviews, materials and seller details that only live there) are not read; see "Reviews" below. Skipped with a warning. |
| Daily-new / flash-sale / sale page | `https://au.shein.com/daily-new.html` | **Not supported** (not yet verified). Skipped with a warning. |
| Store page | `https://au.shein.com/store/home?store_code=...` | **Not supported** (not yet verified). Skipped with a warning. |
| Brand page | `https://au.shein.com/brand-nike.html` | **Not supported** (not yet verified). Skipped with a warning. |
| Share / short link | e.g. an `a.shein.com` redirect link | **Not supported** (not yet verified). Skipped with a warning. |

Every shape goes through the same SHEIN risk check. Measured 2026-10-06, the first page a fresh session opens is the one most often answered with SHEIN's verification page, and search pages were refused every time they were the first page. So when a run does not start on a RecommendSelection link, the actor first opens one in the background (its products are not returned and not billed), and a refused link is retried once at the end of the walk. The run log reports `refused=`, `recovered-on-retry=` and per-link product counts. Category pages were the least reliable shape in those measurements (served 3 times out of 6), so for a large or scheduled pull prefer RecommendSelection links and keywords. A run that returns nothing at all fails with a clear message instead of an empty dataset.

If you need one of the "not yet verified" shapes supported, open a ticket; see "Support & custom scrapers" below.

#### Reviews

This actor does not scrape individual review text or Q\&A, only the summary `rating`, `reviewsCount`, `reviewsCountText` and the shopper-reported `fit` that every listing record already carries (see "Data You Get" above). Full reviews, the material and attribute table, the size chart and the seller profile live only on each product's own page, which this actor does not open. Measured 2026-10-06 in the actor's own browser session: after a listing was read, 1 of 7 product-page visits loaded and the other 6 stayed on SHEIN's verification page, and the next fresh session was refused its listing page as well. Opening product pages would therefore put the listing data itself at risk, so product-page data is not offered until it can be read reliably. Re-measured 2026-10-12 through the same browser session: a product page stayed on the verification page on 2 of 2 attempts (direct and through a residential exit) while the control listing link was served on both, so product-page links, product ID lists and a full-detail mode stay out of scope.

#### Resume

**Resume** (`resumeFromRunId`) continues one interrupted run: paste its run ID and the actor skips everything already returned there, so you don't pay twice.

#### Incremental mode (`incrementalMode`)

Turn this on to monitor the same listing link on a schedule and get back only what's new or changed, instead of re-billing the whole listing every run. The saved baseline belongs to the exact set of links and keywords plus any filter or SHEIN-side sort you set, so changing one of those starts a separate baseline; changing only **Maximum products** does not. A run where nothing changed since the last one finishes successfully with no rows and says so in its status message. Each run compares against a saved baseline and classifies every product as `NEW`, `UPDATED` (with `changedFields` naming what moved), `UNCHANGED`, `REAPPEARED` or `EXPIRED`, and saves the result back for next time. **Compared:** the price, original price (and the discount figures derived from them), the in-stock flag, the title, the product link, the category and the other catalogue details such as `brandCode`, `storeCode`, `currencyCode`, `quickShip` and `videoUrl`. **Never compared** (still returned on every row): rating, review count, sold count, the fit split, the stock count, the USD price, pictures, the flash-sale flag, card labels, promotions and deal timing, the coupon-estimated price, the formatted price strings, and the `brand` and `storeName` display names. The `incrementalMode` input description says the same.

A few fields never trigger a false `UPDATED` by themselves, because they drift on their own without the product actually changing: the stock count (SHEIN's own counter, capped and bouncing with every sale/restock elsewhere in the catalogue), `priceUsd` (a live exchange-rate conversion of the local `price`, which stays tracked), `image`/`images` (SHEIN rotates CDN hosts and size variants for the same picture) and `isFlashSale` (a promotional window that opens and closes many times a day). The same goes for the card labels and promotion windows, the coupon-estimated price, the formatted price strings, the full-size and colour pictures, and the `brand` and `storeName` display names (their `brandCode` and `storeCode` stay tracked); the full list is under `ignoreFieldsForChanges` in the parameter table below. They are still returned on every record, only excluded from change detection. Add more field names with **Ignore fields when detecting changes** (`ignoreFieldsForChanges`) if another field in your setup turns out to be noisy; it only adds to this built-in list, never removes from it.

`rating` and `reviewsCount` are handled the same way but are **never** configurable: they come from other shoppers' activity, not the product's own catalog data, and move on their own schedule, independent of (and usually faster than) any real price/stock/spec change. A product whose *only* change since the last run is a new rating or review count is classified `UNCHANGED` and, with the default `emitUnchanged: false`, is not returned (or billed) that run. There is no input that re-admits them to change detection. If you need to track rating/review movement run-to-run, read it from every run's full dataset (incremental mode off, or `emitUnchanged: true`), not from the `UPDATED` rows.

`REAPPEARED` and `EXPIRED` need a run to have read every tracked listing all the way to its own true natural end, with nothing cut short. The actor's default run path walks every pasted link, but it decides each link is "done" using a no-further-growth heuristic (a couple of scroll cycles that add nothing new) and a shared wall-clock budget across the whole walk; neither is a provable complete read, so this path never marks the baseline as a complete scan, and `EXPIRED`/`REAPPEARED` will not occur on it. `NEW` and `UPDATED` (suppressing `UNCHANGED`) are what you get in practice. A capped, interrupted or otherwise partial run is never treated as proof that a product is gone, on any path. Use `resumeFromRunId` instead of incremental mode if you only want to avoid paying twice for products within one run.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each product, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify, Settings, Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where products should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip. A problem with a connector never fails the run or changes the dataset.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `urls` | array | the three example links (one RecommendSelection listing, one search page, one category page) | SHEIN listing links (RecommendSelection, search results or category pages), one per line, read in order. Every usable link is scraped in the same run, deduplicated by product ID; `www.shein.com` and `m.shein.com` links are opened on the selected storefront; unsupported shapes (see "Supported URL shapes" above) are skipped with a warning naming each one. **Leaving this field out scrapes the three example links instead of failing.** The Console form pre-fills those three plus a second RecommendSelection example. |
| `searchTerms` | array | (none) | Plain keywords, one per line, each searched on the selected storefront like SHEIN's own search box. Use with or instead of `urls`; at least one of the two is needed. |
| `storefront` | string | `au` | Which storefront to read: `au` (AU$), `sg` (S$) or `ca` (CA$). Left on the default, it is inferred from the first pasted link's own host (or the mobile site's `/au/`, `/sg/` or `/ca/` segment) when that names one of the three supported storefronts; set it explicitly to override. The catalogue and currency come from the link itself either way; this value also decides the product-URL host and the locale used to read the site. |
| `includeOutOfStock` | boolean | `true` | Keep products that are sold out. Turn off to return only what is currently available. |
| `sortBy` | string | `relevance` | `relevance`, `most_popular`, `new_arrivals`, `top_rated`, `price_asc` and `price_desc` are SHEIN's own sort orders, sent with every link and keyword, so the listing returns a different set of products (e.g. its cheapest). `rating_desc`, `reviews_desc` and `discount_desc` have no SHEIN equivalent and only re-order the products returned. |
| `minPrice` | number | (none) | Lowest price, in the storefront's currency. Sent to SHEIN's own price filter and checked again on every product, so every published `price` is in range. |
| `maxPrice` | number | (none) | Highest price, in the storefront's currency. Sent to SHEIN's own price filter and checked again on every product. |
| `minRating` | number | (none) | 1 to 5. Applied after fetching: products below it, or not yet rated, are dropped, so a run can return fewer than `maxItems`. |
| `onSaleOnly` | boolean | `false` | Keep only products with a genuine was-price. Applied after fetching. |
| `categories` | array | (none) | Keep only products in these categories: a plain name or part of one (`dresses` keeps Women Short Dresses and Women Midi Dresses, case-insensitive) or a SHEIN category number (`1727` also keeps its sub-categories). Any line may match. Applied after fetching, so a run can return fewer than `maxItems`; to read one whole category, paste its category page link instead. |
| `maxItems` | integer | `20` | Upper bound on products requested, across all links and keywords combined. `0` means no limit: every link is read to its own natural end (up to about 120 products per link or keyword). |
| `maxPages` | integer | `0` | Limit on how many pages of the listing are read. Has no effect on the run path this actor currently uses. |
| `enableLegacyBrowserLane` | boolean | `false` | Retired option kept only so saved inputs keep working. It has no effect. |
| `resumeFromRunId` | string | (none) | Continue an interrupted run: paste its run ID and products it already returned are skipped. |
| `incrementalMode` | boolean | `false` | Recurring monitoring of the same listing link: remembers what was returned last time and classifies every product as `NEW`/`UPDATED`/`UNCHANGED`/`REAPPEARED`/`EXPIRED`, see "Incremental mode" above. |
| `stateKey` | string | (none) | Name the saved state used by incremental mode. Leave empty to derive one automatically from the listing link(s). |
| `emitUnchanged` | boolean | `false` | Also return (and bill) products whose details have not changed since the last run. |
| `emitExpired` | boolean | `false` | Also return (and bill) products that were seen before but disappeared from the listing. In practice this does not fire on the actor's default run path, see "Incremental mode" above. |
| `ignoreFieldsForChanges` | array | `[]` | Incremental mode only: extra field names to exclude from change detection, on top of the built-in handling for `stock`, `priceUsd`, all image fields, `isFlashSale`, the card labels (`badges`, `rankLabel`, `dealEndsAt`), `promotions`, `estimatedPrice` and the formatted price strings, `brand` and `storeName` (their codes stay tracked), plus the shopper-driven `rating`, `reviewsCount`, `reviewsCountText`, `soldCountText` and `fit` (see "Incremental mode" above). Use this if another field in your setup turns out to be noisy. |
| `proxy` | object | prefill and default: Apify Proxy, residential group | Connection setting. If left on the default (Apify Proxy, residential group, no country) the actor starts on Apify's datacenter pool, which cleared SHEIN's check in testing while Apify residential mostly did not, then tries Apify residential, then its own backup connections. Other Apify Proxy choices (a country pin or other proxy groups) are tried first exactly as entered, then the backup connections. Your own proxy URLs, or no proxy, are used as the only connection, with no backup; if that connection is refused the run fails. A residential-only selection without a country counts as the default. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each product to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which product pages are created. Ignored by other connectors. |
| `maxNotifyListings` | integer | `50` | Cap on products written to each connector per run. Does not affect the dataset. |

No single field is required. Give `urls`, `searchTerms` or both; a run with **neither** (the field omitted, for example `{}` from the API) scrapes the three example links in `urls` (one RecommendSelection listing, one search page, one category page) rather than failing, and the log says so. A call that sets only `searchTerms` does **not** get those examples: the example links are dropped again, so you only pay for the keywords you asked for. Clearing `urls` to an empty list with no keywords still fails with a clear message. Every other row above shows a real schema `default`, except where marked "(none)".

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "productId": "48213097",
  "sku": "sw2412089988771",
  "spu": "mp2412089988771",
  "title": "Fictional Wrapstone Print Midi Dress",
  "url": "https://au.shein.com/fictional-wrapstone-print-midi-dress-p-48213097-cat-1745822.html",
  "category": "Women Dresses",
  "categoryId": "1745822",
  "price": 15.0,
  "originalPrice": 20.0,
  "discountPercent": 25,
  "discountAmount": 5.0,
  "isOnSale": true,
  "currency": "AU$",
  "priceUsd": 9.9,
  "rating": 4.6,
  "reviewsCount": 812,
  "inStock": true,
  "stock": 143,
  "isFlashSale": false,
  "isClearance": false,
  "hasVideo": true,
  "storeCode": "12345",
  "mallCode": "1",
  "image": "https://img.ltwebstatic.com/images3_pi/2026/09/29/fictional-9988776-1.jpg",
  "images": [
    "https://img.ltwebstatic.com/images3_pi/2026/09/29/fictional-9988776-1.jpg",
    "https://img.ltwebstatic.com/images3_pi/2026/09/29/fictional-9988776-2.jpg"
  ],
  "scrapedAt": "2026-09-29T02:15:40+00:00",
  "brand": "EMERY ROSE",
  "storeName": null,
  "sourceUrl": "https://au.shein.com/RecommendSelection/Women-Clothing-sc-017172961.html",
  "searchQuery": null
}
```

#### Dataset views

The Console dataset has two views: **Products** (id, title, price, was-price, discount, rating, reviews, stock and link, one row per product) and **Changes** (the incremental-mode columns `changeType`, `changedFields`, `firstSeenAt` and `lastSeenAt` next to the product identity and price). Every run also stores a short run summary under the `OUTPUT` key of its key-value store.

### Plan Requirement

SHEIN's risk check refuses plain HTTP requests on every connection type: tests with ordinary and browser-impersonating HTTP clients through datacenter and residential proxies were all redirected to SHEIN's verification page and never returned a product page. The actor therefore reads pages in a real browser session, which is the only approach that cleared the check in testing. Clearing it also relies on an automatic verification step that runs inside the actor, and the result is not guaranteed on every attempt.

Residential proxies are not required, and a datacenter connection is not ruled out in the browser session: when **Connection** is left on its default (Apify Proxy, residential group, no country) the actor starts on Apify's datacenter pool, which cleared SHEIN's check in testing while Apify residential mostly did not, then tries Apify residential, then its own backup connections, one after another. Other Apify Proxy choices (a country pin or other proxy groups) are tried first exactly as entered, then the backup connections. Your own proxy URLs, or no proxy, are used as the only connection, with no backup; if that connection is refused the run fails. A residential-only selection without a country counts as the default. Apify free plans do not include residential proxies. When the selected connection is unavailable the log says:

> The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable. For residential access, upgrade at https://apify.com/pricing or supply your own proxy URLs.

and, once a page is served that way, "Served over the backup connection (the default connection was unavailable)." A run fails with one of three messages: the proxy connection was refused for this account (no page could be read; a paid plan is required, or check your own proxy URL credentials); no results because residential proxies are unavailable and the backup connection could not load the requested pages; or "Could not load any results page (the requests failed)... a connection problem, not an empty search". None of these bills any product. Because SHEIN's check is strict, a run can fail or return fewer pages on a bad attempt; retry in a few minutes.

### FAQ

#### How much does it cost?

You pay per product returned, plus a one-off start fee for the run. There is no separate detail-fetch charge: everything published comes from the listing itself, with no per-product detail step to pay for. Duplicate products within the same run are filtered out before they are billed. At the time of writing, on the Free plan the rate is $0.0022 per product returned plus a start fee of $0.08 per GB of memory (this actor runs with 2 GB, so $0.16 per run); paid plans get lower per-product rates. The **Pricing** tab always shows the current rates.

#### Is it legal to scrape SHEIN?

This actor collects only publicly available product data shown on SHEIN's own storefronts. You are responsible for how you use it: follow SHEIN's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Product names and prices are generally facts, but photos and video may be subject to third-party or seller rights.

#### Can I get only new or changed products on a schedule?

Yes. Turn on **Incremental mode** and schedule the run: the first run returns everything as `NEW`, later runs return only `NEW` and `UPDATED` products (with `changedFields`), and unchanged products are neither returned nor billed. A run where nothing changed succeeds with an empty dataset and a status message saying so. See "Incremental mode" above for which fields never count as a change.

#### Can I paste several listing links in one run?

Yes. Every usable link in **Listing URLs** is scraped in the same run (see "Multiple listing links per run" above), deduplicated by product ID, up to **Maximum products** in total. A link in an unsupported shape (product-detail, daily-new, store, etc.) is skipped with a warning naming it, and the rest still run.

#### Why did my run fail instead of returning an empty dataset?

A run fails, with the reason in its status message, in two cases. Either no listing page could be read at all (SHEIN's verification page refused every attempt), or SHEIN did return products but none of them could be returned to you, because none matched your filters (**Categories**, **Minimum rating**, **Discounted products only**, the price range, in-stock only) or every one was already returned by the run you are resuming. The message gives the counts, and no product is billed. An incremental run where nothing changed since the last run is not a failure: it succeeds with an empty dataset. Loosen the filters or paste other links and try again, or retry a refused run in a few minutes.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more e-commerce data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🔌 <a href="https://apify.com/abotapi/pbtech-co-nz-scraper"><b>PB Tech Scraper</b></a><br>Scrape pbtech.co.nz products with full specifications, price, brand, condition, per-store...</td><td>🛒 <a href="https://apify.com/abotapi/muji-scraper"><b>MUJI Scraper</b></a><br>Scrape MUJI products from the United States, Canada and Australia storefronts. Search by...</td></tr>
<tr><td>🛒 <a href="https://apify.com/abotapi/kogan-com-scraper"><b>Kogan.com Scraper</b></a><br>Scrape Kogan.com products with full details and customer reviews. Search by keyword or...</td><td>🛒 <a href="https://apify.com/abotapi/thegioididong-scraper"><b>Thegioididong Scraper</b></a><br>Scrape thegioididong.com products with full specifications, current &amp; original price...</td></tr>
<tr><td>🛒 <a href="https://apify.com/abotapi/pccomponentes-com-scraper"><b>PcComponentes Scraper</b></a><br>Scrape PcComponentes (pccomponentes.com) products: current price plus strike-through...</td><td>🛒 <a href="https://apify.com/abotapi/zalando-scraper"><b>Zalando Scraper</b></a><br>Scrape Zalando products: name, brand, current and original price, discount, sizes...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/shein-product-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `urls` (type: `array`):

SHEIN listing links. Three link shapes are read, one example of each is pre-filled (two RecommendSelection examples): RecommendSelection listings (e.g. https://au.shein.com/RecommendSelection/Home-Kitchen-sc-017185546.html), search results pages (e.g. https://au.shein.com/pdsearch/summer%20dress/) and category pages (e.g. https://au.shein.com/Women-Dresses-c-1727.html). Links are read in order and each returns up to about 120 products (SHEIN's first listing page), so with the default 'Maximum products' only the first link is needed. Leave this field untouched and the three example links (one RecommendSelection listing, one search page, one category page) are scraped; a run that sets only 'Search keywords' does not use them. Paste several links for larger pulls: every usable link is read in the same run and results are deduplicated across them. Links on www.shein.com or the m.shein.com mobile site are opened on the selected storefront's own site with the same path. Product, daily-new, sale, store, brand and share links are not listings; a run with a mix skips those with a warning naming each one and still reads the rest. The storefront (Australia/Singapore/Canada) is detected from the link's own host, or from the mobile site's /au/, /sg/ or /ca/ prefix, when 'Storefront' below is left on its default.

## `searchTerms` (type: `array`):

Optional. Plain keywords, one per line (e.g. summer dress). Each keyword is searched on the selected storefront exactly like SHEIN's own search box and returns up to about 120 products. Use with or instead of listing links. A SHEIN link typed here is read as a link.

## `storefront` (type: `string`):

Which regional storefront to read. This sets the catalogue, language and currency of the results, and where keywords are searched. Left on the default, it is auto-detected from the pasted link's own host (au./sg./ca.) when that host is one of the three supported storefronts; set it explicitly to override that detection.

## `includeOutOfStock` (type: `boolean`):

Keep products that are sold out. Turn off to return only products currently available.

## `sortBy` (type: `string`):

Order of the results. The first five are SHEIN's own sort orders: the listing is opened already sorted, so with a 'Maximum products' cap you get e.g. the cheapest or newest products of the whole listing, not just the default ones re-ordered. The last three have no SHEIN equivalent: they only re-order the products the listing returned. Applies to every link and keyword.

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

Lowest current price, in the storefront's currency (AU$, S$ or CA$). Sent to SHEIN's own price filter, so the listing returns products in range, and checked again on every product, so every published price is inside the range. Leave empty for no minimum. Applies to every link and keyword.

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

Highest current price, in the storefront's currency. Works like 'Minimum price'. Leave empty for no maximum.

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

Keep only products rated at least this many stars (1 to 5). SHEIN has no rating filter, so this is applied after fetching: products below it, or not rated yet, are removed from what the listing returned, and a run can return fewer than 'Maximum products'. Leave empty for no filter.

## `onSaleOnly` (type: `boolean`):

Keep only products with a genuine was-price above the current price. Applied after fetching, so a run can return fewer than 'Maximum products'.

## `categories` (type: `array`):

Optional. Keep only products in these categories, one per line. Type a plain category name or part of one (e.g. dresses keeps Women Short Dresses and Women Midi Dresses; matched case-insensitively against each product's category name), or a SHEIN category number (e.g. 1727, which also keeps every sub-category under it). A product matching any line is kept. Applied after fetching, so a run can return fewer than 'Maximum products'; to read one whole category, paste its category page link above instead.

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

Stop after this many products in total, across all listing URLs. Set 0 for no limit.

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

How many listing pages to read for each listing URL. Set 0 for no limit.

## `enableLegacyBrowserLane` (type: `boolean`):

Retired option kept so saved inputs keep validating. It has no effect.

## `proxy` (type: `object`):

Connection used to reach the site. The default works for most runs. When left on the default (Apify Proxy, residential group, no country) the actor starts on Apify's datacenter pool, which cleared the site's check in testing while Apify residential mostly did not, then tries Apify residential and its own backup connections. Other Apify Proxy choices are tried first as entered, then backup connections; your own proxy URLs or no proxy are used alone, with no backup.

## `resumeFromRunId` (type: `string`):

Continue an interrupted run: paste its run ID and products it already returned are skipped.

## `incrementalMode` (type: `boolean`):

For recurring runs of the same listing URLs: remembers what was seen last time and returns only new and changed products. Compared: price, original price, in-stock flag, title, link, category and the other catalogue details (a change is reported as UPDATED with the changed field names). Never compared, still returned on every row: rating, review count, sold count, fit split, stock count, USD price, pictures, flash-sale flag, card labels, promotions and deal timing, the coupon-estimated price, formatted price strings, and brand and store display names. Add more with the ignore-fields option below.

## `stateKey` (type: `string`):

Name the saved state used by incremental mode. Leave empty to derive one from the listing URLs.

## `emitUnchanged` (type: `boolean`):

In incremental mode, also return products whose details have not changed since the last run.

## `emitExpired` (type: `boolean`):

In incremental mode, also return products that were seen before but have now disappeared from the listing.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Field names to exclude from UPDATED change detection, in addition to the built-in handling. Built in, these never trigger an UPDATED by themselves: rating, review count and its rounded text, the sold-count text and the fit split (they move as shoppers buy and review); stock count, the USD-converted price, image links (image, images, full-size images, colour images and colour variants), the flash-sale flag, card labels (badges, rank label), deal end time, promotions, the coupon-estimated price, the formatted price strings and the brand and store names (whose codes stay tracked) (marketing timing, exchange rates and CDN hosting rather than a real catalogue change; the price itself is still tracked). Use this if another field in your setup turns out to be noisy.

## `mcpConnectors` (type: `array`):

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a page per product; other connectors get a best-effort write. Leave empty to skip. Never changes the dataset output.

## `notionParentPageUrl` (type: `string`):

URL or id of the Notion page under which product pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on products written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "urls": [
    "https://au.shein.com/RecommendSelection/Women-Clothing-sc-017172961.html",
    "https://au.shein.com/RecommendSelection/Home-Kitchen-sc-017185546.html",
    "https://au.shein.com/pdsearch/summer%20dress/",
    "https://au.shein.com/Women-Dresses-c-1727.html"
  ],
  "searchTerms": [],
  "storefront": "au",
  "includeOutOfStock": true,
  "sortBy": "relevance",
  "onSaleOnly": false,
  "categories": [],
  "maxItems": 20,
  "maxPages": 0,
  "enableLegacyBrowserLane": false,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `summary` (type: `string`):

No description

# 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 = {
    "urls": [
        "https://au.shein.com/RecommendSelection/Women-Clothing-sc-017172961.html",
        "https://au.shein.com/RecommendSelection/Home-Kitchen-sc-017185546.html",
        "https://au.shein.com/pdsearch/summer%20dress/",
        "https://au.shein.com/Women-Dresses-c-1727.html"
    ],
    "searchTerms": [],
    "categories": [],
    "maxItems": 20,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    },
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/shein-product-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 = {
    "urls": [
        "https://au.shein.com/RecommendSelection/Women-Clothing-sc-017172961.html",
        "https://au.shein.com/RecommendSelection/Home-Kitchen-sc-017185546.html",
        "https://au.shein.com/pdsearch/summer%20dress/",
        "https://au.shein.com/Women-Dresses-c-1727.html",
    ],
    "searchTerms": [],
    "categories": [],
    "maxItems": 20,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/shein-product-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 '{
  "urls": [
    "https://au.shein.com/RecommendSelection/Women-Clothing-sc-017172961.html",
    "https://au.shein.com/RecommendSelection/Home-Kitchen-sc-017185546.html",
    "https://au.shein.com/pdsearch/summer%20dress/",
    "https://au.shein.com/Women-Dresses-c-1727.html"
  ],
  "searchTerms": [],
  "categories": [],
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/shein-product-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/shein-product-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/H0iCIuDTcWMbCwgt1/builds/WqeyE2upkKVFI6P9F/openapi.json
