Skip to main content
Back to blog

How to Scrape Amazon Reviews With Python

Enrika avatarshinthiya avatar

Enrika Pavlovskytė

Last updated by Shinthiya Nowsain Promi

2026-06-22

8 min read

AI Summary:

This guide shows how to scrape Amazon reviews using Python, from setup to export. It covers the legal and ethical boundaries, then walks through building a custom scraper with Requests and BeautifulSoup to extract fields like author, rating, date, review text, and images before saving them to CSV with Pandas. It also flags the challenges of scaling manual scraping, and offers the Oxylabs Web Scraper API as a simpler alternative that returns clean, structured JSON.

As sellers pack the digital shelves with goods, customers grow fickle and switch between brands quickly in search of something that meets their expectations best. They're also more vocal than ever about product experiences, often leaving feedback that helps other shoppers decide on their next purchase. For companies, that pile of opinions is an opportunity: tune into what customers actually want and improve products accordingly.

In this tutorial, we'll show you how to scrape Amazon reviews from one of the biggest e-commerce sites in the world using Python. We've already covered broader Amazon scraping and automated Amazon price tracking, so this time we'll focus on building a custom Amazon review scraper from scratch – and then show you a simpler way to scale it.

Let's get to it.

What Amazon review data tells you

Amazon reviews typically consist of product ratings and written feedback, and that combination is surprisingly rich. Scraping Amazon product reviews can surface insights into customer preferences that aren't visible from a star average alone – recurring complaints, praised features, and the language real shoppers use to describe a product.

A few of the most common ways teams put Amazon review data to work:

  • Market research and competitive analysis. Comparing review content across rival listings highlights gaps your product can fill and weaknesses to avoid.

  • Product performance trends. Tracking reviews over time helps you identify when sentiment shifts after a redesign, a price change, or a new batch.

  • Inventory and demand signals. Review velocity and recurring themes can feed into better inventory management decisions.

  • Pricing context. Pairing reviews with scraped Amazon prices lets you connect price changes to satisfaction.

In short, scraping Amazon reviews turns scattered opinions into structured data points you can analyze at scale.

Before you scrape anything, it's worth understanding the boundaries. This isn't legal advice – consult your own counsel – but a few widely accepted principles apply.

Scraping publicly available data is generally permitted under U.S. law, and publicly accessible product information is usually defensible to extract. At the same time, scraping public data can conflict with Amazon's Terms of Service, which restrict data mining, robots, and similar extraction tools. The enforceability of those terms varies by jurisdiction, so the safest path is a conservative one:

  • Only collect public, non-personal data. Data privacy guidelines under frameworks like GDPR and CCPA prohibit extracting personally identifiable information. Reviewer display names are shown publicly, but you should avoid building profiles or harvesting anything that identifies a real person.

  • Never scrape behind a login. Scraping logged-in data can lead to account bans or legal threats. Stick to what's visible to a logged-out visitor.

  • Respect copyright norms when you report on or republish anything derived from scraped review content.

Ethically obtained data – public, non-personal, and collected at a reasonable rate – keeps you aligned with privacy laws and reduces risk.

How many reviews can you actually access?

One practical limitation shapes every Amazon review scraper: for non-logged-in users, Amazon limits the visible reviews to roughly 8 to 13 featured reviews per product page. The full review history sits behind pagination and, increasingly, behind a login.

This matters for two reasons. First, a single request to a product page will only ever return that featured subset, so your expectations for a basic scraper should match. Second – and we can't stress this enough – scraping reviews behind a login can lead to account bans. The approach in this tutorial deliberately stays on the public product page, where extraction is most defensible.

Setting up

For this tutorial you'll use Python, so make sure you have Python 3.8 or above installed along with four packages – Requests, Pandas, BeautifulSoup, and lxml. We detailed the installation steps in our earlier post on Amazon product data scraping, but the short version is:

pip install requests pandas beautifulsoup4 lxml

To begin, import the libraries, specify the product ASIN (the Amazon Standard Identification Number found in any product page URL), and create a custom headers dictionary:

