Next-Gen App & Browser Testing Cloud
Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

A hands-on tutorial to build an Amazon scraper with Playwright: real selectors, structured JSON output, pagination, the anti-bot reality, and a run on a real cloud browser.

Swastika Yadav
Author

Samyak Goyal
Reviewer
Last Updated on: July 6, 2026
You write a script that fetches an Amazon search page, parse the HTML for prices, and get back nothing. The prices, the ratings, the variant options, none of it is in the response you received. That is the moment most people learn that a modern product page is assembled in the browser, not shipped as finished HTML.
This matters because product data now sits behind the front door of a very large slice of retail. E-commerce accounted for 16.9 percent of total US retail sales in the first quarter of 2026, per the US Census Bureau Quarterly Retail E-Commerce Sales report. When a market that large runs on catalog pages, being able to read those pages reliably is a real skill.
This is a narrow, hands-on tutorial. You will build an amazon scraper with Playwright step by step: launch a real browser, find selectors that do not shatter on the next deploy, emit clean structured JSON, walk through pagination, and confront the anti-bot reality head on. For the broader mechanics of Playwright scraping, the Playwright for web scraping guide is the hub this tutorial builds on.
Overview
What Does This Tutorial Build?
A working Amazon-style product scraper in Playwright that launches a real browser, finds selectors that survive a redesign, emits structured JSON, and walks pagination. The technique is shown on a public playground because Amazon's own terms restrict automated access.
What Do You Need to Follow Along?
Can You Bypass Amazon's Bot Defenses?
No, and no tool guarantees it. Amazon runs aggressive anti-bot detection, so build for graceful failure: detect a challenge, back off, and prefer the official Product Advertising API where it fits.
Before writing code, it helps to know the three failure modes that kill most scrapers, because every technical choice in this tutorial is a direct answer to one of them.
Playwright answers the first two directly. It drives a real Chromium, Firefox, or WebKit browser, and its Playwright locators are, in the words of the official docs, the central piece of Playwright's auto-waiting and retry-ability. That auto-waiting is why a well-built locator reads data that is not on the page yet without a fixed sleep.
Keep the prerequisites minimal. This tutorial uses Node.js and JavaScript, and every code block is runnable as written.
mkdir amazon-scraper && cd amazon-scraper
npm init -y
npm install playwright
npx playwright install chromiumOne honest note on the target. Amazon's Conditions of Use restrict automated access, so we demonstrate the technique against the public Ecommerce Playground, which mirrors a real storefront's structure. Every selector strategy here transfers to any product grid; the playground just lets you run the tutorial without tripping a defense you should be respecting anyway.
The first script does three things: launches Chromium, opens a search results page, and waits for the product grid to render. Running headed at first (with headless: false) lets you watch the page load and confirm the data actually appears.
const { chromium } = require("playwright");
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
const query = "apple";
const url =
"https://ecommerce-playground.lambdatest.io/index.php?route=product/search&search=" +
encodeURIComponent(query);
await page.goto(url, { waitUntil: "domcontentloaded" });
// Wait for the first product card instead of a fixed sleep
await page.locator(".product-thumb").first().waitFor();
const count = await page.locator(".product-thumb").count();
console.log("Product cards on page:", count);
await browser.close();
})();The line that matters is waitFor() on the first product card. Playwright auto-waits for a range of actionability checks, including whether an element is visible and stable, before it acts. Waiting on a real element, not a guessed number of milliseconds, is the single biggest reliability win over a naive scraper.
A selector is a promise about the page structure. Amazon and any large storefront change markup constantly, so the goal is locators that describe what an element means, not where it currently sits in the tree.
Playwright's docs list the recommended locator methods in priority order, and the pattern generalizes well beyond the playground.
| Data point | Preferred locator | Why it survives change |
|---|---|---|
| Product title | getByRole("heading") or a title link inside the card | Accessible role and name stay constant even when wrapper classes are renamed. |
| Price | A price class scoped inside the card, or getByText with a currency pattern | Scoping to the card avoids grabbing unrelated prices elsewhere on the page. |
| Product link | The anchor whose href points at the product route | The URL pattern is a stable contract; layout classes are not. |
| Card container | A single repeating container class scoped once, then iterated | Iterating cards keeps each record's fields grouped correctly. |
The rule of thumb: locate the repeating card once, then read each field relative to that card. Scoping field lookups inside the card is what stops the price from row two attaching itself to the title from row one.
Note: Scraping breaks in production because a stripped headless setup misses dynamically rendered fields. Real Chrome sessions on TestMu AI Browser Cloud execute JavaScript and hydrate the page the way a user sees it. Start free with TestMu AI
Raw text is not the deliverable. You want a clean array of records that a database, a spreadsheet, or a downstream job can consume. Iterate over each product card and build one object per product.
const fs = require("fs");
const { chromium } = require("playwright");
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
const url =
"https://ecommerce-playground.lambdatest.io/index.php?route=product/search&search=apple";
await page.goto(url, { waitUntil: "domcontentloaded" });
await page.locator(".product-thumb").first().waitFor();
const cards = page.locator(".product-thumb");
const total = await cards.count();
const products = [];
for (let i = 0; i < total; i++) {
const card = cards.nth(i);
const title = (await card.locator(".caption a").first().textContent())?.trim();
const price = (await card.locator(".price-new, .price").first().textContent())?.trim();
const link = await card.locator("a").first().getAttribute("href");
if (title) {
products.push({ title, price: price || null, url: link || null });
}
}
fs.writeFileSync("products.json", JSON.stringify(products, null, 2));
console.log("Saved", products.length, "products to products.json");
await browser.close();
})();Two details make this robust. Each field is read inside the card with a scoped locator, and the optional chaining plus the if (title) guard skip empty cards instead of pushing junk records. The output is a JSON file you can diff, load, or ship.
One page of results is rarely enough. There are two reliable pagination strategies, and the URL approach is the one to prefer when it exists.
async function scrapeAllPages(page, baseUrl, maxPages = 5) {
const all = [];
for (let p = 1; p <= maxPages; p++) {
await page.goto(baseUrl + "&page=" + p, { waitUntil: "domcontentloaded" });
// If no cards render, we have walked past the last page
const hasResults = await page.locator(".product-thumb").first().isVisible().catch(() => false);
if (!hasResults) break;
const cards = page.locator(".product-thumb");
const total = await cards.count();
for (let i = 0; i < total; i++) {
const card = cards.nth(i);
const title = (await card.locator(".caption a").first().textContent())?.trim();
const price = (await card.locator(".price-new, .price").first().textContent())?.trim();
if (title) all.push({ page: p, title, price: price || null });
}
// Be a polite client: randomized pause between pages
await page.waitForTimeout(1000 + Math.random() * 1500);
}
return all;
}The maxPages cap and the randomized pause are not optional polish. They keep the scraper from running away into thousands of requests and from hammering the server at a machine-gun cadence, which is exactly the behavior bot defenses are tuned to catch.
Here is the part most tutorials skip. Amazon runs some of the most aggressive bot detection on the public web, and no library, plugin, or cloud feature guarantees a bypass. Anyone selling you certainty is selling you a future outage.
What you can do is behave less like a bot and fail gracefully when you are caught. In practice that means:
const context = await browser.newContext({
userAgent:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
"(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
viewport: { width: 1366, height: 768 },
locale: "en-US",
});
const page = await context.newPage();
// Detect a block before parsing so failures are explicit, not silent
const blocked = await page.getByText(/enter the characters|robot check/i)
.first()
.isVisible()
.catch(() => false);
if (blocked) {
console.warn("Bot challenge detected. Backing off.");
}Cloud browser platforms, including TestMu AI Browser Cloud, offer best-effort stealth features such as fingerprint masking and CAPTCHA handling. The word that matters is best-effort: it improves success rates on resistant sites, it does not make a session undetectable, and it is never a guarantee.
A single local browser is fine for a demo. Scraping a catalog at any real volume means many browsers running in parallel, which is memory-heavy on a laptop and painful to operate on your own servers. This is the gap TestMu AI Browser Cloud is built to close: it provisions real, full-featured Chrome sessions on demand and records video, console logs, and network logs for every run so a failed scrape is debuggable instead of a black box.
To validate the extraction logic against a real cloud session rather than a local one, the Browser Cloud SDK runs a quick scrape without any session boilerplate. The snippet below is what produced the run captured after it.
const { Browser } = require("@testmuai/browser-cloud");
const client = new Browser();
(async () => {
const data = await client.scrape({
url: "https://ecommerce-playground.lambdatest.io/index.php?route=product/search&search=apple",
format: "html",
lambdatestOptions: { "LT:Options": { username: "YOUR_USERNAME", accessKey: "YOUR_ACCESS_KEY" } },
});
const html = typeof data === "string" ? data : data.html;
// Parse the fully rendered HTML into structured records here
console.log("Rendered HTML length:", html.length);
})();Running that scrape against the Ecommerce Playground on a real cloud Chrome session returned a fully rendered page and 15 product cards in about seven seconds. Parsing the rendered HTML produced this structured output, which is the actual console result of the run:
SCRAPE OK chars=228575 time=7.4s products_found=15
[
{ "title": "iPod Shuffle", "price": "$182.00" },
{ "title": "iPod Nano", "price": "$122.00" },
{ "title": "Apple Cinema 30\"", "price": "$122.00" },
{ "title": "MacBook Pro", "price": "$2,000.00" },
{ "title": "Apple Cinema 30\"", "price": "$122.00" }
]That is the whole pipeline in one result: a real browser rendered the page, JavaScript ran, and the parser produced clean records. The Browser Cloud documentation walks through session transparency and the built-in tunnel for reaching staging or private storefronts. Teams that prefer driving the browser from a terminal or a CI job can reach the same infrastructure through the Kane CLI testing agent.
A working scraper is a technical achievement; a defensible one is an operational discipline. Treat these as guardrails, not afterthoughts.
For scenarios where continuously reading dynamic prices is the whole job, the deep dive on price scraping at scale covers the compliance and throughput tradeoffs in more depth than a single tutorial can.
Start by running the structured-JSON script from the extraction section against the Ecommerce Playground; once it emits a clean products.json, you have the core of a working amazon scraper. Then layer on pagination, add the block-detection check, and only after that consider volume. When you outgrow a single site, the broader guide to ecommerce web scraping covers the production architecture that sits around a scraper like this one.
When one browser is no longer enough, move the same Playwright logic onto real cloud Chrome sessions with TestMu AI Browser Cloud, and use its recorded video and network logs to debug the runs that come back empty. Keep the terms of use in front of you the entire time, prefer an official API where one fits, and treat stealth as best-effort rather than a promise. Build for graceful failure and your scraper will outlast the next markup change.
Note: AI assistance was used in researching and drafting this article. Swastika Yadav (Community Evangelist at TestMu AI, expertise in Playwright and automation testing) verified every statistic, link, and product claim against primary sources before publication, following our editorial process and AI use policy.
Author
Swastika Yadav is a community evangelist and Developer Advocate with 4+ years of experience in software testing, full-stack development, and developer tooling. She has worked with Phyllo, Turso, and UnitedHealth Group, contributing to test-driven applications, QA practices, and developer experience strategies. Swastika holds a B.Tech in Computer Science and is recognized as a contributor to the global QA and developer community. She engages with 8,500+ LinkedIn followers and a 60K+ Twitter audience of testers, developers, and tech leaders.
Reviewer
Samyak Goyal is a Senior Member of Technical Staff at TestMu AI engineering Kane CLI, the command-line tool that runs browser automation from the terminal, where a flow described in natural language executes in a real Chrome browser and returns pass or fail with shareable proof. He is a backend engineer with 4+ years of experience, previously an SDE at Innovaccer, where he built APIs, introduced Kafka, and cut deployment from weeks to hours. Samyak also builds multi-agent systems, skill-orchestration frameworks, and a personal copilot that indexes 200+ microservice repositories.
Did you find this page helpful?
More Related Blogs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance