Amazon Scraper Guide: Product Tracking Code Samples

Track Amazon prices and availability by ASIN with PHP, Node.js and Rust code, what each selector returns, and fixes for empty fields and localised pages.

Updated 4 min read

Tracking Amazon product prices and availability is one of the most popular e-commerce scraping use cases — for repricing, competitor monitoring, and deal alerts. Amazon's anti-bot systems make raw requests unreliable, so the samples below route requests through the Bright Data Web Unlocker (which handles CAPTCHAs, fingerprinting, and IP rotation) and then parse the product page for title, price, availability, and rating.

Prerequisites

Set a PROXY_URL pointing at your Web Unlocker zone:

export PROXY_URL="http://brd-customer-<id>-zone-<unblocker_zone>:<password>@brd.superproxy.io:22225"

New to Bright Data? Get started →

We identify products by their ASIN (the 10-character ID in every Amazon URL, e.g. B08LMTQ7ZF).

PHP

<?php
// Run: php amazon.php B08LMTQ7ZF
$proxy = getenv('PROXY_URL');
$asin  = $argv[1] ?? 'B08LMTQ7ZF';

$ch = curl_init("https://www.amazon.com/dp/$asin");
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);

$doc = new DOMDocument();
@$doc->loadHTML($html);
$xp = new DOMXPath($doc);

$text = function (string $q) use ($xp): ?string {
    $node = $xp->query($q)->item(0);
    return $node ? trim($node->textContent) : null;
};

$product = [
    'asin'         => $asin,
    'title'        => $text('//*[@id="productTitle"]'),
    'price'        => $text('(//span[@class="a-price"]//span[@class="a-offscreen"])[1]'),
    'availability' => $text('//*[@id="availability"]//span'),
    'rating'       => $text('//*[@id="acrPopover"]//span[@class="a-icon-alt"]'),
];

echo json_encode($product, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;

Node.js

// amazon.mjs — node amazon.mjs B08LMTQ7ZF
// 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 asin = process.argv[2] ?? 'B08LMTQ7ZF';

const { data: html } = await axios.get(`https://www.amazon.com/dp/${asin}`, {
  httpsAgent: agent,
  proxy: false,
  timeout: 60_000,
  headers: { 'Accept-Language': 'en-US,en;q=0.9' },
});

const $ = cheerio.load(html);
const product = {
  asin,
  title: $('#productTitle').text().trim(),
  price: $('span.a-price span.a-offscreen').first().text().trim(),
  availability: $('#availability span').first().text().trim(),
  rating: $('#acrPopover span.a-icon-alt').first().text().trim(),
};

console.log(JSON.stringify(product, null, 2));

Rust

// Cargo.toml:
//   reqwest = { version = "0.12", features = ["blocking"] }
//   scraper = "0.20"
//   serde_json = "1"
use scraper::{Html, Selector};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let asin = std::env::args().nth(1).unwrap_or_else(|| "B08LMTQ7ZF".into());

    let client = reqwest::blocking::Client::builder()
        .proxy(reqwest::Proxy::all(std::env::var("PROXY_URL")?)?)
        .danger_accept_invalid_certs(true)
        .build()?;

    let html = client
        .get(format!("https://www.amazon.com/dp/{asin}"))
        .header("Accept-Language", "en-US,en;q=0.9")
        .send()?
        .text()?;

    let doc = Html::parse_document(&html);
    let pick = |sel: &str| {
        Selector::parse(sel)
            .ok()
            .and_then(|s| doc.select(&s).next())
            .map(|el| el.text().collect::<String>().trim().to_string())
    };

    let product = serde_json::json!({
        "asin": asin,
        "title": pick("#productTitle"),
        "price": pick("span.a-price span.a-offscreen"),
        "availability": pick("#availability span"),
        "rating": pick("#acrPopover span.a-icon-alt"),
    });

    println!("{}", serde_json::to_string_pretty(&product)?);
    Ok(())
}

What the samples return

FieldSelectorNotes
title#productTitleThe product name as shown on the page
pricefirst span.a-price span.a-offscreenThe first price on the page, usually the buy-box price
availability#availability spanText such as "In Stock" or a delivery estimate
rating#acrPopover span.a-icon-altText such as "4.5 out of 5 stars"

Amazon's page varies by category, by seller and by the delivery location it detects, so treat every field as optional.

When the fields come back empty

  • The page is localised for another country. Amazon shows a different layout, and sometimes no buy box, when it thinks you're outside the store's market. When we requested a product page from the Philippines in October 2026, it came back without the #productTitle block at all. Set your unblocker's exit country to the store's country, for example the US for amazon.com.
  • A challenge page. If the HTML is short and mentions a CAPTCHA, the request was flagged. Route it through the unblocker and slow down.
  • A dead ASIN. Products are removed and ASINs stop resolving: the example ASIN this page used before October 2026 now returns a 404. Check the HTTP status and store missing products as such, not as a price of zero.
  • A different price element. Deals, subscriptions and multi-seller listings can show several prices. If the first a-offscreen value isn't the one you want, inspect the live page and narrow the selector to the buy box.

From Scraper to Tracker

To turn this into a price tracker:

  1. Store each run — write {asin, price, timestamp} to a database or CSV on every scrape.
  2. Schedule it — run the script on a cron job (e.g. hourly or daily).
  3. Compare and alert — diff the latest price against the previous one and trigger an email/Slack alert when it drops below your threshold.

Staying within the rules

Collect public product data only, keep request rates modest, and read the site's terms before you build anything commercial on the data. Don't scrape account pages, buyer or seller personal details, or anything behind a login. Our how to avoid getting blocked guide covers polite crawling in more detail.

FAQ

What is an ASIN and where do I find it?

The ASIN is Amazon's 10-character product ID. It appears in every product URL after /dp/, for example amazon.com/dp/B08LMTQ7ZF, and in the product details section of the page.

Why does my Amazon scraper return empty fields?

Usually because Amazon served a page for a different country, a challenge page, or a product that no longer exists. Fix the exit country to the store's market, check the HTTP status, and print the page title before parsing.

How often should I check Amazon prices?

Daily suits most competitor monitoring. Hourly checks are only worth it for fast-moving categories, and every extra request raises the chance of being blocked.

See our E-commerce Web Scraping Solutions overview, the Bright Data Web Unlocker review, and How to Avoid Getting Blocked.

Get started with Bright Data →