import time
import random
import requests
from bs4 import BeautifulSoup
import pandas as pd

asin = "B098FKXT8L"

custom_headers = {
    "Accept": (
        "text/html,application/xhtml+xml,application/xml;"
        "q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"
    ),
    "Accept-Language": "en-US,en;q=0.9",
    "Connection": "keep-alive",
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "Gecko/20100101 Firefox/139.0"
    ),
}

Setting custom headers is a crucial step that helps you maintain reliable access while scraping Amazon reviews – we covered this in detail in our product scraping post. You'll need to ensure stable connections, so you'll also want quality proxies for anything beyond a one-off test. Using residential proxies helps you maintain stable access by routing requests through real user IPs, which emulate browser behavior. For smaller jobs, free proxies can be a starting point.

Making a request

Next, define a get_soup() function that sends a request to the Amazon product page and returns a BeautifulSoup instance, making the HTML ready for parsing:

def get_soup(url):
    response = requests.get(url, headers=custom_headers)
    if response.status_code != 200:
        print(f"Error fetching page: status {response.status_code}")
        exit(-1)
    return BeautifulSoup(response.text, "lxml")

If Amazon responds with anything other than 200, the function reports it and stops. A 503 or a CAPTCHA page is a common sign that your requests have been flagged.

Getting the review objects

Now that you're ready to start, collect all the review objects and extract the information you need from each. You'll first find a CSS selector for the reviews, then use the .select method to grab them.

