Scraping Amazon
with Python in 2026:
Complete Guide

Python scraping Amazon product data with code on screen
TL;DR
  • Datacenter IPs are blocked at the network layer before a single header is read. Residential proxies with consumer ISP ASNs are the baseline requirement, not an upgrade.
  • Chrome sends 12 headers in a specific order. Python sends 2. That gap triggers Layer 3 detection within 20-50 requests. Adding a User-Agent alone does not fix it.
  • AWS WAF added JA4 TLS fingerprinting in March 2025. Python's requests library is now identifiable at the handshake layer before any header is read.
  • Most Amazon product pages do not need Playwright. Title, price, and availability are in the initial HTML. Use async httpx for scale.
  • Single selectors break silently. Amazon A/B tests its UI constantly. Fallback chains are required, not optional.
  • The 503 is an IP problem first, a code problem second. Fix the proxy layer before debugging your parser.

You have probably read a guide that showed you three lines of code, maybe a static User-Agent header, and claimed that was enough to scrape Amazon. And then you ran it, and it worked for exactly long enough to feel confident before it stopped working entirely.

Here's the thing: scraping Amazon with Python is solvable. But it requires understanding what is actually happening when your requests get blocked. The 503 is not random. Amazon's infrastructure is built on AWS, and the same AWS WAF Bot Control system they sell to enterprises to protect their own sites is what protects Amazon's product pages. Knowing that changes how you approach every decision in your scraper.

This guide covers the full picture: why the blocks happen, what Python tools actually work and when, complete production-ready code for product pages, search results, and reviews, and how to keep it running at scale.


Part 1 Legal & Risk

Part 2 Detection Architecture

Why Amazon Keeps Blocking Your Scraping Requests

Most guides stop at "Amazon detects bots." That is accurate and useless. Amazon uses AWS WAF Bot Control which runs four detection layers in sequence: IP/ASN reputation, TLS fingerprinting (JA3/JA4), HTTP request fingerprinting, and behavioral ML scoring. Adding a User-Agent only patches part of Layer 3 and does nothing about the other three. Here is what is actually happening at the network layer, in the order Amazon evaluates it.

Layer 1
IP and ASN Reputation
Every IP address belongs to an Autonomous System Number registered to a network operator. AWS, Google Cloud, DigitalOcean, Hetzner, and OVH have publicly documented ASN ranges. Amazon's WAF queries ASN databases before the HTTP layer opens. If your IP resolves to a datacenter ASN, it is assigned a low trust score before your first header is read.
Fix: Residential proxy with a clean consumer ISP ASN (Comcast, AT&T, BT, Deutsche Telekom).
Layer 2
TLS Fingerprinting (JA3 and JA4)
Every HTTP client sends a TLS Client Hello message during the SSL handshake. The combination of cipher suites, extensions, elliptic curves, and signature algorithms creates a fingerprint. Python's requests has a known JA3 signature. AWS WAF added JA4 fingerprinting in March 2025, making Python's HTTP clients even more identifiable than they were before.
Fix: Use Playwright (which runs actual Chromium with a real browser TLS stack), or use a TLS-spoofing library like curl_cffi.
Layer 3
HTTP Request Fingerprinting
A real Chrome browser sends 12 or more headers in a specific order. Python's requests sends 4 by default. Beyond header presence, the order headers appear in the request is a detection signal. Adding a User-Agent addresses one header but leaves the order mismatch and the missing Sec-Fetch-* headers intact. This is why "just add a User-Agent" works briefly then fails.
Fix: Send the complete 12-header stack in the exact order a Chrome browser sends them (see Section 4 below).
Layer 4
Behavioral ML Scoring
AWS WAF Bot Control Targeted Mode scores sessions over time. Signals include: request intervals under 1 second, requests arriving in perfectly regular intervals, product pages requested without a prior search or category page, and the same IP accessing thousands of pages in a session pattern no human browsing session would produce.
Fix: Randomized delays of 2-5 seconds between requests, session warm-up through a search page before hitting product URLs.
Why "works for 20 requests then blocks" makes sense now: Layers 1 and 2 are evaluated on the first connection. If your IP passes (e.g., a fresh residential IP with an okay reputation), Layers 3 and 4 accumulate scoring data over the session. Your trust score degrades as request patterns accumulate, until the behavioral threshold is crossed. This is why scrapers that "worked yesterday" stop working; not because Amazon changed their code, but because your IP's session score degraded.

Why Running Your Scraper on AWS or GCP Makes It Worse

If your Python scraper runs on an EC2 instance, a Google Cloud VM, or any major cloud compute platform, you are scraping Amazon from IPs that belong to Amazon's own ASN neighbors. Amazon's WAF has explicit rules for these address ranges. You are not just blocked at Layer 1 - you are pre-burned. Always run scrapers behind residential proxies, never from cloud servers directly.


Part 3 Tooling Strategy

Which Python Tool Should You Actually Use?

Amazon has different page types with different rendering behaviors. Matching your tool to the page type saves hours of debugging: requests + BeautifulSoup for static product data, httpx + asyncio for bulk scale, Playwright for lazy-loaded reviews or scroll-based search results only.

Use Case Best Tool Proxy Needed? JS Rendering?
Product detail page (title, price, rating) requests + BeautifulSoup Yes (residential) No - data is in initial HTML
Bulk ASIN list, 100+ products httpx async Yes (rotating residential) No
Product reviews (lazy-loaded) Playwright Yes (residential) Yes
Search results with infinite scroll Playwright Yes (residential) Sometimes
Price monitoring pipeline at scale httpx async + residential proxies Yes (premium rotating) No
Common Mistake
Reaching for Playwright because "it uses a real browser." Playwright launches a real Chromium instance, which means real RAM consumption (roughly 150-300MB per browser context) and real startup overhead (1-2 seconds per launch without persistent contexts). For 10,000 product pages, httpx async with proper headers is dramatically faster and cheaper to run. Use Playwright when the page actually requires JavaScript execution to produce the data you need.

The Complete Header Stack That Passes Amazon's Layer 3 Check

The single most actionable fix for most developers hitting the 20-request wall. You need 12 headers in Chrome-matching order. Header order matters as much as header content. Always use requests.Session() to preserve cookies across requests.

Python - Full Amazon Header Stack (Chrome 122 baseline)
# This header stack mirrors Chrome 122 on Windows 10. # Keep the key order exactly as written. # Python 3.7+ dicts preserve insertion order. import requests import time, random HEADERS = { "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/122.0.0.0 Safari/537.36" ), "Accept": ( "text/html,application/xhtml+xml,application/xml;" "q=0.9,image/avif,image/webp,image/apng,*/*;" "q=0.8,application/signed-exchange;v=b3;q=0.7" ), "Accept-Language": "en-US,en;q=0.9", "Accept-Encoding": "gzip, deflate, br", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Sec-Fetch-User": "?1", "Cache-Control": "max-age=0", } # Always use a Session. Amazon sets cookies on the first request. # Dropping those cookies resets your session trust score. session = requests.Session() session.headers.update(HEADERS)
Header Order Is a Real Signal
Python's dict preserves insertion order in Python 3.7 and later. The order above mirrors the order Chrome sends these headers. Scrambling the order produces a different fingerprint profile. If you copy this into a different structure, verify the order is preserved in the request wire format.

Why Sec-Fetch Headers Matter Specifically

The Sec-Fetch-* headers were introduced in Chrome 76 and are sent by every Chromium-based browser on every navigation. They tell the server the context of the request: is this a top-level navigation? Is it from the same site? Was it user-initiated? Amazon's WAF specifically checks for Sec-Fetch-Dest: document and Sec-Fetch-Site: none on direct URL navigations. Requests missing these headers are trivially identifiable as non-browser traffic.


Part 4 Implementation

The Proxy Layer: Why Datacenter Fails on Amazon and What to Use

Once you understand the four detection layers, the proxy choice is straightforward. You need an IP whose ASN passes Layer 1, which means a consumer ISP ASN, not a datacenter ASN. Datacenter proxies hit 20-40% success rates on Amazon product pages because their ASN is flagged before a single header is processed. Residential proxies reach 85-95% with correct headers.

Success Rate Reality by Proxy Type on Amazon

Proxy Type Amazon Product Pages Search Results Review Pages Session / Login
Datacenter 20-40% 15-35% 10-25% Near 0%
Residential (rotating) 85-95% 80-92% 78-90% Moderate
ISP (static residential) 88-95% 85-92% 83-91% High (stable IP)

Success rate ranges sourced from independent proxy benchmarks: Proxyway Proxy Market Research 2024 and Bright Data proxy comparison (2025). Rates reflect correct header configuration; results vary by IP pool quality and request rate.

Rotating vs. Sticky Sessions for Amazon

Rotating residential proxies assign a new IP on each request or on a configurable timer. This is correct for stateless scraping where you do not need to maintain session context between requests. But here's the thing: several Amazon page types do require session continuity.

Review pages, "Customers also bought" sections, and any page that requires cookie state from a prior navigation need a consistent IP for the duration of that logical session. Rotating mid-session resets your trust score and triggers re-evaluation at Layer 4. The rule is: rotate between distinct scraping tasks, not within a session.

Proxy Integration with Session Management
The code below wraps a requests.Session() with residential proxy credentials, exponential backoff on 503, and a human-like delay between requests. This is the production pattern, not the tutorial pattern.
Python - Session + Proxy + Retry (Production Pattern)
import requests, time, random from typing import Optional class AmazonScraper: def __init__(self, proxy_user: str, proxy_pass: str): self.session = requests.Session() self.session.headers.update(HEADERS) # HEADERS from Section 4 self.proxies = { "http": f"http://{proxy_user}:{proxy_pass}@residential.torchproxies.com:8080", "https": f"http://{proxy_user}:{proxy_pass}@residential.torchproxies.com:8080", } self.session.proxies.update(self.proxies) def get(self, url: str, retries: int = 3) -> Optional[requests.Response]: for attempt in range(retries): # Human-like delay: 2-5 seconds between requests time.sleep(random.uniform(2.0, 5.0)) try: resp = self.session.get(url, timeout=15) if resp.status_code == 200: return resp if resp.status_code == 503: # Exponential backoff on 503 time.sleep((2 ** attempt) + random.uniform(0, 1)) continue except requests.RequestException as e: if attempt == retries - 1: raise return None

Test Your Actual Amazon Success Rate Free

Run your scraper against your real target before spending on infrastructure. See what layer is failing before you fix it.

Claim Free Trial

No credit card required  Â·  Cancel anytime


Scraping Amazon Product Pages: Full Working Code

Setup

Terminal - Install dependencies
pip install requests beautifulsoup4 lxml httpx

Parsing a Product Detail Page

The code below extracts title, price, rating, review count, and availability from any Amazon product page URL. Every field uses a fallback chain so that when Amazon changes its HTML in an A/B test, you get None instead of a crash.

Python - extract_product.py (BeautifulSoup)
from bs4 import BeautifulSoup def extract_product(html: str) -> dict: soup = BeautifulSoup(html, "lxml") # Title: ID selector is stable. Class-based titles change frequently. title_el = ( soup.select_one("#productTitle") or soup.select_one("#title span") or soup.select_one("h1.a-size-large span") ) title = title_el.get_text(strip=True) if title_el else None # Price: Amazon shows price in multiple elements depending on context price_el = ( soup.select_one(".a-price .a-offscreen") or soup.select_one("#priceblock_ourprice") or soup.select_one("#priceblock_dealprice") or soup.select_one('[data-a-color="price"] .a-offscreen') ) price = price_el.get_text(strip=True) if price_el else None # Rating rating_el = ( soup.select_one('span[data-hook="rating-out-of-text"]') or soup.select_one("i.a-icon-star span.a-icon-alt") ) rating = rating_el.get_text(strip=True) if rating_el else None # Review count reviews_el = ( soup.select_one("#acrCustomerReviewText") or soup.select_one('span[data-hook="total-review-count"]') ) reviews = reviews_el.get_text(strip=True) if reviews_el else None # Availability avail_el = soup.select_one("#availability span") availability = avail_el.get_text(strip=True) if avail_el else None return { "title": title, "price": price, "rating": rating, "review_count": reviews, "availability": availability, } # Use the AmazonScraper from Section 5 to get the HTML def scrape_asin(scraper, asin: str) -> dict: url = f"https://www.amazon.com/dp/{asin}" resp = scraper.get(url) if resp is None: return {"asin": asin, "error": "request_failed"} data = extract_product(resp.text) data["asin"] = asin return data
Selector Fragility Warning
Amazon runs continuous A/B tests across its UI. Auto-generated class names like a-color-base.a-text-bold change on every deploy cycle. Never write a scraper with a single class-based selector and no fallback. ID selectors and data-hook attribute selectors are Amazon's most stable targeting points.

Exporting to CSV and JSON

Python - export helpers
import json, csv def save_json(data: list[dict], path: str = "amazon_products.json"): with open(path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) def save_csv(data: list[dict], path: str = "amazon_products.csv"): if not data: return with open(path, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=data[0].keys()) writer.writeheader() writer.writerows(data)

Part 5 Scaling & Troubleshooting

Scaling Up: Async Scraping with httpx

Running 500 product pages synchronously with a 3-second delay between each takes over 25 minutes. The same list with 5 concurrent async requests takes around 5 minutes. The trick is keeping concurrency within limits that do not trigger behavioral scoring.

Python - async_scraper.py (httpx + asyncio)
import asyncio, httpx, random from typing import List # Reuse HEADERS from Section 4 async def scrape_asin_async( client: httpx.AsyncClient, asin: str, sem: asyncio.Semaphore ) -> dict: url = f"https://www.amazon.com/dp/{asin}" async with sem: # Randomised delay inside the semaphore slot await asyncio.sleep(random.uniform(2.0, 5.0)) try: resp = await client.get(url, timeout=15) if resp.status_code == 200: data = extract_product(resp.text) # from Section 6 data["asin"] = asin return data return {"asin": asin, "error": f"http_{resp.status_code}"} except httpx.RequestError: return {"asin": asin, "error": "request_error"} async def scrape_batch(asins: List[str], proxy_url: str) -> List[dict]: # Max 5 concurrent requests: safe for rotating residential pools sem = asyncio.Semaphore(5) async with httpx.AsyncClient( headers=HEADERS, proxies={"all://": proxy_url}, follow_redirects=True, ) as client: tasks = [scrape_asin_async(client, asin, sem) for asin in asins] return await asyncio.gather(*tasks) # Entry point if __name__ == "__main__": PROXY = "http://user:[email protected]:8080" asins = ["B098FKXT8L", "B07ZPKN6YR", "B09G9HD6PD"] results = asyncio.run(scrape_batch(asins, PROXY)) save_json(results)
Concurrency Sweet Spot
Keep the Semaphore limit at 3-7 for Amazon. Each concurrent "slot" is operating from the same proxy session. Higher concurrency compresses the time gap between requests on the same session, which directly feeds Layer 4 behavioral scoring. Test your specific proxy pool's concurrency tolerance during your trial before running production volumes.

Scraping Amazon Search Results and Building a Product Pipeline

Most scraping guides only show product detail pages. A real workflow starts with a search query and works down to individual products. Use the [data-asin] attribute selector on search result pages. It is Amazon's most stable targeting point and survives HTML restructuring.

Amazon Search URL Parameters

URL Structure - Amazon Search
# Base URL for Amazon search results https://www.amazon.com/s?k=SEARCH+TERM&page=2 # Key parameters: k # Search query (URL-encoded) page # Page number (typically 1-20, about 400 total results max) rh # Refinement: category node, price range, star rating s # Sort: relevanceblender, price-asc-rank, review-rank # Build search URLs in Python import urllib.parse def build_search_url(query: str, page: int = 1) -> str: params = urllib.parse.urlencode({"k": query, "page": page}) return f"https://www.amazon.com/s?{params}"

Extracting ASINs and Product Data from Search Pages

Python - parse_search_results()
from bs4 import BeautifulSoup def parse_search_results(html: str) -> list[dict]: soup = BeautifulSoup(html, "lxml") products = [] # data-asin is the most stable selector on search pages. # It is present on every product card and survives redesigns. items = soup.select("[data-asin]:not([data-asin=''])") for item in items: asin = item.get("data-asin") title_el = item.select_one("h2 a span") or item.select_one("h2 span.a-text-normal") price_el = item.select_one("span.a-price .a-offscreen") rating_el = item.select_one("span.a-icon-alt") count_el = item.select_one("span.a-size-base.s-underline-text") products.append({ "asin": asin, "title": title_el.get_text(strip=True) if title_el else None, "price": price_el.get_text(strip=True) if price_el else None, "rating": rating_el.get_text(strip=True) if rating_el else None, "review_count": count_el.get_text(strip=True) if count_el else None, }) # Remove cards without a title (ads, banners, empty slots) return [p for p in products if p["title"]]

