# AliExpress Scraper (`s-r/aliexpress-scraper`) Actor

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

## Pricing

Pay per event

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 Scraper

Search AliExpress and get **60 products per page** back as rows: price in your own currency, the previous price, the star rating, and how many units have sold.

No login, no cookie, no API key. It reads the ordinary search page from an exit in the country you pick, so prices and currency are the ones a shopper there sees.

### What you get

- **60 products per page, not 17.** The rendered grid links 17 products. The page's own embedded payload carries 60, and that is what this actor reads. A scraper built on the HTML throws away nearly three quarters of every page it fetches
- **Units sold, on the markets that show it.** `sold_text` is AliExpress's own wording ("700+ sold") and `sold_at_least` is that as a number. It is the closest thing to a demand signal any marketplace publishes, and it is in the payload but not in the grid. AliExpress only serves it to some countries: measured present on the US and UK exits and absent on the Dutch, German and Brazilian ones
- **Price in the right currency, verified.** Pick a country and results come back for that market; the currency comes out of the payload rather than being assumed. US returns USD, Netherlands and Germany return EUR, Brazil returns BRL
- **Star rating** on roughly seven products in ten
- **Both prices and the claimed discount**, with the previous price dropped when it is not actually above the current one
- **Where it ships from.** `ships_from` is decoded from the card's own tracking blob and `ships_locally` flags anything not coming from China. On a Dutch run that separated 17 items in French, German, Spanish, Polish and Italian warehouses from 43 shipping out of China. This is the single field that decides whether an order takes days or weeks, and it is nowhere in the rendered grid
- **Every image, not just the thumbnail.** `images` averages six per product
- **Scarcity and promo badges as text**, for example "Early bird deal, only 1 left"
- **Paid placements flagged.** `is_promoted` marks AliExpress's own `ad` product type, which runs to about a quarter of a results page
- **Product id, SKU id and image** on every row

### Why the payload matters more than usual here

Most marketplace scrapers can get away with reading the rendered page, because the rendered page is what the shopper sees. AliExpress is different: it renders a fraction of its results as links and keeps the rest in a JavaScript config object that the front end paints later.

The number that matters is 17 against 60. A DOM parser returns 17 rows, they are all valid, and nothing anywhere reports that 43 products were dropped. The run looks fine. You only find out when your competitor's dataset is three and a half times the size of yours.

Reading that object is not a regex job either. It is close to 400 KB of nested JSON, and product titles routinely contain braces, brackets and escaped quotes, so counting delimiters without tracking string state closes the object halfway through somebody's product name. This actor uses a string-aware matcher.

### Input

| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
| `query` | string | one of the two | `laptop` | What to search for |
| `country` | select | no | `us` | Sets the market, and therefore price and currency |
| `url` | string | one of the two | – | An AliExpress search or category URL. Takes precedence |
| `deals_only` | boolean | no | `false` | Keep only products with a real previous price |
| `limit` | integer | no | `60` | Products to return, 1 to 1000. A page carries 60 |
| `retries` | integer | no | `3` | Retry attempts per page |

### Output

```json
{
  "position": 1,
  "product_id": "3256809981706062",
  "url": "https://www.aliexpress.com/item/3256809981706062.html",
  "title": "14.1\" Ultra Slim Windows 11 Pro Laptop Computer Intel 12GB RAM 1TB SSD",
  "price": 184.54,
  "price_text": "US $184.54",
  "was_price": 456.79,
  "was_price_text": "US $456.79",
  "discount_percent": 59.0,
  "currency": "USD",
  "rating": 4.9,
  "sold_text": "700+ sold",
  "sold_at_least": 700,
  "ships_from": "ES",
  "ships_locally": true,
  "selling_points": [
    "Early bird deal, only 1 left"
  ],
  "image_count": 8,
  "image": "https://ae-pic-a1.aliexpress-media.com/kf/...",
  "sku_id": "12000055944120646",
  "product_type": "natural",
  "is_promoted": false,
  "country": "us",
  "query": "laptop"
}
```

### Use cases

**Finding what actually sells.** Sort a category by `sold_at_least` and you have the products with proven demand rather than the ones AliExpress chose to rank. For anyone sourcing stock, that column is the whole point of the dataset.

**Sourcing price research.** Run your product category and take the distribution of `price`. Because results are per country you can run the same term for US, EU and Brazil and see three real landed-price pictures rather than one converted number.

**Competitor product discovery.** `product_id` is stable, so a scheduled run diffed on id shows what appeared in a category and what disappeared.

**Sourcing from EU or US warehouses only.** Filter on `ships_locally` and you have the subset that arrives in days instead of weeks. On a Dutch run that was 17 products out of 60, spread across five European countries. Buyers care about this more than about a few euros of price difference, and no other AliExpress actor returns it.

**Rating-weighted shortlists.** Combine `rating` with `sold_at_least` to filter out the listings with a high score and eleven sales, which dominate a naive sort by rating.

**Feeding a reverse lookup.** `title` plus `image` is enough to match an AliExpress listing back to the same product on Amazon or a European marketplace.

### How it compares

