DEV Community

Greta
Greta

Posted on

Scraping Amazon Prices at Scale: The Survival Architecture

Scraping Amazon Prices at Scale: The Survival Architecture

Amazon is the final boss of scraping targets: no free price API, one of the most sophisticated anti-bot systems deployed, and product pages that vary by marketplace, region, and even time of day. Which is exactly why price monitoring on Amazon is a paid service everywhere you look.

This is the architecture I've run in production for months — the request patterns, the session strategy, and the failure modes, with runnable Python.

Why Naive Scrapers Die in Minutes

Amazon's bot detection operates on accumulated signals: IP reputation (datacenter IPs start guilty), request volume per IP, session coherence, and mechanical timing. The failure progression is predictable:

  • Minute 1: everything works. Your scraper feels invincible.
  • Hour 1: soft CAPTCHAs on a percentage of responses (api-services-support@amazon.com pages)
  • Day 1: hard 403s from your IP ranges
  • Day 3: your cloud provider's entire subnet is flagged, and even a brand-new server inherits the block

The lesson: by the time you notice blocking, the system flagged you long ago. Architecture has to be right from request one.

The Core Pattern: One Product, One Identity

The pattern that survives is simple to state: each product gets its own short-lived sticky session from a residential IP.

for each product:
    1. create a sticky session (one real ISP IP, holds up to 30 min)
    2. brief "arrival" — fetch the storefront homepage first (1 request)
    3. fetch the product page, extract price/availability
    4. release the session; random delay; next product with a NEW session
Enter fullscreen mode Exit fullscreen mode

Each session is a complete "shopping trip": arrives, browses briefly, looks at one product, leaves. No IP accumulates volume, no session lacks browsing shape, and a flagged session costs you exactly one product before being discarded.

The Implementation

import time, random, uuid
import requests
from bs4 import BeautifulSoup

PROXY_HOST = "gate.thordata.com"
PROXY_PORT = 9000
PROXY_USER, PROXY_PASS = "your-username", "your-password"
GEO = "us"  # marketplace you're monitoring — keep IP country aligned

def make_session() -> requests.Session:
    """Fresh session bound to one sticky residential IP."""
    sid = uuid.uuid4().hex[:12]
    user = f"{PROXY_USER}-session-{sid}-country-{GEO}"
    url = f"http://{user}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"
    s = requests.Session()
    s.proxies = {"http": url, "https": url}
    s.headers.update({
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 (KHTML, like Gecko) "
                      "Chrome/126.0.0.0 Safari/537.36",
        "Accept-Language": "en-US,en;q=0.9",
    })
    return s

def scrape_asin(s: requests.Session, asin: str) -> dict | None:
    try:
        s.get(f"https://www.amazon.com/", timeout=30)          # arrival
        time.sleep(random.uniform(0.8, 2.0))
        r = s.get(f"https://www.amazon.com/dp/{asin}", timeout=30)
        if r.status_code != 200 or "api-services-support" in r.text:
            return None                                          # CAPTCHA/blocked
        soup = BeautifulSoup(r.text, "html.parser")
        price = soup.select_one("span.a-price span.a-offscreen")
        if not price:
            return None
        return {
            "asin": asin,
            "price": price.get_text(strip=True),
            "availability": (soup.select_one("#availability") or {}).get(
                "text", "").strip() if soup.select_one("#availability") else None,
        }
    except requests.RequestException:
        return None

def monitor(asins: list[str]) -> list[dict]:
    results = []
    for asin in asins:
        for attempt in range(3):
            data = scrape_asin(make_session(), asin)  # NEW session each retry
            if data:
                results.append(data)
                print(f"OK   {asin}: {data['price']}")
                break
            time.sleep(random.uniform(3, 8))          # backoff, then fresh IP
        time.sleep(random.uniform(1.5, 4.0))          # breathing room
    return results
Enter fullscreen mode Exit fullscreen mode

The details that matter: retries spin up new sessions (retrying a flagged identity compounds the flag); delays are randomized everywhere; and the marketplace (GEO) matches the proxy country so you get the market's actual prices, not geo-redirected ones.

Cost Arithmetic

Residential proxy traffic is billed per GB; Amazon product pages run 300–600KB including images you don't fetch (we only pull HTML). Rules of thumb:

  • 1,000 products ≈ 0.3–0.6 GB
  • 500 products hourly ≈ ~1 GB/day
  • At Thordata's rotating rate ($0.65/GB), a 500-product hourly monitor costs roughly $0.65/day

Against that: one missed repricing window on a competitive listing can cost more before lunch. New accounts can validate the whole pipeline on the free trial traffic; code thor020 takes 10% off paid plans.

Scheduling Discipline

Continuous monitoring is where good scrapapers go bad:

  • Poll every 2–4 hours, not minutes — price strategy rarely needs more
  • Add startup jitter (random.uniform(0, 900)) so runs never start at :00
  • Rotate your watchlist order between runs — identical sequences are patterns
  • Track your block rate; if retries exceed ~5%, slow down before Amazon escalates

The Failure Table

Symptom Cause Fix
503 + CAPTCHA page flagged session or IP fresh sticky session per retry (built in above)
Wrong currency proxy geo ≠ marketplace align GEO with marketplace
Works hours, then dies per-IP accumulation fresh session per product, lower frequency
Price parses as None page layout changed or buybox variant extend selector set, log raw HTML on failure

Beyond Price

The same page carries stock status, buybox seller, review counts, and ratings — parse them in the same pass; they're free once you've paid for the fetch. Store snapshots with timestamps: price history is where repricing strategies actually come from, and (asin, price, ts) in SQLite is enough to start.

The code above is ~80 lines. The architecture — session-per-product, geo-aligned IPs, randomized pacing — is the part that keeps it running for months instead of minutes.


Disclosure: this pipeline runs on Thordata's residential proxies (100M+ IPs, sticky sessions, country/city targeting; rotating from $0.65/GB). The architecture works with any provider offering equivalent session control.

Top comments (0)