# AliExpress Search Scraper — Prices and Promotion Context (`peerless_columbine/independent-aliexpress-research`) Actor

Collect AliExpress keyword listings with displayed prices, currency, ratings, sales badges and promotion conditions. Independent search tool with locale checks and bounded diagnostics.

- **URL**: https://apify.com/peerless\_columbine/independent-aliexpress-research.md
- **Developed by:** [tingyou333 zhuang](https://apify.com/peerless_columbine) (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.50 / 1,000 product rows

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## AliExpress Search Scraper — Prices and Promotion Context

Collect AliExpress keyword listings with product IDs, titles, images, displayed prices, source currency, ratings, sales badges and the conditions attached to promotional offers. Export JSON, CSV or Excel for product discovery and catalog comparison.

**Independent tool; not affiliated with AliExpress.** The supported release scope is bounded keyword search. Product-detail/SKU extraction, delivery eligibility and broad international coverage are not established capabilities of this release.

### What you can use it for

- Build shortlists of listings for a keyword, retaining the product link and original source position.
- Compare displayed offers together with their currency, promotion notices and selected SKU reference.
- Export available ratings and sales badges with explicit nulls and lower-bound indicators.
- Inspect source-locale and rejected-card diagnostics before using a result downstream.

Searches use public structured pages. The Actor does not use your cookies, log in, solve challenges or substitute static/sample products when the source is blocked.

### Quick start

Start with one keyword, three results and one page. This example is the exact saved validation input; `country` is intentionally omitted. A Korean language/KRW request does **not** mean a Korean delivery destination. The historical cloud source reported US/ko/KRW for this input.

```json
{
  "keywords": [
    "wireless earbuds"
  ],
  "currency": "KRW",
  "language": "ko",
  "maxItems": 3,
  "maxPages": 1,
  "includeVariants": false,
  "maxConcurrency": 1,
  "maxRetries": 0,
  "requestTimeoutSecs": 30,
  "maxRunSeconds": 90,
  "dnsMode": "system",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "sort": "default",
  "requireLocaleMatch": true,
  "outputFormat": "kawsar"
}
```

After the run, open the dataset and **SUMMARY**. A nonempty dataset can belong to a PARTIAL run, so check both. Copy the complete input above. Selected fields from one actual result appear in [Example and price conditions](#example-and-price-conditions), with the observed counts and locale in [Latest acceptance sample](#latest-acceptance-sample). These are historical observations, not current quotes.

The demonstrated acquisition scope includes small ko/KRW search samples; en/USD and explicit US settings have local saved-response and earlier live evidence. Each new request still needs observed source-locale verification. Shipping, seller eligibility and worldwide coverage are not inferred from language, currency or a regional hostname.

### Inputs

| Field | Default / limits | Behavior |
|---|---|---|
| `keywords` | Required unless an alternate target input is used; 1–10 combined targets | Search each distinct keyword separately. |
| `maxItems` | 48; 1–200 per keyword | Maximum valid output rows after filters and deduplication; not a promise to find that many. |
| `maxPages` | `ceil(maxItems / 60)`; 1–20 | Hard page cap. Results can end earlier due to source limits, duplicates or errors. |
| `sort` | `default` | `default`, `price_asc`, `price_desc`, `orders`, `rating`. Default preserves source order; other modes sort collected candidates. Global catalog ranking is unverified. |
| `minPrice`, `maxPrice` | Absent | Inclusive filters on the raw displayed value in its source currency. No FX conversion or ordinary-price inference. |
| `minRating`, `minOrders` | Absent; rating 0–5, orders ≥0 | Unknown values do not satisfy an active filter. Abbreviated sales badges are marked lower bounds. |
| `currency`, `country` | Absent | Optional uppercase currency (3 letters) and destination (2 letters). Country is never derived from language/currency. |
| `language` | Runtime request language `en` when omitted | Explicit choices: en, de, es, fr, pt, it, nl, ja, ko. Input availability does not prove source support in every locale. |
| `requireLocaleMatch` | `true` | Reject mismatched/unverified explicit locale requests. False retains actual source values but reports locale gaps/PARTIAL; it does not convert currency. |
| `requestTimeoutSecs` | 60; 30–180 | Per-request limit, also constrained by the whole-run budget. |
| `maxRunSeconds` | 95; 10–110 | Collection deadline; set a compatible platform timeout, such as 120 seconds. |
| `maxConcurrency`, `maxRetries` | 2 / 1; ranges 1–8 / 0–2 | Bounded parallel targets and retries. Challenges are not solved or repeatedly retried. |
| `proxyConfiguration`, `useProxy` | Off | Optional user-owned proxy settings. Configuration failures are explicit; real paid-proxy connectivity is unverified and proxy costs are extra. |
| `dnsMode` | `system` | `google-doh` is an explicit alternate resolver with TLS and public-IP checks. Keep system DNS for the supplied cloud example. |
| `outputFormat` | `kawsar` | Primary field layout; `devcake` selects alternate names and string-valued tags. This is a field mapping, not complete competitor parity. |
| `searchQueries`, `maxProducts`, `sortBy` | Secondary aliases | `maxProducts` defaults to 50 with `searchQueries`, range 50–1000. Conflicting aliases fail. `sortBy` omits `rating`. |
| `productUrls`, `includeVariants` | Absent / false | Detail and SKU routes exist but are **outside the validated release scope**. Missing required detail/SKU data fails the target. Leave them unused for the supported search workflow. |

Local filters and candidate sorting are implemented; exact server filter/sort behavior is not independently guaranteed. Requests for variants can make otherwise usable listings fail if the detail source is unavailable.

### Output fields

| Fields | Type | Meaning |
|---|---|---|
| `keyword`, `page`, `position` | string, integer, integer | Original query and 1-based source page/card position; positions can have gaps after filtering or rejection. |
| `productId`, `productTitle`, `productUrl` | string | Source listing identity, full title and direct item link. |
| `imageUrl`, `additionalImages` | string, string\[] | Source image links; extra images may be empty. |
| `price`, `currency` | number, string | Finite displayed price and source currency. Never relabelled to the requested currency. |
| `formattedPrice`, `originalPrice`, `originalFormattedPrice`, `discount` | string/number or null | Source display/reference price and discount badge, when usable. Reference price is not a verified normal or historical selling price. |
| `priceContext` | object | Promotion type, builder, selected SKU, original page notice, marker sources and explicit uncertainty flags. |
| `rating`, `reviewCount` | number/integer or null | Available source rating/review count; absent values stay null. |
| `orders`, `ordersText`, `ordersIsLowerBound` | integer/null, string/null, boolean | Parsed sales badge, original text and whether it is abbreviated/qualified; not audited transaction history. |
| `tags`, `shipping`, `shippingFrom` | string\[], string/null, string/null | Public card labels and optional explicit shipping/origin fields. Null is unknown, not free shipping or delivery eligibility. |
| `isTopRated` | boolean | This Actor's rating ≥4.8 rule, not an independently verified AliExpress designation. |
| `scrapedAt`, `recordType` | string | UTC observation timestamp and record kind (`search` in the supported workflow). |
| `variants`, `variantStatus` | array/null, string | Null / `NOT_REQUESTED` for normal search. A selected SKU reference in priceContext is not a complete variant list. |
| `_source` | object | Response URL/hash, observation time, requested versus observed locale and ordering scope. |

The default dataset views are **products**, **devcake** and **provenance**. Both output formats preserve priceContext. The Output fields table describes the primary `kawsar` layout. The `devcake` option changes field names and uses string-valued tags; confirm its output shape in a small run before migrating.

### Example and price conditions

This is an excerpt of one actual row observed on 26 September 2026 UTC. Selected fields from one of the three accepted cloud rows are shown below. The exact quickstart input completed its bounded three-row request with verified US/ko/KRW source locale. This does not establish complete catalog coverage.

```json
{
  "keyword": "wireless earbuds",
  "page": 1,
  "position": 1,
  "productId": "3256811621288203",
  "productTitle": "2026 뉴 에어 3【 프로 3 】 심박수 모니터링 기능이 있는 블루투스 무선 이어버드, 능동형 소음 제거 헤드폰, 일상 사용을 위한 방수 기능, 아이폰 IOS 스마트폰용, 게임/스포츠/피트니스 이어폰.",
  "price": 29363,
  "currency": "KRW",
  "formattedPrice": "₩ 29,363",
  "orders": 4000,
  "ordersIsLowerBound": true,
  "priceContext": {
    "kind": "DISPLAYED_PROMOTIONAL_PRICE",
    "builderType": "skuCoupon",
    "selectedSkuId": "12000056622341651",
    "ordinarySingleItemPrice": null,
    "eligibilityVerified": false,
    "sourcePriceTips": {
      "confirm": "알겠습니다",
      "title": "가격 설명",
      "priceDesc": "신규 회원용 표시 가격은 #placeholder#에서 상품 3개를 구매할 때 지불할 총구매 가격입니다."
    }
  }
}
```

**These are displayed promotional values, not verified ordinary single-item or checkout prices.** Source notices can refer to new-member, coupon or multi-product offers. We retain the original notice and selected SKU instead of inferring eligibility, dividing a bundle value, converting currency or filling an ordinary price. A page-level notice does not prove that every item has identical conditions. `ordinarySingleItemPrice=null` and `eligibilityVerified=false` must remain visible in downstream use.

### Result status and diagnostics

`maxItems` is an upper bound. With default order, no filters/variants and verified locale, the requested N valid rows can complete even when a later card is invalid. The run then retains a warning and `sourceComplete=false`. Unknown card types are diagnosed; valid advertisements are not discarded just because they are advertisements.

An uncertain card inside the selected prefix, too few valid rows with card issues, sorting/filter/variant uncertainty, repeated pages, source errors or locale mismatches remain PARTIAL/BLOCKED as appropriate. All fetched cards and page/query/locale checks run before the cap is accepted. Earlier valid pages may remain in the dataset after a later failure. No error object is written as a product.

`sourceComplete` concerns detected core-field integrity of fetched material, not proof of complete page/catalog enumeration, optional fields, details or delivery. Use the cap, selection-prefix and PARTIAL rules in this section when automating.

| Output | Contents |
|---|---|
| Dataset | Valid persisted product rows only. |
| `SUMMARY` | Outcome, stop reasons, selected counts, warnings and locale observations. |
| `DIAGNOSTICS` | HTTP/DNS failures and response hashes. |
| `CARD_DIAGNOSTICS` | At most 32 rejected-card samples and 32 KiB of complete SDK-serialized JSON per run; aggregate/truncation counts continue even when samples are omitted. |

### Pricing

The paid unit is **one valid product row saved in the default dataset**. Each separately saved record is another row. Inline arrays do not create extra row events. Failed requests, duplicate rows and diagnostic records do not create result events. A valid source record may have nullable optional fields; a row charge does not guarantee every field.

| Apify plan | USD per row | USD per 1,000 rows |
|---|---:|---:|
| FREE | 0.002 | 2.00 |
| BRONZE | 0.0018 | 1.80 |
| SILVER | 0.0016 | 1.60 |
| GOLD | 0.0015 | 1.50 |
| PLATINUM | 0.0015 | 1.50 |
| DIAMOND | 0.0015 | 1.50 |

There is no startup event fee. FREE names the Apify subscription tier; it does not mean results are free. The Pricing tab shows the active rate before a run.

**Apify platform compute, storage and transfer are charged separately**, including for failed or empty runs. An explicitly enabled proxy can add provider fees. A row limit is not an all-inclusive dollar cap. Use small inputs first and inspect actual run usage. Historical owner test runs are not customer revenue or cost forecasts.

### API example

Save the JSON from Quick start as `input.json` in your current directory. This example starts a charged run. Keep your Apify token in the `APIFY_TOKEN` environment variable.

```python
import json
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
with open("input.json", encoding="utf-8") as handle:
    actor_input = json.load(handle)
run = client.actor("2qUM3kDDGZaYKvMaE").call(
    run_input=actor_input, memory_mbytes=256, timeout_secs=120
)
summary = client.key_value_store(run["defaultKeyValueStoreId"]).get_record("SUMMARY")
rows = client.dataset(run["defaultDatasetId"]).list_items().items
print(run["status"], summary["value"], rows)
```

### FAQ

**Why did I get fewer results or a failed run?** The cap is a maximum. Source challenges, locale mismatches and incomplete core cards are explicit outcomes. Check SUMMARY before treating partial data as complete.

**Does Korean/KRW mean shipping to Korea?** No. Country is separate. Request it explicitly if needed; strict mode requires the source to report that destination. Reported destination is still not proof of delivery eligibility.

**Are stock, full variants or ordinary prices guaranteed?** No. Those are outside the validated search scope. Unknown fields remain null and promotional conditions remain attached.

**Can I migrate from another scraper?** Common input and field layouts are supported, with documented type differences. Complete competitor coverage, exit-status parity and worldwide reliability are not claimed.

**Can it use my cookies or solve a CAPTCHA?** No. Only fresh anonymous public locale preferences are generated. Login/challenge pages are failures, not product rows.

The [Latest acceptance sample](#latest-acceptance-sample) section records the bounded cloud evidence; other coverage limits are stated in Inputs and Result status and diagnostics. Brand artwork identifies the target service; it does not imply affiliation or endorsement.

### Latest acceptance sample

The quickstart input was checked in Apify Cloud on 2026-09-26 (UTC; run finished at 2026-09-26T18:34:39.340Z). It saved 3 valid rows. The output example on this page copies real source values from that dataset; it is a dated sample, not current inventory or a current quote.

The three-row core sample observed US/ko/KRW and 60 valid cards with no rejected cards. A separate one-row billing check of the identical runtime found 56 valid cards and four cards without prices at positions 39, 42, 46 and 57. All four were after the selected first row: the request succeeded with `sourceComplete=false` and `INVALID_CARDS_AFTER_CAP`. The four rejected cards were saved only in CARD\_DIAGNOSTICS (8,161 serialized bytes), not as billed dataset rows. The 32 KiB truncation stress case has local test coverage only. Displayed promotional values do not establish an ordinary unit price or purchase eligibility.

# Actor input Schema

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

One or more product keywords to search on AliExpress (e.g. 'smartphone', 'wireless earbuds', 'phone case').

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

How to sort the search results page.

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

Only include products priced at or above this value. Applies to the raw displayed promotional value; not a verified ordinary single-item or checkout price. See priceContext.

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

Only include products priced at or below this value. Applies to the raw displayed promotional value; not a verified ordinary single-item or checkout price. See priceContext.

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

Only include products with a star rating at or above this value (0 to 5).

## `minOrders` (type: `integer`):

Only include products with at least this many orders sold.

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

Maximum number of products to collect per keyword.

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

Hard cap on pages scraped per keyword. Leave blank to auto-paginate based on Max items (recommended).

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout in seconds. Increase if you see timeout errors.

## `searchQueries` (type: `array`):

Optional secondary-contract alias of keywords. Conflicting aliases are rejected.

## `productUrls` (type: `array`):

Public AliExpress item URLs or page-one search URLs. Tracking spm is ignored; pass other filters explicitly.

## `maxProducts` (type: `integer`):

Secondary contract per-query limit; default 50 when searchQueries is used.

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

Secondary-contract alias of sort.

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

User-owned proxy configuration; off by default. May incur charges in your account. Never silently falls back.

## `useProxy` (type: `boolean`):

Alias enabling your Apify proxy. Omit when proxyConfiguration explicitly selects a mode.

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

Optional requested destination ISO country. Generates only the public region preference; never inferred from language/currency. Strict matching requires source destination metadata.

## `currency` (type: `string`):

Optional requested ISO currency. Generates only the public currency preference; source currency must match, with no conversion or relabeling.

## `language` (type: `string`):

An explicit language generates the documented public site/buyer-locale preferences. No destination or currency is inferred. Strict source locale checking remains enabled.

## `requireLocaleMatch` (type: `boolean`):

Reject unverified or mismatched explicit locale requests. False retains source values with visible provenance gaps.

## `includeVariants` (type: `boolean`):

Fetch item details; requires real SKU ids, prices and properties. Missing SKU data is a target failure.

## `outputFormat` (type: `string`):

kawsar preserves tags as array; devcake uses its documented flat names and string tags.

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

Concurrent targets and maximum HTTP connections; validated range 1 to 8.

## `maxRetries` (type: `integer`):

Retries only network/429/5xx failures; never retry or solve a challenge.

## `maxRunSeconds` (type: `integer`):

Whole collection time budget, leaving room for storage within a 120-second run.

## `dnsMode` (type: `string`):

system by default. Explicit google-doh uses fixed public DNS with TLS and public-IP validation.

## Actor input object example

```json
{
  "keywords": [
    "wireless earbuds"
  ],
  "sort": "default",
  "maxItems": 3,
  "maxPages": 1,
  "requestTimeoutSecs": 60,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "requireLocaleMatch": true,
  "includeVariants": false,
  "outputFormat": "kawsar",
  "maxConcurrency": 2,
  "maxRetries": 1,
  "maxRunSeconds": 95,
  "dnsMode": "system"
}
```

# Actor output Schema

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

No description

## `products` (type: `string`):

No description

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

No description

## `cardDiagnostics` (type: `string`):

No description

## `diagnostics` (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 = {
    "keywords": [
        "wireless earbuds"
    ],
    "maxItems": 3,
    "maxPages": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("peerless_columbine/independent-aliexpress-research").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 = {
    "keywords": ["wireless earbuds"],
    "maxItems": 3,
    "maxPages": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("peerless_columbine/independent-aliexpress-research").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 '{
  "keywords": [
    "wireless earbuds"
  ],
  "maxItems": 3,
  "maxPages": 1
}' |
apify call peerless_columbine/independent-aliexpress-research --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,peerless_columbine/independent-aliexpress-research"
        }
    }
}
```

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/2qUM3kDDGZaYKvMaE/builds/7U7CurpuhtGOSwfYi/openapi.json
