# AliExpress Products Scraper (`scrapyx/aliexpress-products-scraper`) Actor

Product listings from AliExpress search and category pages: title, sale and original price, star rating, orders sold, images and badges. Locale-pinned so language and currency stay fixed, with cross-page de-duplication.

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

## Pricing

from $1.05 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
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?

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 Products Scraper

Product listings from AliExpress search and category pages — title, sale and
original price, star rating, orders sold, images and promo badges.

HTTP only, no browser, no login. Locale is **pinned**, so language and
currency stay fixed no matter which country your proxy exits from.

### Input

```json
{
  "mode": "search",
  "keywords": ["mechanical keyboard", "usb c hub"],
  "locale": "US",
  "sortBy": "orders",
  "maxPages": 3,
  "maxItems": 200
}
```

Or point it at listing URLs you already have:

```json
{
  "mode": "urls",
  "listingUrls": ["https://www.aliexpress.com/w/wholesale-headphones.html"],
  "locale": "DE"
}
```

### What you get

| `recordType` | One per | Carries |
| --- | --- | --- |
| `SEARCH_SUMMARY` | keyword / URL | pages fetched, rows fetched vs returned, duplicates dropped, upstream's total per page, why paging stopped, how many products had no price |
| `PRODUCT` | product | id, URL, title, prices, rating, orders, images, badges — plus the full raw upstream object |
| `ERROR` | failed input | why |

```jsonc
{
  "recordType": "PRODUCT",
  "productId": "3256809308143808",
  "productUrl": "https://www.aliexpress.com/item/3256809308143808.html",
  "title": "AJAZZ NK61 Wired Mechanical Keyboard 61 Keys 60% Compact...",
  "currencyCode": "USD", "salePrice": 14.18, "originalPrice": 48.25,
  "starRating": 4.9,
  "ordersText": "10,000+ sold", "ordersMin": 10000,
  "sellingPoints": ["New shoppers save $34.07", "Lowest price in 90 days"],
  "imageUrls": ["https://ae-pic-a1.aliexpress-media.com/kf/....jpg", "..."],
  "raw": { "...": "upstream's object, untouched" }
}
```

### Known limits — read these before you rely on the output

**Listings only. Product detail pages are not supported.** `/item/…` pages
and per-buyer review text sit behind Alibaba's Baxia WAF with no HTTP-only
path (10+ endpoints were probed). A detail URL is refused with an
explanation rather than half-working.

**The same search returns different products each time.** Page 1 was fetched
twice, back to back, identical URL and session: **28 of 60 products
matched — 32 differed**. That is upstream ranking churn, not this actor.
Consequences: products are de-duplicated by id across pages automatically
(`duplicatesDropped` tells you how many), and two runs with identical input
will legitimately differ. If you need a stable set, run wider (more pages)
rather than repeating.

**`upstreamTotalResultsByPage` is reported, not trusted.** AliExpress's own
result count for one query moved between **62,054** and **5,713 / 5,727 /
5,730** within a single session. It is passed through per page so you can see
the wobble, and is never used to compute completeness or page counts.

**Some genuine products ship with no price.** Upstream sends `prices: null`
on a real minority of listing cards — same `itemType`, real id, real title,
ratings and images all present. Measured 27 of 60 on "phone case", 0 of 60 on
"mechanical keyboard": it varies by query. Those rows keep `salePrice: null`;
nothing is defaulted to `0`. The rate per query is in the summary's
`productsWithoutPrice`.

**Orders are a band, not a count.** Upstream publishes "10,000+ sold", never
an exact figure. `ordersMin` is the parsed **lower bound** and `ordersText`
keeps the original string. Do not treat `ordersMin` as a sales total.

**Locale changes results, not just labels.** With no locale pinned,
AliExpress decides from the exit IP — an Indonesian exit returned Indonesian
titles and IDR prices, *and a different ranking* (a straw handbag was the top
hit for "laptop"; with `US` it was an actual MSI laptop). This actor always
pins the locale cookie, so your proxy country does not leak into your data.

**Seller/store data is gone.** AliExpress removed per-item store fields from
this payload in 2026 — `store` is `null` on every row. That is upstream, not
a parsing gap, so no store column is offered.

### Technical

Server-rendered listing pages; the payload is a JSON object on
`window._dida_config_._init_data_`. 60 products per page.

The WAF here (Alibaba Baxia/Aliyun) answers with **HTTP 200 and a challenge
body**, never a 4xx — so every response is body-scanned for challenge markers
before being treated as content, and a challenge rotates both TLS profile and
exit IP. No challenge was encountered on the listing surface during testing.

`robots.txt` checked: no `ClaudeBot`/`anthropic-ai` group, no blanket
disallow, `/w/` not disallowed.

Full recon trail is in `CRAWLING_METHOD.md`.

# Actor input Schema

## `mode` (type: `string`):

'search' builds listing URLs from your keywords. 'urls' takes AliExpress listing URLs you already have. Product detail pages (/item/…) are not supported and are refused with an explanation — they are WAF-gated and cannot be read over HTTP.

## `keywords` (type: `array`):

Search terms, one per row. Each becomes a listing URL of the form /w/wholesale-<slug>.html and is paged independently.

## `listingUrls` (type: `array`):

AliExpress LISTING URLs (a /w/wholesale-\*.html search page or a category page). Used by the 'urls' mode.

## `locale` (type: `string`):

Pins the country, currency and language. This matters more than it looks: with no pin, AliExpress decides from the exit IP, so on a rotating residential proxy one run can mix languages AND currencies. It also changes the RANKING — the same query returned a straw handbag as the top 'laptop' hit on one locale and an actual laptop on another.

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

Result ordering. All four values were verified to genuinely change the results. An unrecognised value is refused here rather than sent, because upstream does NOT reject one — it silently returns default ordering with HTTP 200.

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

How many listing pages to fetch per keyword or URL. Each page is 60 products before de-duplication. Paging also stops early on a short page or on a page that introduces no new products.

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

Optional cap on total product rows across the whole run, shared across every keyword. Counted AFTER de-duplication, so you get the number you asked for.

## `maxConcurrency` (type: `integer`):

How many requests may be in flight at once. Kept low by default: this property rate-limits page fetches aggressively, and a challenged request costs more than a slow one.

## `minRequestInterval` (type: `number`):

The honest speed control: the shortest gap between two request starts. Deliberately unhurried — the WAF here answers with HTTP 200 and a challenge body, so pushing harder costs pages rather than failing loudly.

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

Apify Proxy on the shared datacenter pool. This is the default because it is included in your plan at no extra cost and this target works through it. If you start seeing blocks, challenges or empty results, switch the group here to Residential -- it uses real consumer IPs and gets through more, but Apify bills residential traffic per gigabyte, so leave it off unless you need it.

## Actor input object example

```json
{
  "mode": "search",
  "keywords": [
    "mechanical keyboard"
  ],
  "listingUrls": [],
  "locale": "US",
  "sortBy": "default",
  "maxPages": 3,
  "maxConcurrency": 2,
  "minRequestInterval": 1,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/aliexpress-products-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/aliexpress-products-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 '{}' |
apify call scrapyx/aliexpress-products-scraper --silent --output-dataset

```

## MCP server setup

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