Amazon serves two kinds of reviews. Use this selector for local reviews (from the page's own marketplace):

#cm-cr-dp-review-list > li

And this selector for global reviews (translated reviews from other marketplaces):

#cm-cr-global-review-list > li

The following code collects both:

local_reviews = soup.select("#cm-cr-dp-review-list > li")
global_reviews = soup.select("#cm-cr-global-review-list > li")

That leaves you with arrays of reviews to iterate over. Wrap the logic in a get_reviews() function that processes each type:

def get_reviews(soup):
    reviews = []
    local_reviews = soup.select("#cm-cr-dp-review-list > li")
    global_reviews = soup.select("#cm-cr-global-review-list > li")

    for review in local_reviews:
        reviews.append(extract_review(review, is_local=True))
    for review in global_reviews:
        reviews.append(extract_review(review, is_local=False))
    return reviews

Note: in the next steps you'll build the extract_review() function, which must be placed above get_reviews() in your file.

A word on selectors: Amazon updates its HTML frequently, so the CSS selectors used throughout this tutorial can change without notice. If your scraper suddenly returns empty results, open the product page in your browser, inspect the review elements, and confirm the class and ID names still match before assuming the logic is broken.

Author name

First up is the reviewer's name. Use this CSS selector:

.a-profile-name

Collect it as plain text:

author = review.select_one(".a-profile-name").text.strip()

Review rating

Next, extract the rating. It lives here:

.review-rating > span

The rating string carries extra text you don't need, so strip it out:

rating = (
    review.select_one(".review-rating > span").text
    .replace("out of 5 stars", "")
    .strip()
)

Date

Grab the review date with:

.review-date

Here’s the code that fetches the date value from the object:

date = review.select_one(".review-date").text.strip()

Title

Extracting the title differs for local and global reviews, so handle them with an if-else branch. 

For a local review:

.review-title span:not([class])

For a global review:

.review-title .cr-original-review-content

The logic looks like this:

if is_local:
    title = (
        review.select_one(".review-title")
        .select_one("span:not([class])")
        .text.strip()
    )
else:
    title = (
        review.select_one(".review-title")
        .select_one(".cr-original-review-content")
        .text.strip()
    )

Review text

The review content also needs two approaches. 

Local review text:

.review-text

Global review text:

.review-text .cr-original-review-content

You can then scrape Amazon review text accordingly:

if is_local:
    content = " ".join(
        review.select_one(".review-text").stripped_strings
    )
else:
    content = " ".join(
        review.select_one(".review-text")
        .select_one(".cr-original-review-content")
        .stripped_strings
    )

Images

If a reviewer added pictures, select the image elements. For a local review:

.review-image-tile

For a global review:

.linkless-review-image-tile

Add the selector to the same if-else section, then pull each image URL from the element's data-src attribute. Using .get("data-src") instead of .attrs["data-src"] means a tile without that attribute is skipped rather than raising a KeyError:

if is_local:
    img_selector = ".review-image-tile"
else:
    img_selector = ".linkless-review-image-tile"


image_elements = review.select(img_selector)
images = (
    [img.get("data-src") for img in image_elements if img.get("data-src")]
    if image_elements else None
)

Verification

Finally, check whether the reviewer purchased the item – i.e., whether it's a verified purchase. That label is here:

span.a-size-mini

And extracted using the following code:

verified_element = review.select_one("span.a-size-mini")
verified = verified_element.text.strip() if verified_element else None

Putting everything together

With every field accounted for, assemble them into an extract_review() function that returns a dictionary per review:

def extract_review(review, is_local=True):
    author = review.select_one(".a-profile-name").text.strip()
    rating = (
        review.select_one(".review-rating > span").text
        .replace("out of 5 stars", "")
        .strip()
    )
    date = review.select_one(".review-date").text.strip()

    if is_local:
        title = (
            review.select_one(".review-title")
            .select_one("span:not([class])")
            .text.strip()
        )
        content = " ".join(
            review.select_one(".review-text").stripped_strings
        )
        img_selector = ".review-image-tile"
    else:
        title = (
            review.select_one(".review-title")
            .select_one(".cr-original-review-content")
            .text.strip()
        )
        content = " ".join(
            review.select_one(".review-text")
            .select_one(".cr-original-review-content")
            .stripped_strings
        )
        img_selector = ".linkless-review-image-tile"

    verified_element = review.select_one("span.a-size-mini")
    verified = verified_element.text.strip() if verified_element else None

    image_elements = review.select(img_selector)
    images = (
        [img.get("data-src") for img in image_elements if img.get("data-src")]
        if image_elements else None
    )

    return {
        "type": "local" if is_local else "global",
        "author": author,
        "rating": rating,
        "title": title,
        "content": content.replace("Read more", ""),
        "date": date,
        "verified": verified,
        "images": images,
    }

Exporting data

Once the reviews are scraped, export them so they can be analyzed. With the data already in a list of dictionaries, Pandas can clean and organize it into a CSV file in two lines:

def main():
    product_url = f"https://www.amazon.com/dp/{asin}"
    time.sleep(random.uniform(1, 3))
    soup = get_soup(product_url)
    reviews = get_reviews(soup)

    df = pd.DataFrame(reviews)
    df.to_csv(f"reviews_{asin}.csv", index=False)
    print(f"Saved {len(reviews)} reviews to reviews_{asin}.csv")


if __name__ == "__main__":
    main()

The time.sleep(random.uniform(1, 3)) line adds a small randomized delay before the request – a simple habit that helps you stay under rate limits. After running the script, you'll find your reviews saved in reviews_B098FKXT8L.csv, ready to open in Excel or load back into Pandas for analysis.

Scraped Amazon reviews

Full code for Amazon reviews scraper

import time
import random
import requests
from bs4 import BeautifulSoup
import pandas as pd

asin = "B098FKXT8L"

custom_headers = {
    "Accept": (
        "text/html,application/xhtml+xml,application/xml;"
        "q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"
    ),
    "Accept-Language": "en-US,en;q=0.9",
    "Connection": "keep-alive",
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "Gecko/20100101 Firefox/139.0"
    ),
}


def get_soup(url):
    response = requests.get(url, headers=custom_headers)
    if response.status_code != 200:
        print(f"Error fetching page: status {response.status_code}")
        exit(-1)
    return BeautifulSoup(response.text, "lxml")


