AliExpress Products Scraper — Prices, Sellers, SKUs & Stock avatar

AliExpress Products Scraper — Prices, Sellers, SKUs & Stock

Pricing

from $2.50 / 1,000 per product returneds

Go to Apify Store
AliExpress Products Scraper — Prices, Sellers, SKUs & Stock

AliExpress Products Scraper — Prices, Sellers, SKUs & Stock

Search AliExpress by keyword or product URL and get every product as one row: sale and original price, discount, rating, orders, store and seller (id, positive rate, opened date), per-SKU prices and stock, shipping fee and delivery date. No login, no browser, no CAPTCHA solving.

Pricing

from $2.50 / 1,000 per product returneds

Rating

0.0

(0)

Developer

Scrapers Delight

Scrapers Delight

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Share

Search AliExpress by keyword and get one clean row per product: sale price, original price, discount %, rating, orders, selling-point tags, the full image gallery, the leaf categoryId, the storeName, the ships-from country and a delivery-days / delivery-date estimate — all of it read out of the search page itself, so it does not depend on any second request clearing.

⚠️ Read this first — the seller / SKU / stock columns are not currently deliverable. Those fields come from AliExpress's signed product-detail API, and as of 2026-08-27 that API refuses every request this Actor can make: the first, unsigned, token-minting call comes back FAIL_SYS_USER_VALIDATE even from a clean IP that has never contacted the host, so a signed call cannot even be constructed. Measured 0/5 on Apify datacenter, 0/4 on Apify RESIDENTIAL and 0/3 from a clean home IP. It is not a rate limit, so waiting, lowering concurrency and swapping proxies do not help. fetchProductDetails therefore ships OFF, and the columns it would fill (skus[], totalStock, reviewCount, storeId, sellerId, sellerPositiveRate, shippingFee, properties, …) ship as null with detailFetched: false. They are still in the schema so the column set never moves, and you are never charged for a row that was not enriched. Turn the toggle on if you want to probe whether it has reopened. Everything listed in the paragraph above is unaffected.

One source, no browser: AliExpress's own search page (60 product cards per page, read from the _init_data_ blob the page ships to itself, including the per-card trace blobs most scrapers never open). No login, no AliExpress account, no API key, no browser, and no CAPTCHA is ever solved — a challenged session is simply retired and the page is retried on a fresh one. The Actor runs in 512 MB.

Scope: one keyword walks 60 products per results page, up to maxPagesPerQuery pages (default 20 ≈ 1,200 products per keyword; hard maximum 200). AliExpress advertises tens of thousands of results for a common term, but a search stops on its own when a page comes back short — the Actor detects that and moves to the next keyword instead of burning your budget on empty pages. Add more keywords to go wider; everything is de-duplicated on productId run-wide, before billing.

{
"searchQueries": ["wireless earbuds"],
"fetchProductDetails": false,
"shipToCountry": "US",
"currency": "USD",
"maxItems": 60
}

Click Try for free and hit Start — that block is literally the input the Actor ships with. It returns 60 products for $0.15, flat, with no second-request lottery in the price.


The wedge: the fields other scrapers leave on the table

Most AliExpress scrapers on the Store return what the search card obviously shows: title, price, rating, "10,000+ sold". But AliExpress hangs a second data layer off every cardimages[] and a trace object with utLogMap, pdpParams.pdp_cdi and custom.p4pExtendParam — and that layer carries fields most parsers never open. This Actor reads it, so these arrive on a plain search run, with no extra request and no second wall to clear (fill measured over 240 cards across four queries, 2026-08-27):

FieldFillWhere it comes from
images[] — the gallery, 5–8 per card99.6%images[] on the card
categoryId — the leaf category id100%trace.utLogMap.categoryId, last segment
shipsFrom — warehouse country, ISO-294%trace.pdpParams.pdp_cdi (URL-encoded JSON)
storeName59%trace.custom.p4pExtendParam.store_name
deliveryDaysMin / deliveryDaysMax / deliveryDate62% (25–75% by keyword)trace.utLogMap.delivery_info

Two of those are worth calling out:

  • shipsFrom separates a China-warehouse 20-day item from a US/EU-warehouse 4-day one — the single biggest driver of whether a product is sellable — and you get it without a product-page request. Sample split: CN 173, US 52. Note it is the ship-from of that card's SKU; a multi-warehouse listing can offer others.
  • categoryId is the leaf id, cross-checked two ways: the last segment of the utLogMap path equals trace.exposure.postCategoryId on 240/240 cards, and on a product where we also held a product-detail payload the leaf (63705) matched the detail API's categoryId exactly.

storeName at 59% is a partial column, not a complete store dataset — it is present on every sponsored card and on 45% of organic ones. Treat it as a strong hint for grouping, and read detailFetched before assuming any store field is authoritative.

What is NOT on the card, and is therefore not currently deliverable: storeId, storeUrl, sellerId, sellerCountry, storeOpenedAt, sellerPositiveRate, sellerLevel, sellerFeedbackCount, sellerScores, storeFollowers, reviewCount, wishlistCount, skus[], skuCount, totalStock, inStock, minSkuPrice, maxSkuPrice, shippingFee, shippingFeeText, freeShipping, freeShippingWithChoice, shippingProvider, properties, storeLogo, storeYearsOpen, topRatedSeller. We verified by needle-sweeping all 240 cards: none of the underlying values appears anywhere in the search payload. They need the product-detail API, which is blocked — see the banner at the top and Honest limits.

  • Enrichment you only pay for when it worked. The product-detail-enriched event is charged per row where the detail API actually returned data. A product that could not be enriched still ships as a search-level row, marked detailFetched: false, and is never charged the enrichment event. You are never billed for a null — which, while the API is blocked, means you are simply never charged that event at all.
  • Filters applied by AliExpress, before anything is fetched. minPrice, maxPrice and sortBy are pushed onto AliExpress's own search URL, so out-of-band products are normally never returned, fetched or billed. The filtering is AliExpress's, not ours — the Actor does not re-check prices locally, so if AliExpress returns a near-band item it will be billed. Filter on salePrice in your own pipeline if the band is hard. excludeSponsored drops paid "Ad" cards before billing too — AliExpress mixes them into every page (measured: 60 of 240 cards across four searches).
  • One record type, stable columns. With enrichment off, the seller / SKU / shipping / stock columns are still present and null. A CSV export or a downstream table keeps the same schema between runs whatever you toggle.