Once you have a list of ASINs from the search results, feed them directly into the scrape_batch() function from Section 7 to pull full product detail data. This two-stage approach separates the rate strategies: you can hit search pages less frequently (they are heavier pages and more closely monitored) while running product detail pages at your full async concurrency.


When You Actually Need Playwright

Use Playwright for review pages with lazy loading, search results that require scrolling to trigger JavaScript rendering, or any page where the content you need is not in the initial HTML. Do not use it for standard product detail pages. Adding 150-300MB RAM and 1-2 seconds per launch for data that is already in the static HTML is waste. Note: even Playwright is detectable without additional configuration. The setup below addresses the most common detection signals.

Python - playwright_amazon.py
from playwright.sync_api import sync_playwright import time, random def scrape_reviews_playwright(asin: str, proxy: dict) -> list[dict]: with sync_playwright() as p: browser = p.chromium.launch( headless=True, proxy=proxy, args=["--disable-blink-features=AutomationControlled"], ) ctx = browser.new_context( user_agent=( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/122.0.0.0 Safari/537.36" ), viewport={"width": 1920, "height": 1080}, locale="en-US", ) page = ctx.new_page() # Block images/fonts to reduce bandwidth and fingerprint noise page.route("**/*.{png,jpg,jpeg,gif,svg,woff,woff2}", lambda r: r.abort()) url = f"https://www.amazon.com/product-reviews/{asin}" page.goto(url, wait_until="domcontentloaded", timeout=30000) time.sleep(random.uniform(1.5, 3.0)) reviews = [] cards = page.locator('[data-hook="review"]').all() for card in cards: body_el = card.locator('[data-hook="review-body"]') title_el = card.locator('[data-hook="review-title"]') rating_el = card.locator('[data-hook="review-star-rating"]') reviews.append({ "title": title_el.text_content() if title_el.count() > 0 else None, "rating": rating_el.text_content() if rating_el.count() > 0 else None, "body": body_el.text_content() if body_el.count() > 0 else None, }) browser.close() return reviews # Proxy config for Playwright proxy_config = { "server": "http://residential.torchproxies.com:8080", "username": "YOUR_USERNAME", "password": "YOUR_PASSWORD", }
Playwright Installation Note
After pip install playwright, run playwright install chromium to download the browser binary. Headless mode works on servers but requires the system dependencies listed in Playwright's documentation for Linux environments. The --disable-blink-features=AutomationControlled flag removes the navigator.webdriver property that headless browsers expose by default.

Troubleshooting: Every Failure Mode and Its Fix

Check the response status code and body content first. A 503 with no body means Layer 1 or 2. A 200 with a "Robot Check" page means Layer 3. A 200 with prices missing means JavaScript rendering. Match your symptom below and skip straight to the fix.

503 on the very first request Your IP's ASN resolved to a datacenter range. The block happened before your headers were evaluated. Fix: switch to a residential proxy with a consumer ISP ASN.
200 response but "Robot Check" page in HTML Headers are incomplete or in non-browser order. TLS fingerprint mismatch at Layer 2 is also possible. Fix: use the full 12-header stack from Section 4 in exact order. If still failing, use Playwright.
Works for 10-50 requests then blocks Layer 4 behavioral scoring threshold hit. Your IP's session reputation degraded. Fix: reduce concurrency, increase random delays (3-6 seconds), rotate IPs more aggressively.
200 response but prices show as None Price element is JavaScript-rendered for this ASIN (common on high-demand items where Amazon varies the buybox display). Fix: use Playwright for this specific page type, or check data-a-color="price" as the selector fallback.
CAPTCHAs appearing on every request Layer 4 behavioral pattern flagged. Amazon is soft-blocking before hard-blocking. Fix: longer delays, sticky sessions, warm up with a homepage request before product pages.
Works locally but fails on a server Your VPS or cloud compute IP is in a datacenter ASN range. Fix: route all outbound scraper traffic through residential proxies. Never scrape Amazon directly from EC2 or GCP instances.
Selectors returning None for known products Amazon deployed an HTML change in an A/B test. The element exists but the selector no longer matches. Fix: inspect the current live HTML for that ASIN and update your fallback chain.
Search pages work but product detail pages 503 Amazon applies stricter ASN checks on product detail pages than on search result pages. Fix: use Premium Residential proxies specifically for detail page requests.

Scraping Amazon with Python: The Approach That Actually Holds

Scraping Amazon with Python is not a solved problem you can copy from a three-year-old tutorial. Amazon's detection runs four independent layers, and a fix that patches one layer while leaving the others untouched only buys you another 20 requests before the pattern accumulates again.

The approach that holds at scale is not about clever code. It is about matching the right tool to the right layer: residential proxies at the IP layer, the complete header stack at the HTTP layer, human-like timing at the behavioral layer, and selector fallback chains at the parsing layer. Get those four things right and most of the frustration goes away.

The one thing to remember: The 503 is not a scraper problem. It is an IP problem. Before debugging your BeautifulSoup selectors, check whether your IP passes Layer 1 by testing with a residential proxy. If it does, then work down through the header stack and timing. Fix the layers in order, not at random.

For production price monitoring or market intelligence workflows that need reliable throughput, TorchProxies Premium Residential handles the proxy layer so your engineering time goes into the data pipeline, not connection management. For login-based or session-persistent workflows, ISP Proxies give you the stable static IP identity that rotating pools cannot provide.

For a deeper look at the fingerprint detection layer (Layer 2) and how TLS impersonation fits into a full anti-detection stack, the companion guide on how to avoid fingerprint detection covers the JA3/JA4 mechanism and the tool landscape in detail.


Your Scraper Is Only as Reliable as the IP Behind It

TorchProxies residential and ISP proxies pass Amazon's ASN check. Test against your actual target before committing to any plan.

See Your Real Success Rate Free

No credit card required  Â·  24/7 support

Frequently Asked Questions

Scraping publicly visible Amazon data like product titles, prices, ratings, and review text is legal in most jurisdictions. The 9th Circuit's 2022 hiQ v. LinkedIn ruling confirmed that accessing publicly available data is not a CFAA violation. Amazon's Terms of Service prohibit automated scraping, which is a contractual issue, not a criminal one. The consequence of violating ToS is that Amazon may block your access, not prosecution. Never scrape login-protected pages, personal buyer data, or internal APIs.
Amazon uses AWS WAF Bot Control which evaluates four layers in sequence: IP/ASN reputation (datacenter IPs flagged before headers are read), TLS fingerprinting using JA3 and JA4 signatures, HTTP header fingerprinting (header presence and order), and behavioral ML scoring. Python's requests library is detectable at Layers 2 and 3 simultaneously. Adding a User-Agent only partially addresses Layer 3 and leaves Layers 1, 2, and 4 unchanged. You need residential proxies, the full header stack, and human-like timing to address all four layers.
It depends on your use case. For static product data (title, price, rating, availability) at low-to-medium volume, requests plus BeautifulSoup with the full header stack and residential proxies works well. For bulk scraping at scale, httpx with asyncio reduces wall-clock time by 60-80% without additional detection risk. For lazy-loaded review pages or scroll-based search results, Playwright with a residential proxy is required. There is no single best choice; match the tool to the page type.
Not for most product data. Amazon product detail pages deliver title, price, rating, and availability in the initial HTML response for the majority of ASINs. Playwright is genuinely needed for review pages with lazy loading, search results that require scrolling to render product cards, and any page where the data you need is injected by JavaScript after the initial page load. Using a headless browser for everything adds significant RAM overhead and latency without benefit when the data is already in the static HTML.
There is no published safe rate. Amazon's WAF behavioral scoring suggests keeping per-IP request rates below 10-15 per minute with randomized 3-6 second delays. With a rotating residential proxy pool, you can maintain higher aggregate throughput by distributing across many IPs. The key signal AWS WAF uses at Layer 4 is per-IP request velocity. Aggregate volume across many IPs is less of an issue than per-IP request timing regularity.
Yes, but review pages require more care than product detail pages. They use lazy loading for additional reviews and require JavaScript execution for pagination in some cases. Use Playwright for review scraping. Residential proxies are required because review pages are more closely monitored by AWS WAF. Keep session continuity within a single product's review pages before rotating IPs. Review pages also load more slowly than product pages, so adjust your timeout values accordingly.