Learn how to scrape AliExpress with a Python scraper that collects product data: title, price, variants, images, and reviews from a product page.
AliExpress pages render with JavaScript, so the most reliable source is structured JSON, not brittle HTML. Blocking and captchas are the main obstacle. If you do not want to maintain parsing logic, a scraping API is the shortcut. Plan for retries and careful request pacing from the first run.
An AliExpress product page is not one clean document. The initial HTML may arrive as a rendering shell, while JavaScript fills in the title, variant cost, images, shipping, ratings and category path. A plain request often returns only that shell. The most useful layer is a JSON object named runParams, embedded in a script tag. AliExpress also fetches later details through hidden internal XHR or API calls, especially customer reviews or refreshed availability. To find the source, open the page, press F12, open Network, filter by Fetch/XHR, and search for a visible price. The response containing that value is usually the best scraping target. For scraping, treat the embedded object as the primary source and the DOM as a fallback. This keeps product data stable when markup changes. If the value appears only after rendering, inspect script variables or network payloads instead of the DOM before parsing.
AliExpress is heavily dynamic. Web Runner 2049 (2020) notes Python Requests scripts do not execute JavaScript. By choosing embedded JSON for speed, you accept brittle field paths. Browser rendering avoids fragile markup, yet it is slower. An LLM can parse messy HTML, but it costs more at scale. The flip side of high stability is slower execution and heavier resource use. Choose the lowest layer that gives clean fields. Do not start with the heaviest option.
Approach | Speed | Stability | Anti-bot exposure | Best for |
|---|---|---|---|---|
Visible markup | Low | Low | High | Rare fallback only |
Embedded source data | High | Medium | Medium | First choice for product fields |
XHR capture | High | High | Medium | Reviews and dynamic prices |
Browser automation or scraping API | Low | High | Low | Paid API credits when block risk is high |
Use Python 3.8 or later. Install requests, BeautifulSoup, and Playwright, then build this Python scraper to scrape AliExpress stepwise: request, embedded JSON, browser XHR. Verify field paths against live pages because markup changes.
pip install requests beautifulsoup4 playwright
python -m playwright install chromium
Start each scrape with a request that imitates a real browser. The code sends headers, sets a timeout, and checks the status code.
import requests
URL = "https://www.aliexpress.com/item/PRODUCT_ID.html"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"Accept": "text/html,application/xhtml+xml,application/xml",
"Accept-Language": "en-US,en",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
}
response = requests.get(URL, headers=HEADERS, timeout=SECONDS)
if response.status_code == 200 and "captcha" not in response.text.lower():
print("OK")
elif "captcha" in response.text.lower():
print("Captcha")
elif response.status_code in (403, 429):
print("Blocked")
else:
print("Check response")
with open("page.html", "w", encoding="utf-8") as f:
f.write(response.text)
A 200 response is not enough. AliExpress can return a captcha shell or blank price fields while reporting success. A 403 or 429 response usually means blocking or throttling. Do not trust that status signal by itself. Search the returned HTML for a captcha marker before parsing anything. Save the raw page for later debugging. If the first request fails, log the request headers and retry deliberately once. For a later scrape, compare the saved page against expected product fields.
Locate the script that defines runParams or initial data, cut out that segment, and parse it with json.loads. Field paths change across page versions, so wrap every lookup in .get(). A missing key should return a default instead of crashing. Pull the title, category path, image URLs, and each SKU’s price into a flat product data dict. Parse the whole variant list, not only the headline amount shown to visitors. Reading a direct key can raise KeyError when the structure differs. Chained calls with defaults are safer. In Python, a regular expression or simple string split can isolate the embedded JSON before decoding. This keeps scrape runs stable and predictable for scheduled jobs.
import re
import json
html = open("page.html", encoding="utf-8").read()
pattern = r"window\.(?:runParams|__INIT_DATA)\s*=\s*({.*?});?\s*</script>"
match = re.search(pattern, html, re.S)
if not match:
raise SystemExit("Embedded JSON block not found")
payload = json.loads(match.group(1))
product_data = {
"title": payload.get("title", ""),
"category": payload.get("categoryPath", []),
"images": payload.get("imageList", []),
"variants": [
{
"sku": item.get("sku", ""),
"price": item.get("price", ""),
"image": item.get("image", ""),
}
for item in payload.get("skuList", [])
],
}
Save the raw decoded payload beside the cleaned fields so broken field paths can be repaired later.
When embedded data misses reviews, dynamic prices, or signed calls, use a browser to capture the XHR traffic. The page runs JavaScript and sends each API request itself. Your code can capture each response. That means the platform signs the API call for you. You avoid reverse-engineering signature parameters.
In DevTools, open the Network tab and locate the endpoint that returns the needed fields. Use it as a reference while scraping. Do not hardcode a guessed endpoint. This works during normal rendering, so the API response arrives as the page loads. The script below listens for every network reply, keeps JSON payloads, and saves source URLs and status codes. Headless browsers are slower and need more resources than a plain HTTP request.
from playwright.sync_api import sync_playwright
payloads = []
def handle_response(response):
content_type = response.headers.get("content-type", "")
if "application/json" not in content_type:
return
try:
payloads.append({
"url": response.url,
"status": response.status,
"body": response.text(),
})
except Exception:
pass
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.on("response", handle_response)
page.goto("PRODUCT_URL", wait_until="domcontentloaded")
page.wait_for_load_state("networkidle")
browser.close()
For each product, iterate every SKU combination. Store the variant’s price, stock state, and its image URL. Do not stop at the headline product data. Collect image URLs from the JSON image list, then download the largest versions separately. For customer reviews, use the separate paginated call or the iframe URL rather than the initial HTML page. Loop those requests with delays and keep only rating, text, date, and country. These entries may include personal details, so handle them carefully. When scraping region-specific listings, record the ship-to country, such as the US, with every saved product row for comparison.
Raw scraped fields need cleanup before analysis. AliExpress shows different items and costs by location, so pin one ship-to country and currency for every run. A US HTML page can return different pricing later if region settings drift. Convert price strings to numbers, keep the currency code, and mark each record as promo or base. Add a timestamp. Deduplicate by product ID, but save raw JSON data too. This preserves source data when parsing bugs appear. The example writes clean product data to disk. Do this before comparing items across regions.
import json, re
def clean_price(value):
text = re.sub(r"[^\d.]", "", str(value))
return float(text) if text else 0.0
record = {"product_id": PRODUCT_ID, "price": clean_price(RAW_PRICE),
"currency": CURRENCY_CODE, "timestamp": TIMESTAMP, "promo": IS_PROMO}
with open("items.json", "w", encoding="utf-8") as f:
json.dump([record], f, ensure_ascii=False, indent=2)
A common shortcut is to send fast page fetches through one shared IP. It feels efficient, but the 2025 study Balancing Security and Privacy notes datacentre proxies are cheaper yet easier to detect. Uniform timing and weak browser signals make the pattern obvious. Anti-bot measures then return 403 or 429, serve a captcha, or give a 200 status code with empty data. The scraper stops, the queue backs up, and you lose pricing history while rebuilding the run.
Check response bodies, not only codes, because challenge pages can arrive with success codes. Slow the traffic down. Add random delays, realistic headers, session reuse, and an automatic retry with backoff. Rotate IPs so one address never takes the whole collection load. A scraping API can absorb some failures, but it still needs limits. For IP-based blocks, mobile proxies on real 4G/5G carrier IPs, served through modems with real SIM cards, resemble ordinary mobile traffic. They support HTTP or SOCKS5 with username and password authentication, which fits the proxies parameter in Python requests. They reduce blocks, but do not replace careful traffic behaviour.
import random, time, requests
session = requests.Session()
proxies = {
"http": "http://USER:PASSWORD@HOST:PORT",
"https": "http://USER:PASSWORD@HOST:PORT",
}
url = "PRODUCT_URL"
for attempt in range(4):
response = session.get(url, proxies=proxies, timeout=30, headers={"User-Agent": "Mozilla/5.0", "Accept-Language": "en-US,en;q=0.9"})
if "captcha" in response.text.lower():
time.sleep(2 ** attempt + random.uniform(1, 4))
continue
if response.status_code == 200:
break
time.sleep(2 ** attempt + random.uniform(1, 4))
else:
response = None
At scale, treat AliExpress scraping as a pipeline. Put product URLs in a queue, limit workers, and set per-domain rate limits. Log failures, save raw responses, and track success rate so one failed request does not stop the run.
For AliExpress price monitoring, schedule recurring jobs with cron, a task scheduler, or a workflow tool. A web scraping API can take over rendering, retries, and block handling, billing in credits. The trade-off is less control and ongoing cost. Keep the API key outside source code. Test the scraper on a small list before expanding. A stable scraper also needs alerts when failures rise.
AliExpress web scraping of public pages may be treated differently from violating platform terms or collecting personal data. Check the terms and robots.txt, respect rate limits, avoid personal details in customer reviews, and get legal advice before commercial use. This is not legal advice.
A scraping API can handle rendering, retries, and some anti-bot measures, but it cannot guarantee that no request will hit a block. Treat it as lower maintenance, not immunity. Keep delays and handle captcha pages or empty data.
Most product pages expose image URLs in embedded JSON, not clean HTML. Parse that JSON, collect the largest available URL for each variant, then download files separately. If you scrape images for a store, some sellers say reusing them without permission raises copyright risk.
Your scraper may receive a JavaScript-rendered shell, a captcha page, or a region-limited response on AliExpress. Check the status and raw data, look for challenge markers, retry with delays, and parse embedded JSON instead of relying on visible markup alone. Save raw payloads for debugging.
For reliable AliExpress scraping, start with embedded JSON, then fall back to browser-captured XHR. Validate prices and design for blocks. Before scaling, scrape a few product pages and check the scraper’s data.