Read this before you buy rows

  1. Buy this for the search-level row, not for the seller/SKU columns. As of 2026-08-27 the product-detail API is refusing every request, so fetchProductDetails ships off and roughly half the schema ships null. What you reliably get per row: title, salePrice, originalPrice, discountPercent, rating, ordersText/ordersCount, sellingPoints, isSponsored, listedAt, imageUrl, images[], categoryId, shipsFrom, storeName (partial), deliveryDaysMin/Max, deliveryDate (partial), and full search provenance. If you need skus[], totalStock, reviewCount or the seller-identity columns, this Actor cannot currently give them to you — do not buy rows expecting them.
  2. The block is not a rate limit, so none of the usual knobs fix it. The first, unsigned, token-minting call to the detail API returns FAIL_SYS_USER_VALIDATE and never sets an _m_h5_tk cookie, from a clean IP that had never contacted the host. Measured 0/5 Apify datacenter, 0/4 Apify RESIDENTIAL, 0/3 clean home IP. Lower maxConcurrency, longer waits, different proxies and re-running later have all been tried and none of them changes it.
  3. Do NOT switch the proxy to RESIDENTIAL to "fix" enrichment. It does not, and datacenter is the right rung for the search pages that carry your actual data. Residential is used automatically as a small capped fallback for search pages when a datacenter page comes back challenged. (One caveat measured 2026-08-27: the punish is not confined to the detail API — a home IP got x5sec-walled on the search path after about six page fetches, while Apify RESIDENTIAL cleared the same queries first try. The Actor detects a walled search page and retries on a fresh session, but a long run can still lose pages.)
  4. ordersCount is a floor, not an exact number. AliExpress prints buckets — "10,000+ sold" — so ordersText is the literal string and ordersCount is the number parsed out of it (10000). Verified over 216 cards where AliExpress's own internal exact counter was also present: ordersCount was never higher than the true count (104 exactly equal, 112 lower). Read it as "at least this many". sellerSalesText is the same bucketed string AliExpress prints on the product page's rating module — it duplicates ordersText on search-level rows.
  5. Product URLs are not usable right now. A URL row has no search card behind it, so everything except productId and productUrl would have to come from the blocked detail API. Use searchQueries instead; the run logs a warning if you paste URLs anyway.
  6. Prices are what a logged-out visitor in your shipToCountry sees. Account-specific coupons, app-only prices, "welcome deal" pricing and basket-level promotions are not visible without logging in, and this Actor never logs in. freeShippingWithChoice: true means shipping is free only on a qualifying Choice basket, not on this item alone — read it as a condition, not a fee.
  7. A search stops when AliExpress runs out. A short page (fewer than half of 60 cards), a page with zero cards, or the advertised total being reached ends that keyword cleanly with a log line, not an error. Deep pagination on a narrow term will simply stop early.

What you get

One row per unique product, 55 fields, de-duplicated run-wide on productId. The search card fills the listing fields; the detail API — currently blocked — would fill the rest and could also refine title, imageUrl, currency, salePrice, originalPrice, discountPercent and rating with the product page's own numbers. Every field below is emitted on every row — as null when it is not available — so the column set never shifts, whatever you toggle and whatever AliExpress is blocking that day.

GroupFields
IdentityproductId, productUrl, title, imageUrl, images[], categoryId, listedAt
Pricecurrency, salePrice, originalPrice, discountPercent, minSkuPrice, maxSkuPrice
Demandrating, reviewCount, ordersText, ordersCount, sellerSalesText, wishlistCount
Store & sellerstoreName, storeId, storeUrl, storeLogo, sellerId, sellerCountry, storeOpenedAt, storeYearsOpen, topRatedSeller, sellerPositiveRate, sellerLevel, sellerFeedbackCount, sellerScores, storeFollowers
SKUs & stockskus[] (skuId, attributes, salePrice, originalPrice, stock, available, maxPerOrder), skuCount, totalStock, inStock
ShippingshippingFee, shippingFeeText, freeShipping, freeShippingWithChoice, deliveryDate, deliveryDaysMin, deliveryDaysMax, shipsFrom, shippingProvider
Listing extrassellingPoints[], isSponsored, properties
ProvenancedetailFetched, searchQuery, searchPage, searchPosition, shipToCountry, scrapedAt

The dataset ships with two saved table views. AliExpress products (the default) shows only columns that fill on every run — Product, Sale price, Original, Off %, Currency, Rating, Orders, Orders (min), Store, Ships from, Ship days min/max, Delivery by, Category id, Listed, Ad, Enriched, URL. Store, seller, SKUs & shipping shows the enrichment-only columns, which are empty while the detail API is blocked. The Console and a CSV export both open on the first one.

Which fields are always filled, and which depend on enrichment

The fill percentages below were measured on 240 search cards across four keywords on 2026-08-27 (wireless earbuds, bluetooth speaker, phone case, running shoes) — one sample, not a guarantee, and the delivery fields in particular swing hard by keyword (phone case returned 25% where the other three returned ~75%). Every run also prints its own counts in the log (search pages ok/failed, rows enriched vs not, duplicates dropped, sponsored rows filtered, and the per-error-code breakdown of retried detail responses). Plan against the categories below, then read your own run's log.