def extract_review(review, is_local=True):
    author = review.select_one(".a-profile-name").text.strip()
    rating = (
        review.select_one(".review-rating > span").text
        .replace("out of 5 stars", "")
        .strip()
    )
    date = review.select_one(".review-date").text.strip()

    if is_local:
        title = (
            review.select_one(".review-title")
            .select_one("span:not([class])")
            .text.strip()
        )
        content = " ".join(
            review.select_one(".review-text").stripped_strings
        )
        img_selector = ".review-image-tile"
    else:
        title = (
            review.select_one(".review-title")
            .select_one(".cr-original-review-content")
            .text.strip()
        )
        content = " ".join(
            review.select_one(".review-text")
            .select_one(".cr-original-review-content")
            .stripped_strings
        )
        img_selector = ".linkless-review-image-tile"

    verified_element = review.select_one("span.a-size-mini")
    verified = verified_element.text.strip() if verified_element else None

    image_elements = review.select(img_selector)
    images = (
        [img.get("data-src") for img in image_elements if img.get("data-src")]
        if image_elements else None
    )

    return {
        "type": "local" if is_local else "global",
        "author": author,
        "rating": rating,
        "title": title,
        "content": content.replace("Read more", ""),
        "date": date,
        "verified": verified,
        "images": images,
    }


def get_reviews(soup):
    reviews = []
    local_reviews = soup.select("#cm-cr-dp-review-list > li")
    global_reviews = soup.select("#cm-cr-global-review-list > li")

    for review in local_reviews:
        reviews.append(extract_review(review, is_local=True))
    for review in global_reviews:
        reviews.append(extract_review(review, is_local=False))
    return reviews


def main():
    product_url = f"https://www.amazon.com/dp/{asin}"
    time.sleep(random.uniform(1, 3))
    soup = get_soup(product_url)
    reviews = get_reviews(soup)

    df = pd.DataFrame(reviews)
    df.to_csv(f"reviews_{asin}.csv", index=False)
    print(f"Saved {len(reviews)} reviews to reviews_{asin}.csv")


if __name__ == "__main__":
    main()

For a deeper dive on Amazon scraping (search, reviews, products, pricing, etc.), check out Oxylabs documentation.

Drawbacks of manual Amazon reviews scraping

A custom-built scraper offers flexibility, and the script above is a solid way to learn how to scrape reviews from Amazon. But the moment you move from a single product page to anything resembling production, the cracks show.

Here's what you run into in practice:

  • Unstable access. Without IP rotation and high-quality proxies, your address gets flagged fast. Large-scale scraping essentially requires rotating residential proxies that emulate browser behavior – a whole layer of infrastructure to build and maintain.

  • Pagination. Remember that logged-out pages expose only 8–13 featured reviews. Retrieving the full review history means working through paginated reviews, which calls for sophisticated automation rather than a single request.

  • Dynamic, JavaScript-rendered content. Parts of Amazon load content with JavaScript that plain requests never see. Handling dynamic content means reaching for browser automation tools like Playwright or Selenium that can emulate a real browser – heavier, slower, and more to manage.

  • Brittle selectors. Amazon changes its HTML regularly. The CSS selectors in this tutorial work today, but a layout tweak can silently break extraction, leaving you to re-inspect the page and patch your scraper. That maintenance never really ends.

None of this is insurmountable, but it adds up to a real engineering project. For many use cases, that overhead is exactly what you'd rather not own.

Scale up with an Amazon Scraper API

If the goal is Amazon review data rather than the thrill of handling JavaScript-rendered content, a scraper API removes the headache. Instead of fetching raw HTML and maintaining selectors, you send one request and get a clean, structured JSON response back – proxies, CAPTCHA management, JavaScript rendering, and parsing are all handled for you.

Oxylabs Web Scraper API does exactly this for Amazon. You submit an ASIN with the amazon_product source, and the API returns the product page already parsed into JSON – including a reviews array with each review's author, rating, content, date, verified-purchase status, and more. There's no HTML to wrangle and no proxy pool to run.

Here's the same review-collection task using the API. Add your API credentials, and Pandas exports the reviews to CSV just like before:

import requests
import pandas as pd

USERNAME = "YOUR_USERNAME"
PASSWORD = "YOUR_PASSWORD"

asin = "B098FKXT8L"

payload = {
    "source": "amazon_product",
    "domain": "com",
    "query": asin,
    "parse": True,
    "geo_location": "90210",
    "context": [
        {"key": "autoselect_variant", "value": True},
    ],
}