| | this actor | `devcake/aliexpress-products-scraper` | `khadinakbar/aliexpress-all-in-one-scraper` |
|---|---|---|---|
| Per 1.000 products | **$2,00** | no per-item rate | $2,00 |
| Actor-start fee | $0,001 | $0,00005 | $0,00005 |
| Products per page | **60** | not stated | not stated |
| Units sold | **yes** | not stated | not stated |
| Currency follows country | **yes, verified** | not stated | not stated |
| Monthly users | new | **301** | 92 |

Honest about the other side: `devcake` has 301 monthly users to this actor's none and charges nothing per row, so on a large run it is cheaper. The category has 98 actors and 1.255 monthly users between them, so no one owns it.

### Pricing

Two events. `run_start` costs $0,0010 per run. `product` costs $0,0020 per product written to the dataset, which is $2,00 per 1.000. Pages whose payload is missing never reach the dataset and are never billed.

### Limits and gotchas

- **Almost everything on AliExpress is "discounted".** 147 of 150 products in a test run carried a previous price. That is a pricing convention on the platform, not a sale. Treat `discount_percent` as a marketing figure and `price` as the real number, and do not build alerts on the discount alone.
- **Units sold is a US and UK field.** Measured across five exits: present on all 60 rows for `us` and `gb`, and on none at all for `nl`, `de` and `br`. If you need that column, run the US market. The summary flags a run where it is entirely absent so a zero is never mistaken for a parse failure.
- **`sold_at_least` is a floor, not a count.** AliExpress rounds and appends a plus, so "700+ sold" becomes 700. It is returned as the number it actually represents rather than dressed up as exact.
- **Some real products ship with no price.** Roughly one row in ten comes back with `price` absent because AliExpress's payload carries `prices: null` for it. Those are genuine products, not banners, so they are kept with title, id, image and rating rather than dropped or given a guessed price. `withPrice` and `withoutPrice` in the summary give the split.
- **About a quarter of a results page is advertising.** `is_promoted` marks them; they are not removed.
- **Rating is missing on about three products in ten**, which are usually new listings with no reviews yet. They come back null, not zero.
- **Promoted listings are mixed into the results.** `product_type` is the closest signal AliExpress gives; they are not separated out.
- **A missing payload is reported, not swallowed.** If the embedded object is absent the run says `no_payload` rather than returning zero products, because an empty result and a soft block look identical otherwise.
- **The same query in two countries returns different products**, not just different prices. AliExpress varies its catalogue by destination.

### FAQ

**Why do I get more products than other AliExpress actors?**
Because this one reads the page's embedded payload, which holds 60 products, instead of the rendered grid, which links 17.

**Are the discounts real?**
Mostly not. Nearly every AliExpress listing shows a previous price. The current price is the meaningful figure.

**Can I get prices in euros?**
Yes, set `country` to a euro market and the currency comes back as EUR.

**Why is units-sold empty for my country?**
Because AliExpress only publishes it to some markets. It is there on the US and UK exits and absent on the Dutch, German and Brazilian ones. Run `country: us` if you need it.

**Is `sold_at_least` an exact sales count?**
No. It is AliExpress's rounded public figure with the plus removed, so it is a lower bound.

**Does it scrape product detail pages or reviews?**
No. It reads search and category pages only. Everything returned is on that one page.

### Why there is no product-detail mode

Deliberate: AliExpress product pages are not readable over a plain request, so this Actor returns listing data only.

So a detail mode here would need a real browser per product, which is a different cost structure and a separate decision. The listing payload already carries price, previous price, discount, rating, units sold, ship-from country, SKU id and up to eight images, which is most of what a detail page would have added.

### Related Actors

- [eMAG Scraper](https://apify.com/s-r/emag-scraper) — Romania, Bulgaria and Hungary
- [Newegg Scraper](https://apify.com/s-r/newegg-scraper) — US retail with real listing-page discounts
- [Beslist.nl Scraper](https://apify.com/s-r/beslist-scraper) — Dutch price comparison with EAN barcodes

# Actor input Schema

## `query` (type: `string`):

What to search for on AliExpress. Optional if you pass a URL instead.

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

Which country to price for. This sets the exit IP, and AliExpress prices and converts currency off it.

## `url` (type: `string`):

An AliExpress search or category URL to read instead of a term. Takes precedence.

## `deals_only` (type: `boolean`):

Drop products with no previous price above the current one. Note that AliExpress marks almost everything as discounted.

## `limit` (type: `integer`):

How many products to return, 1 to 1000. A page carries 60.

## `retries` (type: `integer`):

Retry attempts per page, each with a rotated user agent and TLS fingerprint.

## Actor input object example

```json
{
  "query": "laptop",
  "country": "us",
  "url": "https://www.aliexpress.com/w/wholesale-laptop.html",
  "deals_only": false,
  "limit": 60,
  "retries": 3
}
```

# Actor output Schema

## `results` (type: `string`):

One row per product, from the page's embedded payload rather than its rendered grid.

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

Products returned, items found in the first payload, price, rating and sold-count coverage, and the currencies seen.

## `errors` (type: `string`):

Per-page failures with a code and a redacted message.

# 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 = {
    "query": "laptop",
    "country": "us",
    "limit": 60,
    "retries": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/aliexpress-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 = {
    "query": "laptop",
    "country": "us",
    "limit": 60,
    "retries": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/aliexpress-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 '{
  "query": "laptop",
  "country": "us",
  "limit": 60,
  "retries": 3
}' |
apify call s-r/aliexpress-scraper --silent --output-dataset

```

## MCP server setup

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