CategoryFieldsFilled when
Always presentproductId, productUrl, currency, shipToCountry, detailFetched, scrapedAt, sellingPoints[], isSponsoredEvery row. currency falls back to your currency input; sellingPoints is [] and isSponsored is false rather than null.
From the search cardtitle, imageUrl, salePrice, originalPrice, discountPercent, rating, ordersText, ordersCount, listedAt, searchQuery, searchPage, searchPositionEvery row that came from a keyword search. Rows from productUrls have no card, so searchQuery/searchPage/searchPosition are null and the rest arrive via the detail API.
From the card's trace layerimages[] (99.6%), categoryId (100%), shipsFrom (94%), storeName (59%), deliveryDaysMin/deliveryDaysMax/deliveryDate (62%, 25–75% by keyword), sellerSalesText (100%)Every row from a keyword search, at the fill rates shown (n=240 cards, 2026-08-27). No extra request, so these survive the detail API being blocked. sellerSalesText duplicates ordersText on these rows.
Enrichment-dependentthe whole Store & seller group except storeName, the whole SKUs & stock group, shippingFee, shippingFeeText, freeShipping, freeShippingWithChoice, shippingProvider, plus reviewCount, wishlistCount and propertiesOnly where detailFetched: true — which, while the detail API is blocked, is no rows. Filter on that flag rather than on any single field.
Best-effort even when enrichedsellerScores, properties, storeLogo, shippingProvider, topRatedSeller, storeYearsOpen, maxPerOrder inside skus[]AliExpress does not return every module for every listing — these are null where the product page itself does not carry them. A real zero (e.g. shippingFee: 0 on free shipping) is a zero, never a null.

How to run it

1. The shipped default — one keyword, one page

{
"searchQueries": ["wireless earbuds"],
"maxItems": 60
}

60 products, one results page: title, prices, discount, rating, orders, tags, image gallery, category id, ships-from, delivery estimate and store name where the card carries it. $0.15, flat — enrichment is off by default, so there is no variable half to the bill.

2. Dropshipping research — best sellers in a price band

{
"searchQueries": ["led strip lights", "phone case", "car phone holder"],
"sortBy": "orders",
"minPrice": 2,
"maxPrice": 15,
"excludeSponsored": true,
"maxItemsPerQuery": 120,
"maxItems": 300
}

sortBy: "orders" is AliExpress's most orders first ordering — the dropshipping-research choice. The price band and the sponsored filter are applied before anything is fetched or billed, so a $40 listing and an Ad card never appear on your invoice. maxItemsPerQuery: 120 = two pages per keyword, so one broad term cannot eat the whole 300-product budget. $0.75.

3. Supplier vetting — cheapest first, seller fields front and centre

{
"searchQueries": ["bluetooth speaker waterproof"],
"sortBy": "price_asc",
"shipToCountry": "DE",
"currency": "EUR",
"maxItems": 200
}

Sort by price ascending, then group your own dataset by storeName and shipsFrom. Note: the seller-quality columns this recipe used to lean on (sellerPositiveRate, storeOpenedAt, sellerScores) come from the blocked detail API and will be null — what you can do today is spot the same storeName across many cheap listings and separate CN-warehouse from EU/US-warehouse supply. Country and currency are pinned in AliExpress's locale cookie on the search request, so the result should not depend on the exit IP. The Actor does not verify the returned currency — check currency on the rows if you are mixing regions. $0.50.

4. Price monitor on a keyword ⚠️ (the product-URL version is not usable right now)

{
"searchQueries": ["tws earbuds anc"],
"sortBy": "orders",
"maxItems": 60
}

Save it as a Task, attach a daily Schedule, and diff salePrice / discountPercent / ordersCount / deliveryDaysMax per productId across runs. $0.15 / day.

The productUrls input still exists and still accepts /item/<id>.html URLs, aliexpress.us URLs and bare numeric ids — but a URL row has no search card behind it, so it can only be filled by the blocked detail API. Until that reopens, pasting URLs returns rows carrying nothing but productId and productUrl, and the run says so in the log. Watch products by keyword instead.

5. Cheap catalogue sweep — search-level rows only

{
"searchQueries": ["kitchen gadgets"],
"fetchProductDetails": false,
"maxPagesPerQuery": 10,
"maxItems": 600
}

Enrichment off (the default): title, price, discount, rating, orders, tags, gallery, category id, ships-from and delivery estimate at $0.0025 a row — $1.50 for 600 — and no detail-API calls at all, so the run is fast and never stalls on a challenge. The seller/SKU/stock columns are present and null.

Scheduling and integrations

Save the input as a Task (Console → the Actor → Save as Task), then attach an Apify Schedule. The dataset is available over the REST API and through the standard Apify integrations (Zapier, Make, n8n, webhooks, MCP) like any other Actor. From the API:

curl -X POST "https://api.apify.com/v2/acts/scrapersdelight~aliexpress-products-scraper/runs?token=YOUR_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"searchQueries":["wireless earbuds"],"sortBy":"orders","shipToCountry":"US","currency":"USD","maxItems":120}'

Sample row

What a run returns today (the shipped default, enrichment off)

Every key the Actor emits is present; the enrichment-only ones are null. Values are illustrative.

