How to Scrape Amazon Product Data with Python
· 13 min read
To scrape Amazon product data, send the product URL to an Amazon scraper API and read back the title, price, list price, stock, ASIN, images, and the rating or its breakdown by star as JSON. On 12 product pages tested on October 23, 2026, the default call returned 6 right prices, 5 of them to the cent. Most of the misses went away once the request went through a premium proxy in the marketplace’s country.
In short:
- Default call, 12 pages on amazon.com, .co.uk, .de and .fr: 6 right prices, 2 prices from another country, 3 errors reported as errors (HTTP 520) and 1 wrong price with no error attached.
- A premium proxy set to the marketplace’s country fixed all 5 pages we retried that way. The documentation lists 25 credits for such a call, against 1 for a default one.
- Plain Python requests got all 13 URLs we sent, with no CAPTCHA page, but every price we read in them was in euros, because our office is in France.
- 12 pages on one day give a direction, too few for a success rate.
What you get back from an Amazon product page
Copy the product URL Amazon shows in your browser, or use the short form, the marketplace’s domain followed by /dp/ and the ASIN (https://www.amazon.com/dp/B071WWY2X3), which is what we sent in our test. A call to the Retail endpoint with that URL returns one JSON object. Here is the response for a Hanes hoodie on amazon.com, cut down to the fields most projects use:
{
"title": "Hanes Men's Hoodie - EcoSmart Fleece Hooded Sweatshirt for Men & Women ...",
"price": 9.95,
"originalPrice": 27,
"currency": "USD",
"isInStock": true,
"ASIN": "B071WWY2X3",
"color": "White",
"size": "Large",
"deliveryDate": "Wednesday, October 28",
"statusCode": 200,
"captchaFound": false
}
The full object holds more than that, but not every field is filled on every page. Here is what came back on the 9 product pages that returned a price in our default calls:
| Field | Pages with a value | What it held |
|---|---|---|
title, price, currency, ASIN |
9 of 9 | product name, price, currency code, the ASIN we sent |
images |
9 of 9 | full-size image URLs from Amazon’s media server |
description |
9 of 9 | the product description |
reviews |
9 of 9 | the average star rating on 2 pages, the share of ratings per star on 6, both on 1 |
seller |
8 of 9 | a brand store link on 6 pages, a search link on 1, and https://www.amazon.comundefined on the book; all on amazon.com, even for the .de and .fr pages |
category, categories |
8 of 9 | the category path |
isInStock, size, features, deliveryDate |
7 of 9 | stock, the default size, feature bullets, the delivery date shown |
originalPrice, brand, color |
6 of 9 | list price, brand, the default color |
shippingFees |
5 of 9 | 0 on four pages, 19.83 GBP on the Scott pack |
details |
4 of 9 | product details as name and value pairs (brand, model, weight, manufacturer) |
EAN13, ISBN |
0 of 9 | empty, even for the book |
The reviews field never gave the number of reviews, and it never contains what customers wrote. The brand was empty on the Scott pack, the Hanes hoodie and the book. To see the whole object for your own URL, paste it into the live test.
The hoodie came back as the white one in size Large, the variation Amazon shows by default. We did not test calls on other variation URLs.
A product ID that does not exist does not always produce an error. When we sent the made-up ASIN B000000000, Amazon itself served a page for a bag of Takis tortilla chips at $60, and the API returned that page with the ASIN we had asked for. In the response, the title is the clearest sign of the swap. The page URL also switched to a /clp/ path, but so did the EarPods page in our test, which was a real product.
This guide covers product pages. Search results pages are a different page type, and we did not test them.
How we tested 12 Amazon pages on 4 marketplaces
We took 12 products by hand from the best-seller lists on the morning of October 23, 2026. Search results pages would work as a source of product URLs too; only the product pages themselves were scraped:
- amazon.com: Apple EarPods, Scott toilet paper, the Hanes hoodie, an Owala water bottle and the hardcover of Atomic Habits.
- amazon.co.uk: Brita filter cartridges, a CeraVe cleanser and a heated throw.
- amazon.de: a De’Longhi descaler and adidas Adilette slides.
- amazon.fr: a Philips OneBlade 360 and an Oral-B Vitality Pro toothbrush.
We used a free ScrapingBot account and no option. Each page got one API call, then one plain request (covered below), with 10 to 23 seconds from one page to the next. For the right answer, we opened each page in a browser with a delivery address in the marketplace’s country: New York 10001 for amazon.com, London SW1A 1AA for amazon.co.uk, Berlin 10115 for amazon.de, and France for amazon.fr.
From our office, amazon.co.uk kept showing euros even with the London address, so we checked the pound prices by conversion. The three British pages gave the same euro-to-pound ratio, 1.194, to the third decimal.
Before reading any result, we fixed four classes: right (same price and same list price as the browser), another country (a coherent price for a different delivery country), error reported as an error, and wrong price with no error.
Why the price depends on where the request comes from
| Product | Marketplace | Browser, local address | Default API call | Class |
|---|---|---|---|---|
| Apple EarPods | .com | $19.00 | 19 USD | right |
| Scott toilet paper | .com | $5.68, list $6.49 | 4.25 GBP, list 4.85 | another country |
| Hanes hoodie | .com | $9.95, list $27.00 | 9.95 USD, list 27 | right |
| Owala bottle | .com | $29.99 | 29.99 USD | right |
| Atomic Habits | .com | $8.55 | 8.5568 USD | right, extra decimals |
| Brita cartridges | .co.uk | £23.99 (€28.65 shown) | HTTP 520 | error reported |
| CeraVe cleanser | .co.uk | £10.00 (€11.94 shown) | HTTP 520 | error reported |
| Heated throw | .co.uk | £29.99 (€35.82 shown) | 3212 USD | wrong, no error |
| De’Longhi descaler | .de | €8.49, list €14.99 | 7.71 EUR, list 13.62 | another country |
| adidas slides | .de | €16.03, list €23.00 | HTTP 520 | error reported |
| Philips OneBlade | .fr | €45.62 | 45.62 EUR | right |
| Oral-B Vitality Pro | .fr | €24.97 | 24.97 EUR | right |
Six right prices, one with extra decimals
The EarPods, the hoodie, the bottle and both French products came back exactly as the browser showed them, with the hoodie’s list price. The book came back as 8.5568 where Amazon displays $8.55. Rounding to two decimals gives 8.56, one cent above the page. We saw this on one page out of 12, so compare prices with a one-cent tolerance rather than exact equality.
Two prices from another country
The Scott pack on amazon.com came back at 4.25 GBP, list 4.85. At the 1.194 ratio from our British checks, 4.25 GBP is €5.07, exactly what our plain request from France read on the same page. The API got a price meant for a delivery outside the United States, shown in pounds.
The De’Longhi descaler came back about 9% under the Berlin price, on the price and on the list price alike. That is not the French price either (€7.99 in our plain request), so we cannot name the country.
Both are real prices, each for a shopper somewhere else. For a job that tracks what customers in Germany pay, they are wrong, and nothing in the response says so apart from the currency, which only gave the Scott case away.
One wrong price with no error
The heated throw on amazon.co.uk came back at 3212 USD, with no error field. The page sells it for £29.99. A check on the status code lets this one through, which makes it the result that matters most for price tracking. We have not identified the cause. The code further down shows the check that would have stopped it.
Three errors reported as errors
Brita, CeraVe and adidas returned a statusCode of 520 and a message telling us to retry later. The price field stayed empty. A visible failure like this one is the easy kind: your scraper can log it and retry.
Can you scrape Amazon with plain Python requests?
Plain requests show the same mechanism from the other side. We sent the 12 product URLs, plus the made-up ASIN, with Python requests, a browser User-Agent and an English Accept-Language header, nothing else, from an office connection in France. Every price our quick parser found in the HTML was in euros: the Scott pack on amazon.com read €5.07 instead of $5.68.
All 13 answers were HTTP 200 with the product title in the page, and none was a CAPTCHA page. HasData, which publishes its own Amazon guide, notes that the bot-check page can also come back with a 200 (HasData, read on October 23, 2026), so test for the product title. Our check looked for the productTitle element, which a bot-check page does not have.
Three things did get in the way:
- The prices were French. Amazon priced every page for a delivery to France. To get the American price, the request has to come from the United States.
- The pages are heavy. Each product page weighed between 1.2 and 3.3 MB of HTML, and you write and maintain the selectors to extract the data Amazon displays: price, list price, stock and variations.
- 13 slow requests prove nothing about volume. HasData writes that Amazon fronts its pages with AWS WAF. That service is built to “monitor, block, or rate limit bots such as scrapers, scanners, crawlers” (AWS WAF documentation, read on October 23, 2026). Thousands of pages a day is a different test, and we did not run it. We did not test any way around a block either, and this article does not describe one.
For a one-off check of a few products, requests is enough, as long as you accept your own country’s prices. For daily tracking of prices in a given country, the location problem comes back on every page, which is what the next section measures.
Pin the country: the fix we measured
We tried two fixes on the six pages that went wrong.
A plain retry, with no option, recovered one page out of three. The adidas slides came back on the second try at €13.47, list €19.33, both about 16% under the Berlin prices. That response pointed to another ASIN and a UK size 44.5, so the gap may come from the size as much as from the country. We did not rerun that page with the country set, so it stays open. Brita and CeraVe failed with 520 a second time.
Setting the proxy country fixed every page we tried it on. We sent the same URLs with premiumProxy and proxyCountry set to the marketplace’s country, as described in the advanced options of the documentation:
| Product | proxyCountry |
Result | Browser |
|---|---|---|---|
| Scott toilet paper | US | 5.68 USD, list 6.49 | $5.68, list $6.49 |
| De’Longhi descaler | DE | 8.49 EUR, list 14.99 | €8.49, list €14.99 |
| Heated throw | GB | 29.99 GBP | £29.99 |
| Brita cartridges | GB | 23.99 GBP, list 37.49 | £23.99 |
| CeraVe cleanser | GB | 10.00 GBP, list 12.50 | £10.00 |
Five out of five, with a median of 8.7 seconds per call, against 8.2 for the default calls on all 12 pages. Both options were on together and we did not test premiumProxy alone, so for Brita and CeraVe we cannot say which one removed the 520.
The language follows the country too. With proxyCountry set to DE, the De’Longhi title came back in German, where the default call had returned it in English.
The documentation lists 1 credit for a default Retail call and 25 with a premium proxy and a country. On our seven calls with a country, the balance dropped by exactly 25 each, and the two retries that failed cost nothing. Paying 25 credits makes sense when the price is the point, as in price monitoring across retailers. It is wasted when you only need ASINs or images. Plan volumes against the pricing page before you switch it on everywhere.
Python code that rejects the bad prices we saw
This script reads Amazon product pages with the country set for each marketplace, retries once on an error, and refuses a price when the title or the currency is not the one expected. We ran it on October 23, 2026, on the three URLs below. It returned 5.68 USD for the Scott pack and 8.49 EUR for the descaler, and flagged the made-up ASIN, where we had told it to expect an Owala bottle: “unexpected title: Takis, Intense Nacho Non-Spicy Tortilla Chips, 9.9 Ounce”.
import csv
import os
import time
import requests
API_URL = "https://api.scraping-bot.io/scrape/retail"
AUTH = (os.environ["SCRAPINGBOT_USERNAME"], os.environ["SCRAPINGBOT_API_KEY"])
# Marketplace -> (proxy country, currency we expect back)
MARKETPLACES = {
"www.amazon.com": ("US", "USD"),
"www.amazon.co.uk": ("GB", "GBP"),
"www.amazon.de": ("DE", "EUR"),
"www.amazon.fr": ("FR", "EUR"),
}
# Product URL -> a word the title must contain
PRODUCTS = {
"https://www.amazon.com/dp/B07BGLT25K": "Scott",
"https://www.amazon.de/dp/B008YETL18": "De'Longhi",
"https://www.amazon.com/dp/B000000000": "Owala",
}
def scrape(url, country):
payload = {"url": url, "options": {"premiumProxy": True, "proxyCountry": country}}
response = requests.post(API_URL, json=payload, auth=AUTH, timeout=180)
response.raise_for_status()
return response.json()["data"]
def get_product(url, expected_word):
country, currency = MARKETPLACES[url.split("/")[2]]
data = scrape(url, country)
if data.get("error"): # e.g. statusCode 520: retry once
time.sleep(5)
data = scrape(url, country)
if data.get("error"):
return {"url": url, "status": "failed", "note": data["error"]}
title = data.get("title") or ""
if expected_word.lower() not in title.lower():
return {"url": url, "status": "check", "note": f"unexpected title: {title[:60]}"}
if data.get("currency") != currency or data.get("price") is None:
return {"url": url, "status": "check", "note": f"got {data.get('price')} {data.get('currency')}"}
return {
"url": url,
"status": "ok",
"title": title,
"price": float(data["price"]),
"list_price": data.get("originalPrice"),
"currency": data["currency"],
"in_stock": data.get("isInStock"),
}
rows = [get_product(url, word) for url, word in PRODUCTS.items()]
with open("amazon_prices.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["url", "status", "title", "price", "list_price", "currency", "in_stock", "note"])
writer.writeheader()
writer.writerows(rows)
for row in rows:
print(row["status"], row.get("price"), row.get("currency"), row.get("note", ""), row["url"])
The currency check would have flagged the Scott pack in pounds and the heated throw in dollars. The title check is the one that catches a swapped page: the API returned the Takis page with the ASIN we had sent, so a check on the ASIN would have let it through. With the country set, the title may come back in the marketplace’s language, so pick a word that survives translation, such as the brand.
One check is missing on purpose: a price can be in the right currency and still be far off. If you track the same products every day, compare each price with the previous one and set aside any jump you would not believe, such as a price ten times higher overnight. The first run has no previous value, so read that file yourself.
The isInStock field was empty for the De’Longhi descaler in both calls, so treat an empty value as unknown, and keep it apart from out of stock.
Is it legal to scrape Amazon?
This is not legal advice, and the answer depends on your country and on what you do with the data. Two texts frame the question.
Amazon’s Conditions of Use, last updated on August 14, 2026, give a license to access the site that excludes “any collection and use of any product listings, descriptions, or prices” and “any use of data mining, robots, or similar data gathering and extraction tools” (Amazon Conditions of Use, read on October 23, 2026). Whether those terms bind someone who never logged in is a separate question.
A US federal court addressed that question for another platform. In Meta v. Bright Data, on January 23, 2024, the judge found that Facebook’s and Instagram’s terms “only prohibit logged-in scraping, and not logged-off scraping” (order of the Northern District of California). It is a first-instance decision about other terms, not a ruling on Amazon.
Our test stayed on public product pages, with no login to any account and no personal data collected. Our ecommerce web scraping guide covers the general rules for product pages.
FAQ
How can I scrape prices from Amazon?
You can try both on a free account first. Send each product URL to a scraping API, or fetch it with Python and extract the price from the HTML yourself. In both cases, send the request from the marketplace’s country: in our test, 2 of the 12 default calls returned another country’s price, and setting the country fixed both.
Is Amazon scraping legal?
It depends on your country and your use. Amazon’s Conditions of Use exclude the collection of prices and the use of robots from its license, and a 2024 US decision held that another platform’s terms only covered logged-in scraping. Neither settles your case. Keep to public pages without logging in, and ask a lawyer if the project matters.
How to scrape from Amazon?
Start from product URLs in the /dp/ form, make one call per page, and check the title and the currency of each answer before you store the price. The Python script above does all three.
Try it on your own product pages. Start on the free plan: paste an Amazon URL into the live test to see the fields that come back, then switch on the country setting where the price matters.
See the Amazon scraper