# AliExpress Deals & Flash Sales (`apt_marble/aliexpress-deals-scraper`) Actor

Every live AliExpress deal in one table, with the clock attached: deal price, was-price, discount, stock, rating and the exact moment each offer expires, stamped beside the moment it was collected. Filter by discount, price or time left. Pick your country. No account, no login.

- **URL**: https://apify.com/apt\_marble/aliexpress-deals-scraper.md
- **Developed by:** [Hamza](https://apify.com/apt_marble) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.70 / 1,000 deal scrapeds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## AliExpress Deals & Flash Sales

Every live AliExpress deal in one table — with the clock attached. For each
offer you get the deal price, the price it was, the discount, the stock still
showing, the star rating, how many people have bought it, and **the exact moment
the deal expires**, stamped next to the exact moment it was collected. That last
pair is the point: a discount row without its deadline is just a product row,
and a countdown without a collection time is a number you cannot trust an hour
later. Pick the country you shop from, press start, and the deals arrive ready
to sort. No account and no login are needed.

### What you can do with it

- **Run a deals feed or a deals newsletter.** Schedule it hourly, keep only the
  offers ending in the next twelve hours, and publish a list that is genuinely
  live rather than a week-old screenshot.
- **Catch the biggest markdowns first.** Order by discount, filter to 70 % and
  up, and see what the site is actually giving away today instead of scrolling
  five deal pages.
- **Watch a price war.** Save the same run daily and track how the deal price,
  the crossed-out price and the remaining stock move on the same product.
- **Find dropshipping candidates with a local warehouse.** Where the deal link
  states the dispatch country, the row carries it, so you can pick the ones that
  reach your buyers in days rather than weeks. It is stated on every deal-venue
  row and on roughly half of the front-page rows; the rest come back empty
  rather than guessed.
- **Build an expiry alert.** Keep only offers with a published deadline, feed
  the end time into your own reminder, and never miss a bundle that closes at
  midnight.
- **See what shoppers are looking for.** Each run also saves the search terms
  the site is promoting right now, as their own record next to the deals.
- **Feed a sheet, a store or a bot.** Every field is a plain column — export to
  Excel, Google Sheets, CSV or JSON in one click.

### What you get

One row per deal. A real, abridged example:

```json
{
  "source": "home_feed",
  "dealType": "superdeals",
  "dealSection": "SuperDeals",
  "positionInSection": 1,
  "productId": "3256806453629279",
  "title": "KINGROON PETG 3D Printer Filament 1.75mm 5KG 10KG Mix Color 3D Printing Material 1kg/roll petg Plastic For FDM 3dprinter",
  "url": "https://www.aliexpress.com/item/3256806453629279…",
  "currency": "USD",
  "price": 43.62,
  "originalPrice": 150.55,
  "savingPrice": 106.93,
  "discountPercent": 71,
  "endsAt": "2026-08-10T06:59:59.000Z",
  "secondsRemaining": 68399,
  "collectedAt": "2026-08-09T12:00:00.000Z",
  "hasPublishedExpiry": true,
  "hasEnded": false,
  "countdownText": "Limited time 70% off",
  "stock": 54,
  "rating": 4.8,
  "reviewCount": 572,
  "soldInDeal": 285,
  "soldLifetimeText": "4,000+",
  "soldLifetime": 4780,
  "cartAdds": 60,
  "promotionTags": ["prePlatformItemSubsidy", "dashDeal", "platformItemSubsidy"],
  "shipsFrom": "US"
}
```

### Input reference

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `country` | select | `us` | Which country's version of the site to read. This sets the currency **and** the catalogue — each country gets its own deals, prices and delivery options. |
| `feeds` | multi-select | both | Which front-page deal strips to collect. These are the sections that publish a real expiry, so leave both on if you want countdowns. |
| `channels` | multi-select | SuperDeals, Bundle Deals | Which dedicated deal venues to collect. They carry stock, purchase limits and saving amounts, but no expiry. |
| `maxDeals` | number | 200 | How many deals to keep after ordering and filtering. Maximum 2,000. |
| `sortBy` | select | Ending soonest | Ending soonest, biggest discount, lowest price, most sold, or the site's own order. |
| `onlyWithExpiry` | true/false | off | Keep only deals with a published end time. |
| `expiringWithinHours` | number | — | Keep only deals ending within this many hours of collection. Maximum 8,760. |
| `minDiscountPercent` | number | 0 | Keep only deals discounted at least this much. Maximum 99. |
| `minPrice`, `maxPrice` | number | — | Price band, in the currency of the country you selected. |
| `minRating` | number | — | Keep only deals rated at or above this. Never-rated products are excluded when set. |
| `onlyChoice` | true/false | off | Keep only deals the site marks as Choice. |
| `excludeSoldOut` | true/false | off | Drop deals whose remaining stock is published as zero. Unknown stock is always kept. |
| `includeTrendingKeywords` | true/false | on | Also save the trending search terms the site is promoting. |
| `channelDelaySeconds` | number | 2 | Gap between deal venues. Raise it if you select several. Maximum 60. |
| `channelRecoveryWaitSeconds` | number | 180 | If the deal venues stop serving mid-run, wait this long once and try again. Maximum 900. |

### Output fields

| Field | Type | Meaning |
| --- | --- | --- |
| `source` | text | `home_feed` for a front-page strip, `deal_channel` for a dedicated venue. |
| `sourceHost` | text | Which of the site's addresses served the row. |
| `dealType` | text | `superdeals`, `bundle_deals`, `choice`, `local_shipping`, `curated`, `category_venue`, or `other` for a section the site has newly introduced. |
| `dealSection` | text | The section's own heading, exactly as the site titled it that day. |
| `positionInSection` | number | Where the deal sat inside its section, counted top to bottom in the order the site laid it out. |
| `productId` | text | The product's own identifier. |
| `title` | text | Product title. |
| `url` | text | Direct link to the product, with tracking stripped out. |
| `imageUrl` | text | Deal picture. |
| `skuId` | text | The exact variant the deal price applies to, when published. |
| `categoryIds` | text | The site's own category identifiers for the product. |
| `currency` | text | The currency the row is priced in, read from the answer itself — never assumed. |
| `price`, `priceText` | number / text | The deal price as a number and as the shopper sees it. |
| `originalPrice`, `originalPriceText` | number / text | The crossed-out price. |
| `savingPrice`, `savingPriceText` | number / text | How much the deal takes off. |
| `discountPercent` | number | The discount the site prints on the card. |
| `endsAt` | text | When the deal expires, as a full date and time. Empty when the section publishes no deadline. |
| `endsAtEpochMs` | number | The same instant as a raw number, for arithmetic. |
| `secondsRemaining` | number | Seconds left **at the moment of collection**. Negative if the deal had already closed. |
| `collectedAt` | text | The moment this row was collected. Read it together with `secondsRemaining`. |
| `hasPublishedExpiry` | true/false | Whether the site published a deadline for this deal at all. |
| `hasEnded` | true/false | Whether the deadline had already passed at collection. Empty when there is no deadline. |
| `countdownText` | text | The promotional line beside the timer, such as "Limited time 70% off". |
| `stock` | number | Units still showing as available. Empty when the section publishes no real figure — never filled with a placeholder. |
| `purchaseLimitPerBuyer` | number | Cap per shopper. Empty when there is no real cap. |
| `purchaseLimitPerBuyerPerDay` | number | Cap per shopper per day. |
| `rating` | number | Star rating. Empty means never rated — never zero. |
| `reviewCount` | number | How many reviews the product has, filled in only when the site published an exact figure rather than a rounded one. |
| `soldInDeal`, `soldInDealText` | number / text | Units sold during this promotion. The text is what the site displays; the number is filled in only when the site published an exact figure, never derived from a rounded one. |
| `soldLifetime`, `soldLifetimeText` | number / text | Units sold in total, exact and as displayed. The site rounds the displayed figure heavily, so the two can differ by a lot. |
| `wishlists`, `cartAdds` | number | How many shoppers saved it or put it in a basket. |
| `isChoice` | true/false | Whether the deal carries the site's Choice badge. Empty means the section does not publish badges at all. |
| `hasFreeShippingBadge` | true/false | Whether a free-delivery badge is showing. Empty means the section does not publish delivery badges. |
| `promotionTags` | list | Which promotion the deal belongs to, in the site's own labels. |
| `shipsFrom` | text | The country the item ships from, when the deal link states it. This is the warehouse, not you. |
| `shoppingFromCountry` | text | The country the prices were shown for. |

Two extra records are saved beside the table: a run summary, and the trending
search terms the site is promoting.

### Pricing

You pay per deal returned — nothing else, and nothing for a section that comes
back empty.

| What you pay for | Price |
| --- | --- |
| Each deal row | $0.70 per 1,000 |

**Worked example.** A daily run collecting 200 deals costs **$0.14**. A full
2,000-deal run costs $1.40. A month of hourly runs at 200 deals each is about
$100 for roughly 144,000 rows.

### Limits & what this actor cannot do

- **Only the front-page deal strips publish an expiry.** The dedicated deal
  venues do not publish one at all, so their rows come back with an empty
  `endsAt` rather than a borrowed or estimated one. If countdowns are what you
  are here for, keep the front-page sections switched on.
- **Stock comes from the front-page strips and from Bundle Deals — not from
  every venue.** The SuperDeals and Choice venues do not publish a real
  remaining-units figure, so their rows leave `stock` empty rather than showing
  a placeholder that would read as "one left" on a product with a hundred
  thousand sales. Purchase limits come from Bundle Deals only.
- **Only the dedicated deal venues publish the Choice and free-delivery
  badges.** The front-page strips leave those empty. Selecting both source types
  gives you the widest coverage: the two are merged and de-duplicated for you,
  and where the same product appears on both, the gaps in one are filled from
  the other.
- **The countdown belongs to the deal section, not to the individual product.**
  When a strip publishes an end time, every deal in that strip shares it —
  that is the moment the whole sale closes, which is exactly what an expiry
  alert needs, but it is not a per-product deadline.
- Each deal section publishes a fixed snapshot of what is on it right now, so a
  run returns what the site is showing rather than an unlimited deal catalogue.
  Running on a schedule is how you build depth over time.
- Prices, stock, ratings and countdowns are a snapshot at the moment of
  collection and keep changing afterwards. That is why every row carries the
  collection time next to the remaining seconds.
- Prices and the deal selection differ by country. Two countries are never the
  same list translated — they are different catalogues with different products,
  so do not compare them row for row.
- **The country you choose is a preference, and the site can decline it.** Every
  row states the currency it was actually priced in, and the run summary lists
  every currency that came back. If more than one appears, the minimum and
  maximum price filters — which are a single pair of numbers — were applied
  across all of them, so set a price band only when the summary shows one
  currency.
- The displayed deal price can include a first-time-shopper allowance in some
  countries, which means a returning shopper may be quoted more. The price
  returned is the one the site displayed for the country you chose.
- The site publishes several different "sold" figures for the same product and
  they disagree with each other. Each one is returned under its own name and
  nothing is converted between them.
- Deals shown as sold out are returned and flagged rather than hidden, unless
  you ask for them to be dropped.
- Seller and store details are not published on any deal surface, so no seller
  column is returned. Nothing is invented to fill the gap.
- The deal venues are served slowly by the site and stop serving for a few
  minutes if they are asked for too quickly. A run reads them one at a time,
  and if they stop, the remaining ones are reported as not attempted rather
  than as empty.
- Speed depends on the size of the job and on the source site's own response
  times; no fixed throughput is promised.
- The source site's terms govern automated access. You are responsible for
  using the data lawfully and in line with those terms, and for handling any
  personal data in line with applicable privacy law.

### FAQ

**Do I need an account on AliExpress?**
No. Nothing is signed in to and nothing is bought.

**Does it need my login or password?**
No. There is no place to enter one, and none is ever used.

**Can I schedule it?**
Yes — and this one is built for it. Save your country and filters as a task and
run it hourly or daily. Because every row carries both its deadline and its
collection time, a series of runs lines up into a real history of how each deal
moved.

**Is the data complete?**
It is every deal the site is publishing on the sections you selected, at the
moment you ran it. It is not the whole of AliExpress: the site only puts a
limited set of offers on its deal sections at any one time. Select more sections
and run more often to see more.

**Why do some rows have no end time?**
Because the section they came from does not publish one. Those rows say so
plainly with `hasPublishedExpiry: false` instead of guessing a deadline. Switch
on "only deals with a countdown" if a deal without one is no use to you.

**Why is `rating` empty on some deals?**
Because the product has never been rated. An empty rating and a rating of zero
are different things, so the field is left empty rather than filled with a zero
that would drag your averages down.

**Why did I get different products when I changed country?**
Because the site runs a different deal catalogue for each country, with
different products, prices and delivery options. Changing country is not a
currency switch — it is a different shop.

**What is the difference between `soldInDeal` and `soldLifetime`?**
`soldInDeal` is units moved during the current promotion; `soldLifetime` is the
product's total. The site displays a heavily rounded version of the second one
next to the exact version of the first, which is easy to misread, so each is
returned under its own clear name.

# Actor input Schema

## `country` (type: `string`):

Which country's version of the site to read. This sets the currency AND the catalogue: each country gets its own selection of deals, its own prices and its own shipping options, so two countries are never the same list translated.

## `feeds` (type: `array`):

The deal strips the site puts on its own front page. These are the sections that publish a real expiry time, so leave both selected if you want countdowns.

## `channels` (type: `array`):

The site's dedicated deal venues. They carry the Choice and free-delivery badges and the saving amount that the front page does not, but they publish no expiry time, and only Bundle Deals publishes a real stock figure. Selecting many of them makes a run slower, because the site serves them one at a time.

## `maxDeals` (type: `integer`):

How many deals to keep after ordering and filtering. Each deal section publishes a fixed snapshot, so a run returns what the site is showing right now rather than an unlimited catalogue.

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

How to order the deals before the maximum is applied. Deals with no published countdown always sort last under Ending soonest.

## `onlyWithExpiry` (type: `boolean`):

Keep only the deals for which the site publishes an exact end time. Turn this on when you are building an expiry alert and a deal without a deadline is of no use to you.

## `expiringWithinHours` (type: `integer`):

Keep only deals that end within this many hours of collection. Deals with no published end time are excluded when this is set. Leave empty for no time window.

## `minDiscountPercent` (type: `integer`):

Keep only deals discounted by at least this much, using the discount figure the site itself prints on the card.

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

Keep only deals at or above this price, in the currency of the country you selected. The run summary lists every currency that actually came back — if more than one appears, this single figure was applied across all of them. Leave empty for no floor.

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

Keep only deals at or below this price, in the currency of the country you selected. Leave empty for no ceiling.

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

Keep only deals rated at or above this figure. Products that have never been rated are excluded when this is set, because no rating is not the same as a low one.

## `onlyChoice` (type: `boolean`):

Keep only deals the site marks as Choice. The front-page strips do not publish this marking at all, so switching this on returns deal-channel results only.

## `excludeSoldOut` (type: `boolean`):

Drop deals whose remaining stock is published as zero. Deals with no published stock figure are always kept, because unknown stock is not the same as none, and several deal venues publish no real figure at all.

## `includeTrendingKeywords` (type: `boolean`):

Save the list of trending search terms the site is promoting alongside the deals. They arrive as their own record, separate from the deal table.

## `channelDelaySeconds` (type: `integer`):

How long to wait between deal channels. The site serves these venues slowly and stops serving them for a while if they are asked for too quickly, so raise this if you are selecting several.

## `channelRecoveryWaitSeconds` (type: `integer`):

If the deal channels stop serving mid-run, wait this long once and try again. If they still will not serve, the remaining channels are reported as not attempted rather than as empty.

## Actor input object example

```json
{
  "country": "us",
  "feeds": [
    "home_pc",
    "home_msite"
  ],
  "channels": [
    "superdeals",
    "bundle_deals"
  ],
  "maxDeals": 100,
  "sortBy": "ending_soonest",
  "onlyWithExpiry": false,
  "minDiscountPercent": 0,
  "onlyChoice": false,
  "excludeSoldOut": false,
  "includeTrendingKeywords": true,
  "channelDelaySeconds": 2,
  "channelRecoveryWaitSeconds": 180
}
```

# Actor output Schema

## `deals` (type: `string`):

Every deal collected in this run, with its price, discount, stock and expiry.

## `runSummary` (type: `string`):

What each deal section returned, including any section that could not be read.

## `trendingSearches` (type: `string`):

The search terms the site is promoting at the time of the run.

# 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 = {
    "country": "us",
    "feeds": [
        "home_pc",
        "home_msite"
    ],
    "channels": [
        "superdeals",
        "bundle_deals"
    ],
    "maxDeals": 100,
    "sortBy": "ending_soonest"
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/aliexpress-deals-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 = {
    "country": "us",
    "feeds": [
        "home_pc",
        "home_msite",
    ],
    "channels": [
        "superdeals",
        "bundle_deals",
    ],
    "maxDeals": 100,
    "sortBy": "ending_soonest",
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/aliexpress-deals-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 '{
  "country": "us",
  "feeds": [
    "home_pc",
    "home_msite"
  ],
  "channels": [
    "superdeals",
    "bundle_deals"
  ],
  "maxDeals": 100,
  "sortBy": "ending_soonest"
}' |
apify call apt_marble/aliexpress-deals-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apt_marble/aliexpress-deals-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/I1nU2vEs9XgC8pgej/builds/fgijUogdEGwbsWmLL/openapi.json