response = requests.post(
    "https://realtime.oxylabs.io/v1/queries",
    auth=(USERNAME, PASSWORD),
    json=payload,
    timeout=180,
)
response.raise_for_status()

data = response.json()
content = data["results"][0]["content"]
reviews = content.get("reviews", [])

df = pd.DataFrame(reviews)
df.to_csv(f"reviews_{asin}.csv", index=False)
print(f"Saved {len(reviews)} reviews to reviews_{asin}.csv")

A few things worth noting:

  • parse: true is what returns structured JSON instead of HTML, so you skip BeautifulSoup entirely.

  • geo_location sets the delivery location, since Amazon tailors results by region – handy for consistent, comparable data.

  • Authentication uses your sub-account username and password as the API key equivalent; no proxy configuration required.

Each review object mirrors the fields you'd otherwise extract by hand – id, title, author, rating, content, timestamp, is_verified, and others – so the output drops straight into your analysis pipeline. For teams that just need read-to-use data, Oxylabs also offers a dedicated reviews scraper and ready-made e-commerce datasets.

Wrapping up

There are several ways to scrape Amazon product reviews, and the right one depends on scale. A custom Python scraper built with Requests and BeautifulSoup gives you full control and is a great way to understand how review extraction works – identifying the right HTML tags, separating local and global reviews, and exporting clean data to a CSV file. For small, occasional jobs paired with residential proxies, it's perfectly serviceable.

But Amazon's login walls, pagination, dynamic content, ever-shifting selectors, and other challenges make the manual route hard to scale and tiring to maintain. When you need reliable amazon review data in volume, a simple API that returns a structured JSON response – like Oxylabs Web Scraper API – saves the time and effort you'd otherwise sink into infrastructure. Throughout, keep your scraping ethical: stick to public data, avoid personal information and logged-in pages, and respect Amazon's terms and applicable privacy laws.

If you found this guide helpful, check out our tutorials on scraping Best Buy, Wayfair, and eBay, plus guides on managing Amazon CAPTCHA and building an Amazon price tracker.

About the author

Enrika avatar

Enrika Pavlovskytė

Former Copywriter

Enrika Pavlovskytė was a Copywriter at Oxylabs. With a background in digital heritage research, she became increasingly fascinated with innovative technologies and started transitioning into the tech world. On her days off, you might find her camping in the wilderness and, perhaps, trying to befriend a fox! Even so, she would never pass up a chance to binge-watch old horror movies on the couch.

All information on Oxylabs Blog is provided on an "as is" basis and for informational purposes only. We make no representation and disclaim all liability with respect to your use of any information contained on Oxylabs Blog or any third-party websites that may be linked therein. Before engaging in scraping activities of any kind you should consult your legal advisors and carefully read the particular website's terms of service or receive a scraping license.

Related articles

Price Tracker With Python illustration
How to Build a Price Tracker With Python
author avatar

Augustas Pelakauskas

2025-09-03

Crawl a Website visual
15 Tips on How to Crawl a Website Without Getting Blocked
adelina avatar

Adelina Kiskyte

2024-03-15

How to Make Amazon Price Tracker With Python
How to Track Amazon Prices With Python
author avatar

Yelyzaveta Hayrapetyan

2023-11-22

Frequently asked questions

Is there a way to export Amazon reviews?

Yes. Amazon doesn't provide a built-in feature for downloading reviews, so you'll need a web scraping tool or an API to automate collecting them from product pages. Either approach lets you extract details like ratings, titles, dates, and review text and save them in a structured format.

Free proxies

Get 5GB of traffic per month across 5 US IPs and 20 concurrent sessions.

Sign up

Premium proxies

Choose from several premium proxy categories, including Residential, Datacenter, and more.

Sign up

Get the latest news from data gathering world

Free proxies

Get 5GB of traffic per month across 5 US IPs and 20 concurrent sessions.

Sign up

Premium proxies

Choose from several premium proxy categories, including Residential, Datacenter, and more.

Sign up