AliExpress Products Scraper — Prices, Sellers, SKUs & Stock
Pricing
from $2.50 / 1,000 per product returneds
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
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_VALIDATEeven 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.fetchProductDetailstherefore ships OFF, and the columns it would fill (skus[],totalStock,reviewCount,storeId,sellerId,sellerPositiveRate,shippingFee,properties, …) ship asnullwithdetailFetched: 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 card — images[]
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):
| Field | Fill | Where it comes from |
|---|---|---|
images[] — the gallery, 5–8 per card | 99.6% | images[] on the card |
categoryId — the leaf category id | 100% | trace.utLogMap.categoryId, last segment |
shipsFrom — warehouse country, ISO-2 | 94% | trace.pdpParams.pdp_cdi (URL-encoded JSON) |
storeName | 59% | trace.custom.p4pExtendParam.store_name |
deliveryDaysMin / deliveryDaysMax / deliveryDate | 62% (25–75% by keyword) | trace.utLogMap.delivery_info |
Two of those are worth calling out:
shipsFromseparates 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:CN173,US52. Note it is the ship-from of that card's SKU; a multi-warehouse listing can offer others.categoryIdis the leaf id, cross-checked two ways: the last segment of theutLogMappath equalstrace.exposure.postCategoryIdon 240/240 cards, and on a product where we also held a product-detail payload the leaf (63705) matched the detail API'scategoryIdexactly.
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-enrichedevent 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, markeddetailFetched: 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,maxPriceandsortByare 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 onsalePricein your own pipeline if the band is hard.excludeSponsoreddrops 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
- 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
fetchProductDetailsships off and roughly half the schema shipsnull. 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 needskus[],totalStock,reviewCountor the seller-identity columns, this Actor cannot currently give them to you — do not buy rows expecting them. - 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_VALIDATEand never sets an_m_h5_tkcookie, from a clean IP that had never contacted the host. Measured 0/5 Apify datacenter, 0/4 Apify RESIDENTIAL, 0/3 clean home IP. LowermaxConcurrency, longer waits, different proxies and re-running later have all been tried and none of them changes it. - 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.)
ordersCountis a floor, not an exact number. AliExpress prints buckets — "10,000+ sold" — soordersTextis the literal string andordersCountis the number parsed out of it (10000). Verified over 216 cards where AliExpress's own internal exact counter was also present:ordersCountwas never higher than the true count (104 exactly equal, 112 lower). Read it as "at least this many".sellerSalesTextis the same bucketed string AliExpress prints on the product page's rating module — it duplicatesordersTexton search-level rows.- Product URLs are not usable right now. A URL row has no search card behind it, so everything
except
productIdandproductUrlwould have to come from the blocked detail API. UsesearchQueriesinstead; the run logs a warning if you paste URLs anyway. - Prices are what a logged-out visitor in your
shipToCountrysees. 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: truemeans shipping is free only on a qualifying Choice basket, not on this item alone — read it as a condition, not a fee. - 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.
| Group | Fields |
|---|---|
| Identity | productId, productUrl, title, imageUrl, images[], categoryId, listedAt |
| Price | currency, salePrice, originalPrice, discountPercent, minSkuPrice, maxSkuPrice |
| Demand | rating, reviewCount, ordersText, ordersCount, sellerSalesText, wishlistCount |
| Store & seller | storeName, storeId, storeUrl, storeLogo, sellerId, sellerCountry, storeOpenedAt, storeYearsOpen, topRatedSeller, sellerPositiveRate, sellerLevel, sellerFeedbackCount, sellerScores, storeFollowers |
| SKUs & stock | skus[] (skuId, attributes, salePrice, originalPrice, stock, available, maxPerOrder), skuCount, totalStock, inStock |
| Shipping | shippingFee, shippingFeeText, freeShipping, freeShippingWithChoice, deliveryDate, deliveryDaysMin, deliveryDaysMax, shipsFrom, shippingProvider |
| Listing extras | sellingPoints[], isSponsored, properties |
| Provenance | detailFetched, 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.
| Category | Fields | Filled when |
|---|---|---|
| Always present | productId, productUrl, currency, shipToCountry, detailFetched, scrapedAt, sellingPoints[], isSponsored | Every row. currency falls back to your currency input; sellingPoints is [] and isSponsored is false rather than null. |
| From the search card | title, imageUrl, salePrice, originalPrice, discountPercent, rating, ordersText, ordersCount, listedAt, searchQuery, searchPage, searchPosition | Every 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 layer | images[] (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-dependent | the whole Store & seller group except storeName, the whole SKUs & stock group, shippingFee, shippingFeeText, freeShipping, freeShippingWithChoice, shippingProvider, plus reviewCount, wishlistCount and properties | Only 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 enriched | sellerScores, 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:
salePricevsminSkuPrice.salePriceis the price of the SKU AliExpress preselects;minSkuPrice/maxSkuPriceare 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.discountPercentcomes from AliExpress's own discount rate where the detail API gives one, and is otherwise derived fromsalePricevsoriginalPrice.originalPriceis AliExpress's reference price, which is marketing, not a historical sale price.freeShippingvsfreeShippingWithChoice. The first means this shipping option costs 0 for yourshipToCountry; the second means it is free only inside a qualifying Choice basket.storeIdvssellerId.storeIdis the number in the store URL (joins to store pages);sellerIdis the seller account behind it. One seller can run more than one store.storeYearsOpenis AliExpress's ownsellerInfo.openedYearvalue, passed through unmodified — the Actor does not derive it fromstoreOpenedAt. Read it alongsidestoreOpenedAtrather than trusting it as a year count.shipsFromis the warehouse country code (CN,US,ES…), whilesellerCountryis 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.sellerSalesTextis AliExpress's bucketed "sold" line. On a search-level row it is filled from the card and is therefore the same string asordersText— it is only a distinct value when the product-detail API's rating module answers. (An earliersellerSimilarItemOrderscolumn was removed on 2026-08-27: AliExpress dropped the "…from this seller:detailFetched: falsemeans 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
| Field | Type | Default | What it does |
|---|---|---|---|
| 🎯 What to scrape | |||
searchQueries | string 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. |
productUrls | string 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. |
sortBy | select | default | default (best match) · orders (best selling) · price_asc · price_desc. Keyword searches only. |
minPrice / maxPrice | number | empty | Price band in your currency, applied by AliExpress on the search page — filtered-out products are never fetched or billed. |
excludeSponsored | boolean | false | Drop paid "Ad" cards before they are fetched or billed. Off = keep them with isSponsored: true. |
| 📦 Output shape | |||
fetchProductDetails | boolean | false | Try 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. |
shipToCountry | string | US | Two-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. |
currency | string | USD | Three-letter code for every price in the output (USD, EUR, GBP, AUD, CAD, BRL, MXN…). Price filters are read in this currency. |
| 💷 Limits & cost | |||
maxItems | integer | 60 | Your 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. |
maxItemsPerQuery | integer | 0 | Cap per keyword so one broad term cannot use the whole budget. 0 = no per-keyword cap. 60 per page, so 120 = two pages. |
maxPagesPerQuery | integer | 20 | How deep to walk each keyword. 20 pages ≈ 1,200 products. Maximum 200. |
| ⚙️ Advanced | |||
maxConcurrency | integer | 4 | Parallel 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. |
proxyConfiguration | proxy | Apify datacenter | The 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.
| Run | Products | Cost today (enrichment off) | Ceiling if enrichment reopened |
|---|---|---|---|
| The shipped default | 60 | $0.15 | $0.27 |
| Dropshipping sweep, 3 keywords × 120 | 300 | $0.75 | $1.35 |
| Supplier vetting, one keyword | 500 | $1.25 | $2.25 |
| Full catalogue pull | 1,000 | $2.50 | $4.50 |
| Catalogue sweep | 600 | $1.50 | $2.70 |
| Daily keyword monitor | 60 | $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
productIdbefore 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/maxPriceare filtered by AliExpress before they are ever fetched; products removed byexcludeSponsoredare dropped before billing. Neither appears on your invoice. - Enrichment only bills when it succeeded. The
product-detail-enrichedevent 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 setsx5secdata/x5sectagbut 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 andacs.aliexpress.usfail identically, so the gate is theacsgateway rather than one API. Consequence:fetchProductDetailsships off, and every enrichment-only column shipsnullwithdetailFetched: 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.,.usand a Googlebot user-agent all return the same ~77 KB client-rendered shell withwindow.runParams = {}and no PDP modules, nold+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 , sessions minted and retired, and a per-error-code tally, so a site-side change — in either direction — is visible immediately.detailOk / detailFail
- No reviews, no review text, no Q&A.
rating,reviewCountandsellerScoresare the aggregate numbers; individual customer reviews are not collected. - No product description HTML.
propertiesis AliExpress's structured specification table. The rich HTML/image description block is not parsed. - No order history, no price history, no sales estimates.
ordersText/ordersCountare AliExpress's own bucketed lifetime counter ("10,000+ sold"), soordersCountis 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.
storeNameis on 59% of cards, the three delivery fields on 62% (25–75% depending on the keyword),shipsFromon 94%. These are the fill rates of AliExpress's own card data, not scrape failures, and anullthere 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,
maxPagesPerQuerycapped 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 forUser-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:
| Rung | Search page HTML (/w/wholesale-*.html) | Product-detail mtop API |
|---|---|---|
| Apify datacenter (the default) | clean on every sample query | 0/5 — FAIL_SYS_USER_VALIDATE |
| Apify RESIDENTIAL | cleared all sample queries first try; used as a capped fallback (≤12 fetches/run) when a datacenter page comes back challenged | 0/4 — same refusal |
| Clean home IP, never used against the host | walled on the search path after ~6 fetches | 0/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
maxTotalChargeUsdis 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
pushDataitself — 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,reviewCountor the seller-identity columns are not currently served — they are marked ⚠️ below.
- Dropshippers and product researchers —
sortBy: "orders"inside a price band, sponsored cards filtered out, then rank byordersCountandrating.shipsFromtells you in one column which winners can actually be delivered in under a week, anddeliveryDaysMaxputs a number on it. - ⚠️ Sourcing and procurement teams —
skus[]with per-variant price and stock is a supplier quote sheet:minSkuPrice/maxSkuPricefor the real cost spread,totalStockandmaxPerOrderfor whether they can fill your order,storeOpenedAt+sellerPositiveRate+sellerScoresfor whether you want them to. Blocked today. - Price monitors — recipe 4 on a Schedule, diffing
salePrice,discountPercentanddeliveryDaysMaxperproductId. Cheap enough to run daily on hundreds of listings. (Stock monitoring needstotalStockand 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 needsstoreIdand is ⚠️ blocked. - AI / RAG and analytics pipelines — clean JSON with a stable
productId, ISOscrapedAt, 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.
Legal & fair use
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.