{
"productId": "1005012258457853",
"productUrl": "https://www.aliexpress.com/item/1005012258457853.html",
"title": "TWS Wireless Earbuds Bluetooth 5.3 Headphones with Charging Case",
"imageUrl": "https://ae-pic-a1.aliexpress-media.com/kf/S8f2c1e0a4b3d4e0f9c1a2b3.jpg",
"currency": "USD",
"salePrice": 9.24,
"originalPrice": 23.68,
"discountPercent": 61,
"rating": 4.7,
"ordersText": "10,000+ sold",
"ordersCount": 10000, // a floor: the bucket AliExpress prints, never an overstatement
"sellingPoints": ["Free shipping", "Choice"],
"isSponsored": false,
"listedAt": "2024-11-08",
"shipToCountry": "US",
// --- read off the search card's second data layer, no extra request ---
"images": [ // 99.6% of cards, 5-8 entries
"https://ae-pic-a1.aliexpress-media.com/kf/S8f2c1e0a4b3d4e0f9c1a2b3.jpg",
"https://ae-pic-a1.aliexpress-media.com/kf/Sa1b2c3d4e5f60718293a4b5.jpg"
],
"sellerSalesText": "10,000+ sold", // same bucketed string as ordersText on a search-level row
"categoryId": "63705", // 100% — the LEAF id
"storeName": "TWS Audio Direct Store", // 59% — null on the other 41%
"deliveryDaysMin": 6, // 62% — these four move together
"deliveryDaysMax": 13,
"deliveryDate": "2026-09-09",
"shipsFrom": "CN", // 94% — ISO-2, for THIS card's SKU
// --- enrichment-only: null while the product-detail API is blocked ---
"reviewCount": null,
"inStock": null, "totalStock": null, "skuCount": null, "skus": null,
"minSkuPrice": null, "maxSkuPrice": null,
"storeId": null, "storeUrl": null, "storeLogo": null,
"sellerId": null, "sellerCountry": null, "storeOpenedAt": null, "storeYearsOpen": null,
"topRatedSeller": null, "sellerPositiveRate": null, "sellerLevel": null,
"sellerFeedbackCount": null, "sellerScores": null, "storeFollowers": null, "wishlistCount": null,
"shippingFee": null, "shippingFeeText": null, "freeShipping": null,
"freeShippingWithChoice": null, "shippingProvider": null, "properties": null,
"detailFetched": false,
"searchQuery": "wireless earbuds",
"searchPage": 1,
"searchPosition": 7,
"scrapedAt": "2026-08-27T14:31:09.117Z"
}

What a row would look like if enrichment reopened

Not currently obtainable — kept here so you can see the full schema and judge whether it is worth watching this Actor. Values illustrative.

{
"productId": "1005012258457853",
"productUrl": "https://www.aliexpress.com/item/1005012258457853.html",
"title": "TWS Wireless Earbuds Bluetooth 5.3 Headphones with Charging Case",
"imageUrl": "https://ae01.alicdn.com/kf/S8f2c1e0a4b3d4e0f9c1a2b3c4d5e6f7g.jpg",
"currency": "USD",
"salePrice": 9.24,
"originalPrice": 23.68,
"discountPercent": 61,
"rating": 4.7,
"ordersText": "10,000+ sold",
"ordersCount": 10000,
"sellingPoints": ["Free shipping", "Choice"],
"isSponsored": false,
"listedAt": "2024-11-08",
"shipToCountry": "US",
"images": [
"https://ae01.alicdn.com/kf/S8f2c1e0a4b3d4e0f9c1a2b3c4d5e6f7g.jpg",
"https://ae01.alicdn.com/kf/Sa1b2c3d4e5f60718293a4b5c6d7e8f90.jpg"
],
"reviewCount": 3184,
"sellerSalesText": "600+ sold",
"categoryId": "63705",
"inStock": true,
"totalStock": 8412,
"skuCount": 4,
"skus": [
{
"skuId": "12000029884517291",
"attributes": { "Color": "Black", "Ships From": "China" },
"salePrice": 9.24,
"originalPrice": 23.68,
"stock": 3120,
"available": true,
"maxPerOrder": 200
},
{
"skuId": "12000029884517292",
"attributes": { "Color": "White", "Ships From": "China" },
"salePrice": 9.24,
"originalPrice": 23.68,
"stock": 2894,
"available": true,
"maxPerOrder": 200
},
{
"skuId": "12000029884517293",
"attributes": { "Color": "Blue orange-AI", "Ships From": "United States" },
"salePrice": 11.86,
"originalPrice": 23.68,
"stock": 2398,
"available": true,
"maxPerOrder": 200
},
{
"skuId": "12000029884517294",
"attributes": { "Color": "Pink", "Ships From": "China" },
"salePrice": 9.99,
"originalPrice": 23.68,
"stock": 0,
"available": false,
"maxPerOrder": null
}
],
"minSkuPrice": 9.24,
"maxSkuPrice": 11.86,
"storeName": "TWS Audio Direct Store",
"storeId": "1102358041",
"storeUrl": "https://www.aliexpress.com/store/1102358041",
"storeLogo": "https://ae01.alicdn.com/kf/Sc3d4e5f60718293a4b5c6d7e8f901234.png",
"sellerId": "234618907",
"sellerCountry": "China",
"storeOpenedAt": "2019-06-14",
"storeYearsOpen": null,
"topRatedSeller": true,
"sellerPositiveRate": 96.8,
"sellerLevel": "Top Brand",
"sellerFeedbackCount": 41267,
"sellerScores": {
"Item as described": "4.8",
"Communication": "4.9",
"Shipping speed": "4.8"
},
"storeFollowers": 18422,
"wishlistCount": 6103,
"shippingFee": 0,
"shippingFeeText": "Free shipping",
"freeShipping": true,
"freeShippingWithChoice": true,
"deliveryDate": "Sep 12",
"deliveryDaysMin": 8,
"deliveryDaysMax": 15,
"shipsFrom": "CN",
"shippingProvider": "AliExpress Standard Shipping",
"properties": {
"Brand Name": "NoEnName_Null",
"Bluetooth Version": "5.3",
"Battery Capacity": "300mAh",
"Waterproof": "IPX5"
},
"detailFetched": true,
"searchQuery": "wireless earbuds",
"searchPage": 1,
"searchPosition": 7,
"scrapedAt": "2026-08-22T14:31:09.117Z"
}

