By the end of this tutorial, you will have a short Python script that turns AliExpress search results into structured JSON: one row per product with its id, title, canonical product URL, price, currency, star rating, units sold, image links and the shipping tag the card shows.
We use the Apify Actor AliExpress Scraper (reventadata/aliexpress-scraper). It is for anyone who tracks prices on AliExpress, sizes up a product niche by units sold, or compares listings by rating, and would rather read rows than scroll result cards.
⏱️ Time: setting this up takes a few minutes: an account, a token and one script.
You will learn how to configure the input, run the Actor from Python, read every field it returns, and avoid the filter traps that make a run come back empty.
Prerequisites
- A free Apify account (sign up for free).
- Your Apify API token, found at console.apify.com/settings/integrations.
-
Python 3.11 or newer, which
apify-clientrequires.
Install the client:
pip install "apify-client~=3.0"
No scraping code is needed: the Actor fetches the search pages, reads the cards and saves the rows. You send an input and read the results.
What you'll achieve
You will send one or more search queries (for example phone case or mechanical keyboard) and receive one clean row per product card on the AliExpress search results. The Actor reads the search result cards only; it does not open product pages one by one.
That keeps each row to what a card shows: title, price, rating, the sold count, images and a shipping tag. There is no seller profile, description or variant table.
Example use case: you want the best-rated phone cases with real sales behind them. Set minRating to 4.5 and minSold to 100, and the Actor keeps only cards that clear both bars.
Step 1: Access AliExpress Scraper
Open the AliExpress Scraper page on Apify Store. In Apify Console, an Actor opens on its Input tab, where you can fill the same fields this tutorial sets from code.
We will run it from Python, so keep your API token at hand and export it:
export APIFY_API_TOKEN="<your token>"
Step 2: Configure your queries and the caps
The one required field is searchQueries: a list of search terms. For each query the Actor builds an AliExpress search URL and walks its pages. For the prefill query phone case, page 1 is:
https://www.aliexpress.com/w/wholesale-phone-case.html?page=1&SearchText=phone+case&shipTo=US¤cy=USD
Here is the script. Its input is the Actor's own prefill: one query, 5 rows, one page.
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_API_TOKEN"])
run_input = {
"searchQueries": ["phone case"],
"maxItems": 5,
"maxPages": 1,
"country": "US",
"currency": "USD",
"proxyConfiguration": {
"useApifyProxy": True,
"apifyProxyGroups": ["UNBLOCKER"],
},
}
run = client.actor("reventadata/aliexpress-scraper").call(run_input=run_input)
if run is None or run.status != "SUCCEEDED":
raise SystemExit(f"Run did not succeed (status: {run.status if run else 'unknown'})")
for item in client.dataset(run.default_dataset_id).iterate_items():
print(item["id"], "|", item["title"][:60], "|", item["price"], item["currency"], "|", item["rating"], "|", item["soldCount"])
maxItems is the total number of rows to save across all queries. maxPages caps the pages walked per query. Without them, the defaults are 60 and 5.
Step 3: Add filters
| Parameter | Type | Default | What it does |
|---|---|---|---|
country |
string | US |
Sent to AliExpress as shipTo. |
currency |
string | USD |
Sent to AliExpress as currency. |
minPrice, maxPrice
|
number | none | Price band. A card with no readable price is dropped when either is set. |
minRating |
number | none | Minimum star rating, 0 to 5. Unrated cards are dropped when set. |
minSold |
integer | none | Minimum units sold. Cards without a sold count are dropped when set. |
proxyConfiguration |
object | Unblocker | Apify Unblocker proxy by default. |
Three details worth knowing:
-
The filters run on the cards the Actor reads, after each page is fetched. A tight
minSoldcan leave a page with few kept rows, so raisemaxPageswhen you filter hard. - Every word of a query must appear in the title, ignoring case and accents. That is how the Actor drops the unrelated recommendation cards AliExpress shows when nothing matches a query.
- Accessory and damaged listings are dropped. Titles with words like case, cover, cable, strap or holder are treated as accessories, unless your query itself contains case, cover, protector, cable, strap or tip. Titles admitting damage (broken, faulty, cracked, for parts, not working) are dropped too, but a negation such as "anti scratch" keeps the row.
Step 4: Run the Actor
Run the script with python aliexpress.py. .call() starts the Actor on Apify and waits until it finishes. Behind the scenes the Actor requests each search page (through Apify's Unblocker proxy, unless you change proxyConfiguration), reads the result cards, applies your filters and saves the rows. A product that shows up under two of your queries is saved once.
A query stops at the first page with no cards, or when you reach maxPages. Rows stop being saved at maxItems: the current query may still read its remaining pages, and later queries are skipped.
⚠️ Tip:
.call()blocks until the run ends. To fire and forget, use a webhook instead (see the pro tips below).
Step 5: View and download results
The script prints one line per row. In Apify Console, results appear in the run's Output tab, and Export downloads them. Datasets export as JSON, CSV, XML, Excel, HTML table, RSS or JSONL.
Here is a sample row, a sample from a capture dated 2026-10-16: one search card saved from the Spanish storefront in a browser and parsed with the Actor's own row builder. It is not output of the prefill input above, which is why the price is in euros. matchedKeyword and scrapedAt are left out:
{
"id": "1005010497678198",
"title": "Auriculares Inalámbricos Bluetooth 5.3 HiFi Estéreo Resistente al Sudor Cascos Calgante Deportivo TEISCHE SW-08",
"url": "https://www.aliexpress.com/item/1005010497678198.html",
"price": 7.71,
"originalPrice": null,
"discountPercent": null,
"currency": "EUR",
"rating": 4.9,
"soldCount": 38,
"soldText": "38 vendidos",
"thumbnailUrl": "https://ae-pic-a1.aliexpress-media.com/kf/E8a248d80dbf340dfa7bb04ad6b8d07a0h.jpg_480x480q75.jpg_.avif",
"galleryImages": [
"https://ae-pic-a1.aliexpress-media.com/kf/E8a248d80dbf340dfa7bb04ad6b8d07a0h.jpg_480x480q75.jpg_.avif",
"https://ae-pic-a1.aliexpress-media.com/kf/Eb36c445f517d42efa7b4f1b916048f2ef.jpg_480x480q75.jpg_.avif",
"https://ae-pic-a1.aliexpress-media.com/kf/E95a75f15f62543ccbbd8d0aba4ee4293s.jpg_480x480q75.jpg_.avif",
"https://ae-pic-a1.aliexpress-media.com/kf/E34099cc5d0ee42f59a3a636328810addc.jpg_480x480q75.jpg_.avif",
"https://ae-pic-a1.aliexpress-media.com/kf/E8c10f199c77049a0a5cb4fa099b304efF.jpg_480x480q75.jpg_.avif"
],
"shippingDisplay": "Envío gratis a partir de 10,9€",
"isFreeShipping": false
}
Understanding your results
-
idis the AliExpress product id, andurlis rebuilt from it ashttps://www.aliexpress.com/item/<id>.html, with the long query string of the card link dropped. -
priceis the price shown on the card, as a number.currencyis read from the card when it shows a euro, pound or dollar sign or code, else it is the currency you asked for. -
originalPriceanddiscountPercentare null in every row of the current version, so do not build a discount report on them yet. -
soldCountis the digits ofsoldText: "38 vendidos" becomes 38, and a text like "1,000+ sold" becomes 1000, a floor rather than an exact count. -
isFreeShippingis true only when the tag says plain "Free shipping" (or "Envío gratis"). A conditional tag, like the "free shipping from 10,9€" one above, stays inshippingDisplayand leaves the flag false. -
galleryImagesholds up to 5 image links from the card. -
matchedKeywordis the query that kept the row;scrapedAtis when it was saved.
Pro tips and advanced usage
-
Write queries in the words of the titles. The sample card's title is Spanish, so
wireless earbudswould not keep it, whileauriculares inalambricoswould: every query word must be in the title. -
Use
minSoldto skip untested listings. Cards without a sold count are dropped when it is set, so you only get products with sales on display. -
Put several queries in one run.
searchQueriestakes a list, and a product found by two of them is saved once. -
Hook it to your pipeline. An Apify webhook fires when a run finishes; its
resourceis "the response of the Get Actor run API endpoint" (docs.apify.com/integrations/webhooks/actions), which carriesdefaultDatasetId. -
Check
currencyin your own rows. The Actor sendscountryandcurrencyto AliExpress, and it takes the row'scurrencyfrom the card when the card shows one, so read it from the rows rather than assuming it.
For current pricing and details, see the Store page.
Troubleshooting common issues
-
"0 results for queries ...". A blocked page or a page without cards ends the run FAILED; when pages loaded but nothing passed, it ends SUCCEEDED with an empty dataset. Usually a query word is missing from the titles, or
minRating,minSoldor the price band dropped every card. Loosen one at a time. -
Fewer rows than
maxItems. The filters dropped cards andmaxPagesran out first. RaisemaxPages. - Nothing happens and the log says "No searchQueries provided in input." The query list is empty; add at least one query.
-
An input rejected at start.
maxItemsandmaxPagesmust be at least 1, the price and sold filters 0 or more, andminRatingbetween 0 and 5; an input outside the schema is refused before the run starts.
The run's log shows each page fetched and how many cards were parsed and kept on it.
Next steps
Save your input as an Apify task and add a schedule: schedules use cron expressions, so a daily price check is one line (docs.apify.com/actors/running/schedules). The Apify Python client docs cover the rest of the API. Start with the prefill input above and grow from there.
What are you planning to scrape on AliExpress? Drop a comment! 💬
Top comments (0)