AliExpress product pages carry a block of JSON-LD structured data that holds the product name and its current offer: price, currency and stock status. Reading that block is more stable than parsing the page's HTML, and it replaces the older approach of extracting window.runParams, which is now empty on product pages. The samples below fetch the page through the Bright Data Web Unlocker and return the product as JSON.
Updated October 2026: we loaded a live AliExpress product page on 30 October 2026.
window.runParamswas an empty object, so code that relied on it returned nothing. The JSON-LDProductblock was present, and the samples below were run against that page.
Prerequisites
export PROXY_URL="http://brd-customer-<id>-zone-<unblocker_zone>:<password>@brd.superproxy.io:22225"
New to Bright Data? Get started →
Products are identified by their numeric item ID from the URL: https://www.aliexpress.com/item/<id>.html.
What the samples return
| Field | Where it comes from | Example |
|---|---|---|
name | name in the JSON-LD Product | LED Desk Light with Adjustable Clip… |
price | offers.price | 378.98 |
currency | offers.priceCurrency | PHP |
availability | offers.availability | https://schema.org/InStock |
The example values come from the page we checked. The currency was Philippine pesos because the request came from the Philippines: AliExpress prices pages for the visitor's region, so set your unblocker's exit country to the market you want to track.
PHP
<?php
// Run: php aliexpress.php 1005013406591280
$proxy = getenv('PROXY_URL');
$itemId = $argv[1] ?? '1005013406591280';
$ch = curl_init("https://www.aliexpress.com/item/$itemId.html");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_PROXY => $proxy,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => ['Accept-Language: en-US,en;q=0.9'],
]);
$html = curl_exec($ch);
curl_close($ch);
// Find the JSON-LD block whose @type is Product (it may be wrapped in an array)
$product = [];
if (preg_match_all('#<script type="application/ld\+json"[^>]*>(.*?)</script>#s', $html, $m)) {
foreach ($m[1] as $block) {
$data = json_decode($block, true);
foreach ((isset($data[0]) ? $data : [$data]) as $item) {
if (($item['@type'] ?? null) === 'Product') { $product = $item; break 2; }
}
}
}
$offer = $product['offers'] ?? [];
echo json_encode([
'itemId' => $itemId,
'name' => $product['name'] ?? null,
'price' => $offer['price'] ?? null,
'currency' => $offer['priceCurrency'] ?? null,
'availability' => $offer['availability'] ?? null,
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), PHP_EOL;
Node.js
// aliexpress.mjs — node aliexpress.mjs 1005013406591280
// Install: npm i axios https-proxy-agent cheerio
import axios from 'axios';
import { HttpsProxyAgent } from 'https-proxy-agent';
import * as cheerio from 'cheerio';
const agent = new HttpsProxyAgent(process.env.PROXY_URL);
const itemId = process.argv[2] ?? '1005013406591280';
const { data: html } = await axios.get(`https://www.aliexpress.com/item/${itemId}.html`, {
httpsAgent: agent, proxy: false, timeout: 60_000,
headers: { 'Accept-Language': 'en-US,en;q=0.9' },
});
const $ = cheerio.load(html);
let product = {};
$('script[type="application/ld+json"]').each((_, el) => {
try {
const data = JSON.parse($(el).text());
const found = [].concat(data).find((item) => item['@type'] === 'Product');
if (found) product = found;
} catch { /* skip malformed blocks */ }
});
const offer = product.offers ?? {};
console.log(JSON.stringify({
itemId,
name: product.name ?? null,
price: offer.price ?? null,
currency: offer.priceCurrency ?? null,
availability: offer.availability ?? null,
}, null, 2));
Rust
// Cargo.toml:
// reqwest = { version = "0.12", features = ["blocking"] }
// scraper = "0.20"
// serde_json = "1"
use scraper::{Html, Selector};
use serde_json::{json, Value};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let proxy = std::env::var("PROXY_URL")?;
let item_id = std::env::args().nth(1).unwrap_or_else(|| "1005013406591280".into());
let client = reqwest::blocking::Client::builder()
.proxy(reqwest::Proxy::all(&proxy)?)
.danger_accept_invalid_certs(true)
.timeout(std::time::Duration::from_secs(60))
.build()?;
let html = client
.get(format!("https://www.aliexpress.com/item/{item_id}.html"))
.header("Accept-Language", "en-US,en;q=0.9")
.send()?
.text()?;
let doc = Html::parse_document(&html);
let sel = Selector::parse(r#"script[type="application/ld+json"]"#).unwrap();
// Find the JSON-LD object whose @type is Product (it may be wrapped in an array)
let mut product = Value::Null;
for el in doc.select(&sel) {
let Ok(data) = serde_json::from_str::<Value>(&el.text().collect::<String>()) else { continue };
let items = match data { Value::Array(a) => a, other => vec![other] };
if let Some(p) = items.into_iter().find(|i| i["@type"] == "Product") {
product = p;
break;
}
}
let offer = &product["offers"];
println!("{}", serde_json::to_string_pretty(&json!({
"itemId": item_id,
"name": product["name"],
"price": offer["price"],
"currency": offer["priceCurrency"],
"availability": offer["availability"],
}))?);
Ok(())
}
When the fields come back empty
- A block or challenge page. If the HTML has no JSON-LD at all, check what came back: a verification page means the request was flagged. Route it through the unblocker, slow down, and see how to avoid getting blocked.
- A different product type. The samples look for
@typeequal toProduct. If AliExpress changes the structure, print every JSON-LD block and look for the object that holdsoffers. - A redirect. Removed listings redirect away from the item page. Store the final URL with each result so a disappeared product shows up as such instead of as an empty row.
From scraper to tracker
- Pick the market first. Fix the unblocker's exit country, because the price and currency follow the visitor's region.
- Store each run. Save
{itemId, price, currency, availability, timestamp}to a database or CSV. - Compare like with like. Only compare prices in the same currency, and alert when a price drops below your threshold or stock changes.
- Keep the schedule modest. Daily runs are enough for most dropshipping research and keep the load on AliExpress low.
The Amazon tracker uses the same store-and-compare pattern, and the e-commerce scraping overview covers the other marketplaces.
Staying within the rules
Collect public product data only, keep request rates modest, and read AliExpress's terms before you build anything commercial on the data. Don't scrape account pages, buyer details or anything behind a login.
Get started with Bright Data →
FAQ
Why does my AliExpress scraper return null for the title and price?
If it reads window.runParams, that object is now empty on product pages. When we checked in October 2026, the product data was in a JSON-LD script block instead. Read the Product object from script[type="application/ld+json"], as the samples on this page do.
Why is the AliExpress price in the wrong currency?
AliExpress prices pages for the visitor's region, and the JSON-LD carries the currency it used, such as USD or PHP. Fix the exit country of your proxy or unblocker to the market you want, and store priceCurrency with every price.
Can I get variants, ratings or order counts this way?
Not from the JSON-LD, which only holds the name, images, description and one offer. Variant prices, ratings and order counts need the rendered page or a different data source. Keep the tracker to the fields the JSON-LD actually provides.