Fields people misread:

  • salePrice vs minSkuPrice. salePrice is the price of the SKU AliExpress preselects; minSkuPrice / maxSkuPrice are the cheapest and dearest variants. A listing advertised at "$9.24" can have a $11.86 variant — that is the spread you need for margin maths.
  • discountPercent comes from AliExpress's own discount rate where the detail API gives one, and is otherwise derived from salePrice vs originalPrice. originalPrice is AliExpress's reference price, which is marketing, not a historical sale price.
  • freeShipping vs freeShippingWithChoice. The first means this shipping option costs 0 for your shipToCountry; the second means it is free only inside a qualifying Choice basket.
  • storeId vs sellerId. storeId is the number in the store URL (joins to store pages); sellerId is the seller account behind it. One seller can run more than one store.
  • storeYearsOpen is AliExpress's own sellerInfo.openedYear value, passed through unmodified — the Actor does not derive it from storeOpenedAt. Read it alongside storeOpenedAt rather than trusting it as a year count.
  • shipsFrom is the warehouse country code (CN, US, ES…), while sellerCountry is where the seller is registered. They differ constantly, and the warehouse is the one that decides delivery time. On a search-level row it is the ship-from of that card's SKU, so a multi-warehouse listing may offer others; if enrichment ever runs, the detail API's value (the default SKU's) overwrites it and the two can legitimately disagree.
  • sellerSalesText is AliExpress's bucketed "sold" line. On a search-level row it is filled from the card and is therefore the same string as ordersText — it is only a distinct value when the product-detail API's rating module answers. (An earlier sellerSimilarItemOrders column was removed on 2026-08-27: AliExpress dropped the "…from this seller:
  • detailFetched: false means the detail API never answered for that product — the row is a search-level row, not a broken one, and it was not charged the enrichment event.

Input

FieldTypeDefaultWhat it does
🎯 What to scrape
searchQueriesstring list["wireless earbuds"]Keywords to search, one per line. Each walks the results 60 products per page in the chosen sort order. Empty = the documented sample.
productUrlsstring list[]AliExpress product pages to scrape directly — /item/<id>.html, aliexpress.us URLs, or bare numeric ids. Can only be filled by the blocked detail API, so these currently return id + URL only. Use keywords instead.
sortByselectdefaultdefault (best match) · orders (best selling) · price_asc · price_desc. Keyword searches only.
minPrice / maxPricenumberemptyPrice band in your currency, applied by AliExpress on the search page — filtered-out products are never fetched or billed.
excludeSponsoredbooleanfalseDrop paid "Ad" cards before they are fetched or billed. Off = keep them with isSponsored: true.
📦 Output shape
fetchProductDetailsbooleanfalseTry the product-detail API for every product (store id, seller, SKUs, stock, shipping fee, properties). Blocked as of 2026-08-27 — off by default. Off = search-card fields only, billed at the product rate alone. Columns stay identical either way.
shipToCountrystringUSTwo-letter code AliExpress prices and ships to (US, GB, DE, FR, ES, IT, NL, AU, CA, BR, MX…). Drives shipping fee, delivery date, stock and some prices.
currencystringUSDThree-letter code for every price in the output (USD, EUR, GBP, AUD, CAD, BRL, MXN…). Price filters are read in this currency.
💷 Limits & cost
maxItemsinteger60Your hard cost ceiling. Unique products across all keywords and URLs. At the default (enrichment off): 60 = $0.15, 500 = $1.25, 1,000 = $2.50.
maxItemsPerQueryinteger0Cap per keyword so one broad term cannot use the whole budget. 0 = no per-keyword cap. 60 per page, so 120 = two pages.
maxPagesPerQueryinteger20How deep to walk each keyword. 20 pages ≈ 1,200 products. Maximum 200.
⚙️ Advanced
maxConcurrencyinteger4Parallel detail requests, 1–8. Each worker owns one proxy session. No effect with enrichment off (the default), and it does not get past the current block, which is not rate-based.
proxyConfigurationproxyApify datacenterThe measured rung for the search pages that carry your data, with an automatic capped RESIDENTIAL retry for a challenged page — see the ladder below.

Pricing

$0.0025 per product returned — $2.50 per 1,000 — charged on the product-scraped event. $0.002 per product enriched — charged on product-detail-enriched, and only for rows where AliExpress's product-detail API actually returned data. No monthly fee from this Actor.

Enrichment is off by default and the API it needs is blocked, so in practice you pay a flat $0.0025 a row today. If it reopens and you turn it on, a fully-detailed product is $0.0045.

RunProductsCost today (enrichment off)Ceiling if enrichment reopened
The shipped default60$0.15$0.27
Dropshipping sweep, 3 keywords × 120300$0.75$1.35
Supplier vetting, one keyword500$1.25$2.25
Full catalogue pull1,000$2.50$4.50
Catalogue sweep600$1.50$2.70
Daily keyword monitor60$0.15 / day$0.27 / day

The right-hand column is a ceiling that assumes every row enriched — it is not currently reachable. Plan against the middle column.

  • You are charged per row delivered. Rows shipped and rows billed are always the same number.
  • Duplicates never cost you twice. A product that appears under several keywords, or on more than one page, is de-duplicated on productId before billing, and the run asserts uniqueness across everything it pushed, stopping with a status message that names the failure if that assertion ever trips.
  • Filtered rows are free. Products removed by minPrice / maxPrice are filtered by AliExpress before they are ever fetched; products removed by excludeSponsored are dropped before billing. Neither appears on your invoice.
  • Enrichment only bills when it succeeded. The product-detail-enriched event is charged for the enriched rows among those actually shipped. A row that failed every session ships as a search-level row and is charged the product event only.
  • A budget cap stops the crawl, not just the push. The Actor reads your remaining charge headroom at startup and stops fetching at the affordable product count, so a capped run does not burn compute on rows it could never deliver. If the cap is hit mid-run it stops cleanly and says so — whole rows, never a half-billed dataset.

Why $0.0025 and not the $0.001 floor: a search page is not one request. It is fetched on a rotating proxy session with up to four attempts and an automatic residential retry, because AliExpress challenges the search path too — and what comes back is parsed down to the card's trace layer for the gallery, the leaf category, the ships-from country, the store name and the delivery window, which is the part other scrapers at this price do not give you.


Honest limits

  • The product-detail API is blocked, and that is the biggest limit here. Re-measured 2026-08-27: the first, unsigned, token-minting call returns HTTP 200 with ret: ["FAIL_SYS_USER_VALIDATE", "RGV587_ERROR::SM::…"] and sets x5secdata/x5sectag but no _m_h5_tk, so a signed call cannot even be built. 0/5 Apify datacenter, 0/4 Apify RESIDENTIAL, 0/3 from a clean home IP that had never contacted the host. A virgin IP failing on its first-ever call rules out exit-IP reputation, so this is a gate, not a rate limit — concurrency, waiting and proxy choice do not move it. asyncPCDetail, the msite endpoint and acs.aliexpress.us fail identically, so the gate is the acs gateway rather than one API. Consequence: fetchProductDetails ships off, and every enrichment-only column ships null with detailFetched: false. You are never charged the enrichment event for a row it did not enrich, so a blocked run costs exactly the product rate.
  • There is no SSR / SEO backdoor to fall back on. /i/, m., .us and a Googlebot user-agent all return the same ~77 KB client-rendered shell with window.runParams = {} and no PDP modules, no ld+json. This was checked before concluding the fields are unavailable.
  • What would fix it, and why it is not shipped. A real browser's product-page load still makes exactly this API call, so the endpoint is live — the difference is a cookie jar minted by executing AliExpress's own AWSC/baxia JS. Minting that in Chromium and reusing it for cheap HTTP calls is the open lead. It is unverified, so nothing in this Actor depends on it and nothing here promises it.
  • The search path can be challenged too. Measured 2026-08-27, a home IP was x5sec-walled on the search path after about six page fetches and was still walled 90 s later, while Apify RESIDENTIAL cleared the same queries first try. The Actor detects a challenged page (short body / punish markers) and retries up to four times on fresh sessions, with a capped RESIDENTIAL attempt last — limit 12 fetches per run, and only if your Apify plan includes the RESIDENTIAL group. If it does not, the run logs "RESIDENTIAL proxy unavailable — search fallback rung disabled" and continues on datacenter alone. On a long run, expect some pages to be lost; the log counts them.
  • Enrichment gives up early on purpose. If you switch it on and three pool cooldowns pass with zero successful detail fetches, the Actor stops trying and ships search-level rows for the rest of the run rather than burning your timeout (measured 2026-08-26: 5 cooldowns, 0 enriched rows, 309 s). The log prints "Detail enrichment abandoned after N cooldowns…" plus
    detailOk / detailFail
    , sessions minted and retired, and a per-error-code tally, so a site-side change — in either direction — is visible immediately.
  • No reviews, no review text, no Q&A. rating, reviewCount and sellerScores are the aggregate numbers; individual customer reviews are not collected.
  • No product description HTML. properties is AliExpress's structured specification table. The rich HTML/image description block is not parsed.
  • No order history, no price history, no sales estimates. ordersText/ordersCount are AliExpress's own bucketed lifetime counter ("10,000+ sold"), so ordersCount is a floor — verified never to overstate (216 cards checked against AliExpress's own internal exact counter: 0 overstatements). To get price or stock history, schedule the Actor and diff its runs — nothing historical is fetched.
  • Product URLs are not usable while the detail API is blocked, since there is no search card behind them. The run logs a warning telling you so and pointing you at searchQueries.
  • Some search-side columns are partial by nature. storeName is on 59% of cards, the three delivery fields on 62% (25–75% depending on the keyword), shipsFrom on 94%. These are the fill rates of AliExpress's own card data, not scrape failures, and a null there is not a bug.
  • A URL that is not an AliExpress product link is skipped with a warning. Only /item/<id>.html, /i/<id>.html, ?productId=<id> and bare numeric ids are recognised. Store URLs, category URLs and search URLs are not.
  • A search stops on the first unusable page. Four attempts on fresh sessions, and if none returns a parseable page the keyword ends with a warning rather than skipping ahead. Same if the page returns HTTP 200 but carries no _init_data_ blob (markup change or a soft block).
  • Pagination ceilings. 60 products per page, maxPagesPerQuery capped at 200, and a run-wide hard cap of 100,000 products. AliExpress's advertised result totals are far larger than what it will actually page through.
  • No login, so no logged-in pricing. Coupons tied to an account, app-only prices and welcome deals are invisible to this Actor by design.
  • robots.txt. https://www.aliexpress.com/robots.txt (fetched 2026-08-26) has no blanket disallow for User-agent: *; it disallows paths including /search/*, /api/*, /product/* and /productdetail/*, and explicitly allows /wholesale.html$ and /wholesale-page-*.html. The search path this Actor reads, /w/wholesale-<slug>.html, is named by neither rule — it is not disallowed. The product-detail API this Actor calls lives on a different host, https://acs.aliexpress.com/robots.txt, which reads, verbatim: User-agent: * / Disallow: /. This Actor reads only what a logged-out visitor's own browser loads and nothing behind authentication; whether and how you may use AliExpress content commercially is governed by AliExpress's Terms of Use, and is your responsibility.

How it works, and what it cost to make reliable

Public, logged-out, and no CAPTCHA is ever solved.

1. Search. https://www.aliexpress.com/w/wholesale-<slug>.html?g=y&SearchText=<q> with &page=N, &SortType=, &minPrice=, &maxPrice= appended as needed. The HTML carries window._dida_config_._init_data_ = { data: {…} } — an object literal with an unquoted key, so the Actor walks to the first { after data: and cuts a string-aware balanced JSON object rather than trusting a regex. Inside are 60 product cards per page: productId, title, sale and original price, discount %, star rating, the "10,000+ sold" string, listing date, selling-point tags and the ad flag — plus each card's second data layer, which is where this Actor differs from most: images[] (the gallery), trace.utLogMap.categoryId (a category path whose leaf equals the product-detail API's categoryId), trace.utLogMap.delivery_info (a "min,max,shipDate,etaDate" string), trace.pdpParams.pdp_cdi (URL-encoded JSON carrying the card SKU's shipFrom) and trace.custom.p4pExtendParam.store_name. Those five are parsed into images, categoryId, deliveryDaysMin/Max/deliveryDate, shipsFrom and storeName with no extra request. Seller identity, SKUs and stock genuinely do not exist at this level — verified by needle-sweeping 240 cards — which is what step 2 was for.

2. Detail (optional, and currently blocked — see below). The signed mtop API the product page itself calls: GET https://acs.aliexpress.com/h5/mtop.aliexpress.pdp.pc.query/1.0/, where sign = md5(token & timestamp & appKey & data) and the _m_h5_tk token is minted by the first (unsigned) call's Set-Cookie. It returns ~27 modules — SKU, PRICE, SHIPPING, SHOP_CARD_PC, PC_RATING, QUANTITY_PC, PRODUCT_PROP_PC, WISHLIST, images and more — which the Actor flattens into the row. Your shipToCountry and currency are passed into the call, not inferred from the exit IP.

The wall, and what changed. Alibaba's in-house "punish" / x5sec system guards the detail API. On 2026-08-22 it behaved as a per-exit-IP rate limit: a fresh session cleared 55–70% of the time and served ~8 rapid calls, which is why the Actor is built the way it is — every worker holds exactly one session (its own cookie jar, UA and proxy exit), retires it after 7 successes or on the first failure, retries the product on a fresh session, and the whole pool pauses together when it goes hot.

On 2026-08-27 that stopped being true. The gate now fires before a session can exist: the first, unsigned, token-minting call is refused and no _m_h5_tk is ever set, on a clean IP that had never contacted the host. The rotation machinery is still in the code — it costs nothing when enrichment is off, and it is the cheapest possible probe for the day the gate reopens — but it is no longer a design that can be tuned into working, and this README no longer pretends otherwise.

Transport ladder, measured 2026-08-27:

RungSearch page HTML (/w/wholesale-*.html)Product-detail mtop API
Apify datacenter (the default)clean on every sample query0/5FAIL_SYS_USER_VALIDATE
Apify RESIDENTIALcleared all sample queries first try; used as a capped fallback (≤12 fetches/run) when a datacenter page comes back challenged0/4 — same refusal
Clean home IP, never used against the hostwalled on the search path after ~6 fetches0/3 — same refusal on the first-ever call

The bottom-right cell is the one that matters: a virgin IP refused on its very first request is a gate, not a rate limit. The bottom-left cell is the operational one: the punish is not confined to the detail API, so search pages need the retry ladder too.

Other things built in because they bite in production:

  • No browser. Pure HTTP with got-scraping, so the Actor declares 512 MB and a 1-hour default timeout rather than the 2–4 GB a Chromium-based scraper needs.
  • A wall-clock budget. The Actor reads the run's own timeout, holds back a safety margin, and stops fetching in time to push what it has — a timeout never costs you the rows already collected.
  • A charge-headroom clamp. Remaining maxTotalChargeUsd is read at startup and the crawl stops at the affordable product count, so a capped buyer is not charged compute for rows that could never be delivered.
  • Crash nets. Any unforeseen throw — including the internal uniqueness assertion — is logged and turned into a clean SUCCEEDED exit carrying a status message that names the failure, rather than a FAILED run.
  • Retries on pushData itself — three attempts. If all three fail, the batch is dropped and its ids are rolled back so nothing is billed for rows that never landed; the rows themselves are lost for that run and the log says so. Re-run to collect them.

Duplicates

Within one results page AliExpress repeats nothing. Duplicates come from fan-out — the same product matching "wireless earbuds" and "bluetooth headphones", or appearing again on a later page after the ranking shifts mid-walk. The Actor keeps a run-wide set keyed on productId, de-duplicates before anything is charged, and then asserts uniqueness across everything it pushed at the end of the run — a duplicate leak stops the run with a status message naming the assertion rather than quietly billing you twice. Every run logs the exact number of duplicates it dropped.

Dedupe is per run. Across scheduled runs you will see the same products again; key on productId in your own store and treat repeat rows as updates.


When a run stops early

This Actor never ends FAILED. A duplicate leak trips an internal uniqueness assertion, which the crash net converts into a SUCCEEDED run whose status message reads "Stopped early — Uniqueness assertion failed: …". Check the status message, not the run status.

Every other early stop also ends SUCCEEDED with a status message that says exactly what happened:

  • 0 products → the message names the cause: AliExpress returned pages with no product cards (no results, or a soft block — re-run in a minute), search pages could not be fetched through the proxy after retries, or nothing matched your queries and URLs.
  • Run time budget"Stopped early to stay inside this run's time limit — N product(s) delivered (M with detail). Raise the run timeout or lower maxItems for the rest."
  • Charge cap"Stopped at this run's pay-per-event charge cap with N product(s) returned… Every row delivered was billed. Raise the run's max total charge to collect more."
  • No budget at all → if the run's max total charge cannot pay for even one product, the Actor exits immediately having fetched nothing and billed nothing, and says so.
  • Partial enrichment"N product(s) returned, M with seller/SKU/shipping detail (K could not be enriched and shipped as search-level rows)" — an informational status, not an error.
  • No input at all → a bare {} runs the documented sample search and says so in the status, so a misconfigured Task returns data instead of nothing.

Who buys this

Written for the full field set. While the product-detail API is blocked, the uses that depend on skus[], totalStock, reviewCount or the seller-identity columns are not currently served — they are marked ⚠️ below.

  • Dropshippers and product researcherssortBy: "orders" inside a price band, sponsored cards filtered out, then rank by ordersCount and rating. shipsFrom tells you in one column which winners can actually be delivered in under a week, and deliveryDaysMax puts a number on it.
  • ⚠️ Sourcing and procurement teamsskus[] with per-variant price and stock is a supplier quote sheet: minSkuPrice/maxSkuPrice for the real cost spread, totalStock and maxPerOrder for whether they can fill your order, storeOpenedAt + sellerPositiveRate + sellerScores for whether you want them to. Blocked today.
  • Price monitors — recipe 4 on a Schedule, diffing salePrice, discountPercent and deliveryDaysMax per productId. Cheap enough to run daily on hundreds of listings. (Stock monitoring needs totalStock and is ⚠️ blocked.)
  • Competitive-intelligence and market-research teams — a whole category at 60 rows per page with discount depth, orders, warehouse split and categoryId, in your currency and your shipping lane rather than a US-default one.
  • Brand-protection teams — search your own brand terms, then group by storeName (present on ~59% of cards) to see who is listing your products. Joining to store pages needs storeId and is ⚠️ blocked.
  • AI / RAG and analytics pipelines — clean JSON with a stable productId, ISO scrapedAt, keyword provenance on every row, de-duplicated, no HTML to clean, and a column set that does not move between runs.

FAQ

Does this need an AliExpress account, a login or an API key? No. It reads the same public search pages and the same product-detail endpoint a logged-out browser loads. No cookie of yours is ever used, and nothing behind a login is collected.

Does it solve CAPTCHAs? Never. When AliExpress challenges a session, that session is retired and the product is retried on a fresh one. No challenge is ever answered or bypassed.

Will every row have the seller and SKU fields? No — right now, no row will. Those come from AliExpress's product-detail API, which as of 2026-08-27 refuses every request this Actor can make (0/5 datacenter, 0/4 residential, 0/3 clean home IP — it rejects even the first unsigned call, so it is a gate rather than a rate limit). fetchProductDetails therefore ships off and those columns ship null with detailFetched: false. You are not charged the enrichment event for them. What you do get on every row, from the search page alone: title, prices, discount, rating, orders, tags, listing date, the image gallery, the leaf categoryId, shipsFrom, a delivery-days/date estimate, and storeName on about 59% of cards.

Should I switch the proxy to residential? Not to fix enrichment — residential is blocked on the detail API exactly like datacenter. It is used automatically, and only for search pages, when a datacenter page comes back challenged — capped at 12 fetches per run, and only if your Apify plan includes the RESIDENTIAL group. If it does not, the run logs "RESIDENTIAL proxy unavailable — search fallback rung disabled" and continues on datacenter alone. Making residential the primary is worth trying only if search pages keep failing in your region.

How many products can one keyword return? 60 per page, up to maxPagesPerQuery pages (default 20 ≈ 1,200; maximum 200). A search ends on its own when AliExpress runs out of results. Add keywords to go wider.

Can I get prices in my own currency and shipping to my own country? Yes — set shipToCountry and currency. Both are pinned in AliExpress's locale cookie on the search request (and passed into the detail-API call when enrichment is on), so the result should not depend on which proxy IP served the request. Note the delivery estimate on a search-level row is the one AliExpress renders on the card for that locale, not a checkout quote, and shippingFee itself is enrichment-only and therefore currently null. The Actor does not verify the currency that comes back — check currency on the rows if you are mixing regions.

Do I pay for duplicates or filtered-out products? No. Duplicates are dropped on productId before billing; price-band filtering happens on AliExpress's side before anything is fetched; sponsored rows removed by excludeSponsored are dropped before billing.

What does enrichment actually cost me? $0.002 per row where the detail API returned data, on top of the $0.0025 product row — $0.0045 for a fully-detailed product. It is off by default, and while the API is blocked it returns data for no rows, so you pay $0.0025 flat. Switching it on while it is blocked costs you nothing in charges; it only costs run time, and the Actor gives up after three cooldowns.

Can I scrape a whole store's catalogue? Not directly — this Actor takes keywords and product URLs, not store URLs. Search a brand or store name as a keyword, or collect productUrls and feed them in.

Does it get customer reviews? No. rating, reviewCount and sellerScores are the aggregates; individual review text is out of scope.

Will a run ever succeed with zero rows? It will end SUCCEEDED with a status message naming the cause, so a scheduled monitor on a quiet keyword is not flagged as broken.


This Actor reads public AliExpress search results and public product pages as a logged-out visitor. It does not log in, does not use any account, cookie or API key, does not solve or bypass any challenge, and collects nothing behind authentication.

https://www.aliexpress.com/robots.txt (fetched 2026-08-26) carries no blanket disallow for User-agent: *; it disallows a list of paths including /search/*, /api/*, /product/* and /productdetail/*, and explicitly allows /wholesale.html$ and /wholesale-page-*.html. The search path this Actor reads, /w/wholesale-<slug>.html, is named by neither rule — it is not disallowed. The product-detail endpoint used for enrichment is on a separate host, and https://acs.aliexpress.com/robots.txt reads, verbatim: User-agent: * / Disallow: /. AliExpress's Terms of Use govern how its content may be used.

Product listings, store names and seller identifiers are commercial business data, but rows can still contain names and locations. You are responsible for complying with AliExpress's terms and with how you use the data, including GDPR/CCPA where it applies and the rules on unsolicited contact.

AliExpress® and Alibaba® are trademarks of their owners. This Actor is not affiliated with, endorsed by, or connected to AliExpress or Alibaba Group.


Feedback

Found a missing field or want a new filter — a category filter, a shipsFrom filter, store-URL input? Open an issue on the Issues tab, and if the Actor earns it, a review on the Reviews tab helps other buyers find it.