# Amazon Product Scraper - Prices, ASIN, BSR & Reviews (`harvestlab/amazon-scraper`) Actor

Amazon product scraper for prices, ASINs, ratings, review counts, BSR, availability, sellers, images, and rank alerts across 19 marketplaces. Built for seller research, competitor monitoring, price tracking, and MCP connector alerts.

- **URL**: https://apify.com/harvestlab/amazon-scraper.md
- **Developed by:** [harvestlab](https://apify.com/harvestlab) (community)
- **Categories:** E-commerce, AI
- **Features:** Limited permissions
- **Stats:** 479 total users, 23 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 product scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Amazon Product Scraper - Prices, ASIN, BSR & Reviews

Use this Amazon product scraper to extract product search results and product pages across supported markets, including prices, ASINs, ratings, review counts, Best Sellers Rank, availability, seller data, images, and rank-change alerts.

**Best first run**

```json
{
  "searchQuery": "wireless earbuds",
  "amazonDomain": "amazon.com",
  "maxProducts": 10,
  "fetchDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "US"
  }
}
```

Use this actor for seller research, competitor monitoring, price tracking, keyword checks, ASIN research, and product intelligence. Start small because Amazon is anti-bot-sensitive; increase detail fetching after a basic search works.

Related Amazon workflows: use [Amazon Keyword Rank Tracker](https://apify.com/harvestlab/amazon-keyword-rank-tracker) for share-of-shelf and ASIN rank reporting.

> **No cookies, no OAuth, no API keys (for the scraping)** - runs on public data only. AI keys are optional and only needed when `enableAiAnalysis: true`.

**Track Amazon prices and Best Sellers Rank across 19 marketplaces at a base price of $0.005/product**. At base rates, product-only pricing for 25 products once per day is about $3.75/month (30 days); with 25 successful BSR annotations and three successful webhook alerts per day, the same watch is about $11.43/month. These are product, BSR and webhook event subtotals, not all-in run quotes. Start and default-dataset events listed in the Pricing tab also apply. Optional AI and external-provider charges are separate; check your run cost before scaling.

**Plan discounts:** the product event is $0.004 on Starter, $0.0035 on Scale and $0.003 on Business. These discounts follow your Apify plan, not the number of products in a run. Unless a plan is named, prices and examples below use the Free/no-discount base rates; see the [full pricing table](#pricing).

**Repeatable product/price export:** Put known ASIN URLs in `productUrls`, keep `enableAiAnalysis` off, and schedule the same input in Apify after a successful first run. Product rows expose `asin`, `price`, `currency`, `availability`, `rating`, `reviews_count`, `url`, and `scraped_at`. Export as CSV, JSON, or Excel. The default dataset can include summaries or diagnostics: filter product rows before loading a price feed, and never treat missing prices as zero. Add `trackBestsellerRank` only when you need paid cross-run rank history. Use the self-contained scheduled export recipe below; no private repository access is required.

### Amazon Product Scraper

Search Amazon by keyword or provide direct product URLs to extract product titles, ASINs, prices, ratings, review counts, availability, seller names, images, badges, delivery data, and marketplace currency. Use it for competitor monitoring, price checks, catalog research, affiliate research, and product intelligence.

### Amazon ASIN Scraper

Use direct product URLs or `/dp/ASIN` links when you already know the products to track. Detail runs can return brand, seller, availability, category breadcrumbs, feature bullets, image galleries, Best Sellers Rank, and BSR movement fields.

For a ready-made one-ASIN BSR check, use [Track Amazon BSR for one ASIN](https://apify.com/harvestlab/amazon-scraper/examples/monitor-amazon-prices-and-reviews). It is prefilled for one product URL with detail extraction and rank tracking enabled.

### Scheduled product-only export

Save a successful known-product input as a private task, schedule it daily, and
configure a run-succeeded webhook/integration in Apify. Your authorized runner
should fetch the exact completed run's default dataset as a JSON item array,
not a moving latest dataset, then filter products before loading a price feed.
Here is a small offline CSV mapper for an existing `dataset.json` export:

```python
import csv
import json
import re
from decimal import Decimal, InvalidOperation
from urllib.parse import urlsplit

with open("dataset.json", encoding="utf-8") as source:
    items = json.load(source)
if not isinstance(items, list) or len(items) > 10000:
    raise ValueError("Expected at most 10,000 exported items")
products = {}
for item in items:
    if not isinstance(item, dict) or item.get("diagnostic_item") or item.get("block_detected"):
        continue
    if item.get("result_type") not in (None, "product_data") or item.get("error"):
        continue
    asin, currency = str(item.get("asin") or "").upper(), str(item.get("currency") or "").upper()
    try:
        price = Decimal(str(item.get("price")))
        url = item.get("url") or item.get("product_url") or ""
        if not isinstance(url, str):
            continue
        market = (urlsplit(url).hostname or "").removeprefix("www.")
        source_conflict = False
        for field in ("url", "product_url"):
            if not item.get(field):
                continue
            if not isinstance(item[field], str):
                source_conflict = True
                break
            parsed = urlsplit(item[field])
            path_asins = re.findall(
                r"/(?:dp|gp/product|product)/([A-Z0-9]{10})(?=/|$)",
                parsed.path, flags=re.IGNORECASE,
            )
            if ((parsed.hostname or "").removeprefix("www.") != market
                    or any(value.upper() != asin for value in path_asins)):
                source_conflict = True
                break
    except (InvalidOperation, ValueError, TypeError):
        continue
    if source_conflict:
        continue
    if not (price.is_finite() and price > 0 and len(asin) == 10 and asin.isalnum()
            and len(currency) == 3 and currency.isalpha()
            and market in ("amazon.com", "amazon.ca", "amazon.com.mx", "amazon.com.br",
                           "amazon.co.uk", "amazon.de", "amazon.fr", "amazon.es", "amazon.it",
                           "amazon.nl", "amazon.pl", "amazon.se", "amazon.co.jp", "amazon.in",
                           "amazon.com.au", "amazon.ae", "amazon.sa", "amazon.sg", "amazon.com.tr")
            and item.get("title") and item.get("scraped_at")):
        continue
    key = market + ":" + asin
    products[key] = dict(item, product_key=key)
if not products:
    raise ValueError("No price-bearing products; preserve the previous good feed")
fields = ["product_key", "asin", "title", "price", "currency", "availability",
          "rating", "reviews_count", "url", "scraped_at"]
with open("products.csv", "x", newline="", encoding="utf-8") as target:
    writer = csv.DictWriter(target, fieldnames=fields)
    writer.writeheader()
    for item in products.values():
        row = {key: item.get(key, "") for key in fields}
        row["url"] = item.get("url") or item.get("product_url")
        for key, value in row.items():
            if isinstance(value, str) and value.lstrip().startswith(("=", "+", "-", "@")):
                row[key] = "'" + value
        writer.writerow(row)
```

Use a new private output folder per completed run. This mapper never invokes an
Actor, overwrites a prior CSV, or writes to a third-party destination. In your
sheet/database, deduplicate webhook replays by `product_key + scraped_at`;
marketplace/currency differences must remain explicit. If a run blocks or
exports no qualified products, retain the last good snapshot and mark it stale,
not zero-priced. An existing successful process exit alone is not valid product
data. Test the destination mapping with your own credentials before production.

Rows are also excluded when a recognized `/dp/`, `/gp/product/`, or `/product/`
URL identifies a different ASIN, or populated `url` and `product_url` fields
disagree on marketplace. Matching source URLs retain their original spelling,
tracking parameters and fragments. Legacy links without a recognized ASIN path
remain usable; this consistency check does not prove price accuracy or freshness.

### Amazon Price Tracker

Schedule product-only runs for the same ASINs and export each observed price and currency, or use the LangGraph percentage comparison below with your accepted prior observations. Enable `trackBestsellerRank` only for optional BSR history; `alertWebhookUrl` sends rank-movement alerts, while the graph returns price-alert flags for your own notification workflow.

### Why buyers use this Amazon scraper

- **BSR rank tracking over time** - instead of a single-run snapshot, scheduled runs emit `rank_change`, `rank_peak`, and `rank_movement` deltas. Schedule it once to build a lightweight rank monitor.
- **Real-time webhook alerts** - POST to Slack, Zapier, n8n, Make, Discord, or any HTTP endpoint when a tracked ASIN crosses your rank threshold.
- **Anti-bot resilience** - residential proxy support, per-request access rotation, and WAF-aware retries improve success on Amazon's current marketplace protections.
- **5-provider AI competitive intelligence** - switch between OpenRouter (300+ models), Anthropic Claude, Google Gemini, OpenAI GPT, or self-hosted Ollama. AI auto-switches to a rank-movement narrator when BSR tracking is on.
- **19 Amazon marketplaces** - US, CA, MX, BR, UK, DE, FR, ES, IT, NL, PL, SE, JP, IN, AU, AE, SA, SG, TR. Each domain returns native currency. European decimal-comma prices (`1.299,50 €`) parse correctly.
- **Clear pay-per-result pricing** - $0.005/product, $0.05 flat for AI analysis, and $0.002 per webhook delivered. A failed scrape has no product event and a failed webhook has no alert event, but start and any saved-dataset events can still apply.

### What it does

Scrapes Amazon product listings from search results or direct product URLs and extracts prices, ASINs, ratings, review counts, images, seller info, Prime eligibility, Best Sellers Rank, and more - across 19 Amazon marketplace domains worldwide.

The actor goes beyond a point-in-time snapshot. Enable `trackBestsellerRank` and scheduled runs emit `rank_now`, `rank_previous`, `rank_change`, `rank_peak`, and `rank_movement` ("rising" / "falling" / "steady" / "new") on every item. Pair with Apify's built-in scheduler to build BSR history per ASIN with no external database required.

Set `alertWebhookUrl` and the actor becomes a real-time rank-alert service: it POSTs a compact JSON payload to your URL (Slack, Discord, Zapier, Make, n8n, or any HTTP endpoint) whenever a tracked ASIN's absolute rank change crosses your threshold - no polling, fire-and-forget, billed only on successful delivery at $0.002/webhook.

Optionally generate AI-powered competitive pricing analysis via OpenRouter, Anthropic, Google AI, OpenAI, or Ollama with market positioning, price sweet spots, brand comparisons, and actionable insights. When BSR tracking is also enabled, the AI report switches to a rank-movement narrator that explains why products are climbing or falling.

**Supported Amazon domains:**

| Domain | Country | Currency |
|--------|---------|----------|
| `amazon.com` | United States | USD |
| `amazon.ca` | Canada | CAD |
| `amazon.com.mx` | Mexico | MXN |
| `amazon.com.br` | Brazil | BRL |
| `amazon.co.uk` | United Kingdom | GBP |
| `amazon.de` | Germany | EUR |
| `amazon.fr` | France | EUR |
| `amazon.es` | Spain | EUR |
| `amazon.it` | Italy | EUR |
| `amazon.nl` | Netherlands | EUR |
| `amazon.pl` | Poland | PLN |
| `amazon.se` | Sweden | SEK |
| `amazon.co.jp` | Japan | JPY |
| `amazon.in` | India | INR |
| `amazon.com.au` | Australia | AUD |
| `amazon.ae` | United Arab Emirates | AED |
| `amazon.sa` | Saudi Arabia | SAR |
| `amazon.sg` | Singapore | SGD |
| `amazon.com.tr` | Turkey | TRY |

Each domain returns results in local currency. The `currency` field in each output item reflects the currency actually rendered on the page - if your proxy's geo doesn't match the domain, Amazon may localize prices to the proxy's currency.

### Features

- **Search scraping**: Enter any keyword and scrape product results with automatic pagination across multiple pages
- **Direct URL scraping**: Provide specific Amazon product URLs for detailed extraction including description, features, and seller info
- **19 Amazon domains**: Scrape any marketplace with localized pricing, availability, and language
- **Rich product data**: Title, price, original price, discount %, rating, review count, brand, Prime status, images, badges, delivery info
- **Detail page extraction**: Full product descriptions, bullet-point features, availability status, seller info, and category breadcrumbs
- **Multi-language support**: Rating and review count parsing works across English, German, French, Spanish, Italian, Dutch, and Japanese pages
- **European decimal-comma parsing**: Prices like `19,99 €` or `1.299,50 €` are correctly normalized to floats (19.99, 1299.50)
- **Dynamic currency detection**: The `currency` field reflects the currency actually displayed on the page
- **Best Sellers Rank extraction**: Each product emits `bestseller_rank` (integer) and `bestseller_category`
- **BSR time-series tracking (unique)**: `trackBestsellerRank: true` persists each ASIN's rank across runs and emits `rank_now`, `rank_previous`, `rank_change`, `rank_peak`, `rank_movement` deltas, plus a per-run `rank_movement_summary` item with top-5 climbers and top-5 fallers. Cross-run state is scoped per Amazon domain
- **BSR alert webhooks (unique)**: `alertWebhookUrl` + `alertRankThreshold` (default 50) fires an HTTP POST per ASIN whose `|rank_change| >= threshold`. Payload includes ASIN, title, URL, marketplace, rank_previous/now/change/pct, movement, peak, category, price, and scraped_at. Fire-and-forget with 5s timeout, 1 retry, and `X-Actor-Run-Id` header for traceability
- **Hi-res gallery images**: Detail pages emit an `images` list of up to 12 alt-view images, upscaled to master asset by stripping CDN size specifiers
- **Anti-bot resilience**: Residential proxy support with per-request access rotation and WAF-aware retries on search result pages
- **AI competitive analysis**: OpenRouter (300+ models), Anthropic (Claude), Google AI (Gemini), OpenAI (GPT), or Ollama (self-hosted). Market overview, pricing sweet spots, brand rankings, and opportunity identification
- **Pay-per-event pricing**: $0.005/product at the base rate, with plan discounts and separate start, saved-dataset and enabled optional events; no Actor rental fee

When BSR tracking is enabled, each ASIN can emit `rank_change`, `rank_peak`, and `rank_movement` compared with the previous run. Optional HTTP POST alerts notify your endpoint when a threshold is crossed.

### Use Cases

- **E-commerce sellers** - Monitor product pricing across 19 markets and identify MAP violations. *Typical setup*: 100 ASINs scraped daily across 3 domains = ~$1.50/day.
- **Brand & rank monitoring** - Get a Slack alert when a tracked ASIN's BSR crosses your threshold. At base rates, 25 products with 25 successful BSR annotations daily total $11.25 over 30 days before webhook, start and saved-dataset events.
- **Dropshippers & arbitrage** - Find profitable products by cross-domain price-arbitrage scans (e.g. amazon.com vs amazon.de). The `discount_percentage` and `bestseller_rank` fields surface high-margin candidates fast.
- **Market & category research** - Pull 500-product samples per category and analyze brand share, price distribution, sponsored-vs-organic ratio, and review momentum. AI competitive report turns the raw rows into a 1-page market brief.
- **Affiliate marketers** - Discover top-rated, high-review-count products by category for "best of 2026" listicles. Sort by `avg_review` or `newest` to surface fresh-but-validated picks.
- **MAP & price-policy enforcement** - Daily BSR + price scans across resellers; flag any price below your floor and route the alert to a procurement Slack channel via the webhook.
- **Procurement teams** - Benchmark supplier quotes against live Amazon list prices at scale; the `currency` + `availability` fields make cross-border procurement decisions defensible.
- **AI / ML training data** - Pull thousands of product titles, bullet features, and descriptions for embedding fine-tunes or product-categorization models - no API quotas, just pay-per-row.
- **BI / dashboard pipelines** - Push CSV / JSON straight into Google Sheets, Snowflake, or BigQuery via Apify's 2,000+ integrations. The `scraped_at` ISO timestamp makes time-series joins trivial.

### Input

#### Search Parameters

- **Search Query** (`searchQuery`): Keywords to search for (e.g., "wireless headphones", "laptop stand", "organic coffee")
- **Product URLs** (`productUrls`): Direct Amazon product URLs - provide specific ASINs or product pages for detailed extraction. Overrides search query when provided
- **Amazon Domain** (`amazonDomain`): Which Amazon marketplace to scrape. Options: amazon.com (default), amazon.ca, amazon.com.mx, amazon.com.br, amazon.co.uk, amazon.de, amazon.fr, amazon.es, amazon.it, amazon.nl, amazon.pl, amazon.se, amazon.co.jp, amazon.in, amazon.com.au, amazon.ae, amazon.sa, amazon.sg, amazon.com.tr
- **Max Products** (`maxProducts`): Maximum number of products to scrape (1-100, default: 10). Higher values paginate through multiple search result pages
- **Sort By** (`sortBy`): Sort order for search results - relevance (default), price-asc, price-desc, avg-rating, newest
- **Fetch Product Details** (`fetchDetails`): Visit individual product detail pages to enrich results with brand, availability, description, features, and seller info (default: false for faster first runs)

#### BSR Time-Series & Alerts

- **Track Best Sellers Rank** (`trackBestsellerRank`): Persist each product's BSR across runs and emit rank delta fields (`rank_now`, `rank_previous`, `rank_change`, `rank_peak`, `rank_movement`). Forces `fetchDetails: true`. Default: false
- **BSR Alert Webhook URL** (`alertWebhookUrl`): POST a JSON payload to this URL whenever a tracked ASIN's absolute `rank_change` crosses the threshold. Works with Slack, Zapier, Make, n8n, Discord, or any HTTP endpoint. Auto-enables `trackBestsellerRank`
- **MCP Output Connectors** (`outputConnectors`): Send the same qualifying BSR alerts to authorized Slack, Notion, GitHub, Sheets, CRM, or other MCP connector tools. Auto-enables `trackBestsellerRank`
- **Connector Target** (`connectorAlertTarget`): Optional destination such as a Slack channel, Notion database ID, sheet name, table, or CRM list
- **BSR Alert Threshold** (`alertRankThreshold`): Minimum absolute rank_change required to fire a webhook (default: 50). Lower = more alerts

#### AI Analysis Settings

- **Enable AI Analysis** (`enableAiAnalysis`): Toggle AI-powered competitive analysis on or off (default: off). Requires an LLM API key when enabled
- **LLM Provider** (`llmProvider`): Choose between OpenRouter (recommended - 300+ models), Anthropic (Claude), Google AI (Gemini), OpenAI (GPT), or Ollama (self-hosted)
- **OpenRouter API Key** (`openrouterApiKey`): Your API key from openrouter.ai
- **Anthropic API Key** (`anthropicApiKey`): Your API key from console.anthropic.com
- **Google AI API Key** (`googleApiKey`): Your API key from aistudio.google.com
- **OpenAI API Key** (`openaiApiKey`): Your API key from platform.openai.com
- **Model Override** (`llmModel`): Optional custom model selection. Leave empty for defaults (`google/gemini-3.8-flash` for OpenRouter, `claude-sonnet-5-5` for Anthropic, `gemini-3.8-flash` for Google AI, `gpt-4o-mini` for OpenAI, `llama3.1` for Ollama)

#### Proxy Settings

**Residential proxy is strongly recommended** - Amazon's AWS WAF frequently blocks datacenter IPs, especially on non-US marketplaces.

The actor combines per-request access rotation with automatic WAF-aware retries on search result pages, but residential IPs substantially improve success rates across all domains.

**Key proxy tip**: Set `apifyProxyCountry` to match your target domain. Amazon localizes prices and content based on the requesting IP's geography - a US proxy on amazon.co.jp will return USD prices instead of JPY.

| Target domain | Recommended `apifyProxyCountry` |
|---|---|
| amazon.com | `US` |
| amazon.ca | `CA` |
| amazon.com.mx | `MX` |
| amazon.com.br | `BR` |
| amazon.co.uk | `GB` |
| amazon.de | `DE` |
| amazon.fr | `FR` |
| amazon.es | `ES` |
| amazon.it | `IT` |
| amazon.nl | `NL` |
| amazon.pl | `PL` |
| amazon.se | `SE` |
| amazon.co.jp | `JP` |
| amazon.in | `IN` |
| amazon.com.au | `AU` |
| amazon.ae | `AE` |
| amazon.sa | `SA` |
| amazon.sg | `SG` |
| amazon.com.tr | `TR` |

**amazon.fr WAF limitation**: `amazon.fr` enforces a stricter AWS WAF than other EU domains. Prefer direct product URL scraping (`/dp/ASIN`) over keyword search on this domain. If search fails, fall back to `amazon.de` or `amazon.co.uk` - they share most of the EU catalog.

### Output

#### Search Results

Each product from search results includes:

| Field | Description |
|-------|-------------|
| `asin` | Amazon Standard Identification Number |
| `title` | Product title |
| `price` | Current selling price |
| `original_price` | Original/list price (if discounted) |
| `discount_percentage` | Computed discount percent when both prices are present |
| `currency` | Currency code as displayed on the page (USD, EUR, GBP, JPY, etc.) |
| `rating` | Average customer rating (out of 5) |
| `reviews_count` | Number of customer reviews |
| `is_prime` | Whether the product is Prime eligible |
| `is_sponsored` | Whether the listing is a sponsored ad |
| `in_stock` | Boolean - whether the item is currently purchasable |
| `image_url` | Main product image URL |
| `url` | Direct link to the product page |
| `brand` | Brand or manufacturer name |
| `badge` | Special badges (Best Seller, Amazon's Choice) |
| `delivery_info` | Delivery date information |
| `page_number` | Which search result page the product appeared on |
| `amazon_domain` | The marketplace domain scraped |
| `search_query` | The search term used |
| `scraped_at` | ISO 8601 timestamp |

#### Product Detail Pages

When scraping direct product URLs or enabling `fetchDetails: true` on search, additional fields are extracted:

| Field | Description |
|-------|-------------|
| `description` | Full product description text |
| `features` | Bullet-point feature list |
| `availability` | Stock availability status |
| `seller` | Third-party seller name (if not Amazon) |
| `category` | Product category breadcrumb path |
| `images` | List of hi-res gallery image URLs (up to 12) |
| `bestseller_rank` | Amazon's "Best Sellers Rank" position in its category |
| `bestseller_category` | Category label associated with `bestseller_rank` |

#### BSR Time-Series Fields

When `trackBestsellerRank: true`, these fields are added to every product:

| Field | Description |
|-------|-------------|
| `rank_now` | Current BSR (same as `bestseller_rank`) |
| `rank_previous` | BSR from the previous run (`null` on first run) |
| `rank_change` | `rank_previous - rank_now`. Positive = climbed, negative = fell |
| `rank_peak` | Best (lowest-number) rank ever observed for this ASIN |
| `rank_movement` | `"new"` / `"rising"` / `"falling"` / `"steady"` |
| `rank_previous_scraped_at` | ISO timestamp of the previous snapshot |
| `hours_since_last_snapshot` | Hours between previous snapshot and this run |

#### Sample Output

```json
{
    "asin": "B0CHX3QBCH",
    "title": "Apple iPhone 15 Pro, 128GB, Natural Titanium",
    "price": 999.00,
    "original_price": 1099.00,
    "discount_percentage": 9.1,
    "currency": "USD",
    "rating": 4.6,
    "reviews_count": 2431,
    "is_prime": true,
    "is_sponsored": false,
    "in_stock": true,
    "image_url": "https://m.media-amazon.com/images/I/81Os1SBkFpL._AC_SL1500_.jpg",
    "url": "https://www.amazon.com/dp/B0CHX3QBCH",
    "brand": "Apple",
    "badge": "Amazon's Choice",
    "delivery_info": "FREE delivery Tomorrow",
    "availability": "In Stock",
    "seller": "Amazon.com",
    "features": ["6.1-inch Super Retina XDR display", "A17 Pro chip", "48MP Main camera", "Titanium design"],
    "category": "Electronics > Cell Phones & Accessories > Unlocked Cell Phones",
    "bestseller_rank": 4,
    "bestseller_category": "Unlocked Cell Phones",
    "description": "The iPhone 15 Pro features a titanium design with the A17 Pro chip...",
    "amazon_domain": "amazon.com",
    "search_query": "iphone 15 pro",
    "scraped_at": "2026-04-18T10:15:00Z"
}
```

#### BSR Rank Movement Summary

A `rank_movement_summary` rollup item is pushed at the end of each run when a prior snapshot exists:

```json
{
    "type": "rank_movement_summary",
    "amazon_domain": "amazon.com",
    "search_query": "air fryer",
    "snapshot_at": "2026-04-23T12:00:00+00:00",
    "previous_snapshot_at": "2026-04-22T12:00:00+00:00",
    "climbers_count": 3,
    "fallers_count": 2,
    "top_climbers": [{"asin": "B0C...", "rank_previous": 812, "rank_now": 417, "rank_change": 395, "title": "...", "bestseller_category": "..."}],
    "top_fallers":  [{"asin": "B0D...", "rank_previous": 42,  "rank_now": 210, "rank_change": -168, "title": "...", "bestseller_category": "..."}]
}
```

#### Screenshots / Sample Output

What you'll see in `Apify Console -> Storage -> Default dataset -> JSON` after a daily-scheduled run:

**Sample BSR rank-movement output** (one row per tracked ASIN):

```json
{
    "asin": "B0CHX3QBCH",
    "title": "Keychron Q1 Pro QMK/VIA Wireless Mechanical Keyboard",
    "rank_now": 417,
    "rank_previous": 812,
    "rank_change": 395,
    "rank_change_pct": 48.65,
    "rank_movement": "rising",
    "rank_peak": 390,
    "bestseller_category": "Electronics > Computer Keyboards",
    "scraped_at": "2026-04-23T12:00:00+00:00"
}
```

**Sample Slack alert payload** (POSTed to `alertWebhookUrl` when `|rank_change| >= alertRankThreshold`):

```json
{
    "asin": "B0CHX3QBCH",
    "title": "Keychron Q1 Pro QMK/VIA Wireless Mechanical Keyboard",
    "url": "https://www.amazon.com/dp/B0CHX3QBCH",
    "marketplace": "amazon.com",
    "rank_previous": 812,
    "rank_now": 417,
    "rank_change": 395,
    "rank_change_pct": 48.65,
    "rank_movement": "rising",
    "price": 199.00,
    "currency": "USD",
    "scraped_at": "2026-04-23T12:00:00+00:00"
}
```

Run a single ASIN to see this output live.

#### Pricing

This actor uses pay-per-event pricing. Starter (Bronze), Scale (Silver) and Business (Gold) receive 20%, 30% and 40% off the ordinary events below. Platinum and Diamond use the Business/Gold amounts. Discounts depend on your Apify subscription plan, not a per-run quantity threshold. The [live Pricing tab](https://apify.com/harvestlab/amazon-scraper/pricing) shows your applicable prices.

All amounts are USD per event. The default-dataset fee and legacy $5 bundle stay flat across plans.

| Event | Free / base | Starter / Bronze | Scale / Silver | Business / Gold | When it applies |
| --- | ---: | ---: | ---: | ---: | --- |
| `product-scraped` | $0.005 | $0.004 | $0.0035 | $0.003 | Each product successfully scraped with data |
| `rank-tracked` | $0.01 | $0.008 | $0.007 | $0.006 | Each ASIN annotated with BSR time-series deltas when `trackBestsellerRank: true` |
| `alert-dispatched` | $0.002 | $0.0016 | $0.0014 | $0.0012 | Each BSR-alert webhook successfully POSTed with a 2xx response |
| `connector-alert-dispatched` | $0.002 | $0.0016 | $0.0014 | $0.0012 | Each qualifying BSR alert successfully delivered through an MCP output connector |
| `ai-analysis-completed` | $0.05 | $0.04 | $0.035 | $0.03 | One-time charge when AI competitive analysis is generated |
| `apify-actor-start` | $0.00005 | $0.00004 | $0.000035 | $0.00003 | Once when the Actor starts |
| `apify-default-dataset-item` | $0.00001 | $0.00001 | $0.00001 | $0.00001 | Each item saved to the default dataset |
| `skyfire-bundle-1000-products` | $5 | $5 | $5 | $5 | Legacy listed event; see the qualification below |

A failed scrape requests no product event, and a failed webhook or connector attempt requests no corresponding alert event. Start and saved-dataset events can still apply to a run without useful products. Optional external AI-provider charges are separate.

##### Skyfire payment note

The legacy `skyfire-bundle-1000-products` event remains listed at $5 on every plan. A prepaid workflow that replaces ordinary product, rank or AI events with this bundle has not been verified; do not use its name as a quote for included output. Ordinary runs use the event table above. [Apify's Skyfire integration](https://docs.apify.com/integrations/skyfire) requires at least $5 of available PAY-token balance, not a $5 minimum charge per invocation.

**Base-rate event subtotal examples:** These exclude start and default-dataset events and any external-provider charges. Multiply the listed product, rank, alert and AI subtotals by 0.8 on Starter, 0.7 on Scale or 0.6 on Business; add the applicable start and dataset fees separately.

- 100 products = **$0.50**
- 100 products + AI analysis = **$0.55**
- 100 products + BSR tracking (60 have BSR) + AI rank report = $0.005 x 100 + $0.01 x 60 + $0.05 = **$1.15**
- 25-product daily watchlist with 3 webhook alerts/day = $0.005 x 25 + $0.01 x 25 + $0.002 x 3 = **$0.381/day ~ $11.43/month**

**Export formats**: JSON, CSV, Excel - plus 2,000+ app integrations via Apify (Google Sheets, Slack, Zapier, Make, Amazon S3).

There is no separate Actor rental fee. The base product event is $0.005; paid Apify plans qualify for the discounts above. Enabled rank tracking, successful alert delivery, completed AI analysis, startup and saved-dataset events are separately chargeable as listed.

### Quick Start

**Minimum working input** - scrape 10 products from amazon.com with residential proxy:

```json
{
    "searchQuery": "wireless headphones",
    "amazonDomain": "amazon.com",
    "maxProducts": 10,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US"
    }
}
```

**Base-rate event subtotal**: 10 products x $0.005 = **$0.05**, before start and saved-dataset events. Add `enableAiAnalysis: true` for a one-time +$0.05 completed competitive report, plus any external-provider charges. Eligible plan discounts apply as shown above.

#### Scrape specific products by ASIN

```json
{
    "productUrls": [
        "https://www.amazon.com/dp/B0C8S5Y3KV",
        "https://www.amazon.com/dp/B09XS7JWHH"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": ["RESIDENTIAL"]
    }
}
```

#### Search with AI competitive analysis

```json
{
    "searchQuery": "standing desk",
    "amazonDomain": "amazon.com",
    "maxProducts": 50,
    "enableAiAnalysis": true,
    "llmProvider": "openrouter",
    "openrouterApiKey": "sk-or-...",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": ["RESIDENTIAL"]
    }
}
```

AI analysis examines all scraped products and generates a structured report including: market overview, price sweet spot ranges, top-5 best-value picks, brand share distribution, and opportunity identification. Output is returned as structured JSON alongside the product data.

#### Track Best Sellers Rank over time

Run once per day on a schedule - each ASIN will emit its rank delta vs. the previous run:

```json
{
    "searchQuery": "air fryer",
    "amazonDomain": "amazon.com",
    "maxProducts": 50,
    "trackBestsellerRank": true,
    "enableAiAnalysis": true,
    "llmProvider": "openrouter",
    "openrouterApiKey": "sk-or-...",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US"
    }
}
```

First run: every ASIN emits `rank_movement: "new"` and null deltas (baseline established). Second run onward: every ASIN emits `rank_change`, `rank_movement`, and a historical `rank_peak`. The AI report switches to a rank-movement narrator listing top climbers and fallers with plausible drivers.

#### Real-time BSR alerts via webhook

```json
{
    "searchQuery": "mechanical keyboard",
    "amazonDomain": "amazon.com",
    "maxProducts": 25,
    "trackBestsellerRank": true,
    "alertWebhookUrl": "https://hooks.zapier.com/hooks/catch/12345/abcde",
    "alertRankThreshold": 100,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US"
    }
}
```

Webhook payload shape (one POST per qualifying ASIN):

```json
{
    "asin": "B0CHX3QBCH",
    "title": "Keychron Q1 Pro QMK/VIA Wireless Mechanical Keyboard",
    "url": "https://www.amazon.com/dp/B0CHX3QBCH",
    "marketplace": "amazon.com",
    "rank_previous": 812,
    "rank_now": 417,
    "rank_change": 395,
    "rank_change_pct": 48.65,
    "rank_movement": "rising",
    "rank_peak": 390,
    "bestseller_category": "Electronics > Computer Keyboards",
    "price": 199.00,
    "currency": "USD",
    "hours_since_last_snapshot": 24.03,
    "scraped_at": "2026-04-23T12:00:00+00:00"
}
```

Each webhook includes an `X-Actor-Run-Id` header for correlating alerts with the run in the Apify Console. Only ASINs whose **absolute** `rank_change` is >= `alertRankThreshold` fire (both climbers and fallers). A per-run cap of 250 webhooks prevents runaway misconfigurations.

#### MCP Quickstart - call this actor from Claude / Cursor / ChatGPT

Open Apify's hosted MCP server with this actor selected:

```text
https://mcp.apify.com?tools=harvestlab/amazon-scraper
```

Then prompt the agent:

> "Use the harvestlab/amazon-scraper actor on Apify to find the top 10 wireless earbuds under $100 on amazon.com sorted by review count, with rating and ASIN. Push the results back as JSON."

Through Apify MCP, the agent will discover the actor schema, generate the right input, run it, and pipe the typed output back into your conversation.

### Use with AI agents

amazon-scraper output is agent-ready: typed product dicts with BSR rank, time-series deltas, currency, and AI sentiment across 19 marketplaces - wrap it as a single tool and your product-research, arbitrage, or competitive-intel agent gets cross-marketplace BSR + price intelligence that commodity Amazon scrapers don't expose. See Apify's [`actor-templates`](https://github.com/apify/actor-templates) - specifically [`js-langchain`](https://github.com/apify/actor-templates/tree/master/templates/js-langchain) and [`js-langgraph-agent`](https://github.com/apify/actor-templates/tree/master/templates/js-langgraph-agent) - for end-to-end skeletons.

#### LangChain tool wrapper

These runner examples use the typed results in `apify-client` 3.3/3.4 and your
existing LangChain/LangGraph installation. Start with one known ASIN. Each call
starts one paid Actor run, bounded here to ten URLs, 180 seconds and a $0.10
event-charge cap; platform usage is separate. BSR, AI and receivers are off.

```python
from datetime import timedelta
from decimal import Decimal
import os
from urllib.parse import urlsplit
from apify_client import ApifyClient
from langchain_core.tools import tool

MARKETS = {"amazon." + suffix for suffix in (
    "com ca com.mx com.br co.uk de fr es it nl pl se co.jp in com.au ae sa sg com.tr"
).split()}
client = ApifyClient(os.environ["APIFY_TOKEN"])

@tool
def track_amazon_products(productUrls: list[str], amazonDomain: str = "amazon.com") -> list[dict]:
    """Fetch one bounded product batch; the dataset may also contain diagnostics."""
    if amazonDomain not in MARKETS or not 1 <= len(productUrls) <= 10:
        raise ValueError("Use a canonical supported amazonDomain and 1-10 product URLs")
    for url in productUrls:
        parsed = urlsplit(url)
        if (parsed.scheme != "https" or parsed.username or parsed.password or parsed.port
                or (parsed.hostname or "").removeprefix("www.") != amazonDomain):
            raise ValueError("Use HTTPS product URLs in the selected marketplace")
    run = client.actor("harvestlab/amazon-scraper").call(
        run_input={"productUrls": productUrls, "amazonDomain": amazonDomain,
                   "maxProducts": len(productUrls), "fetchDetails": True,
                   "trackBestsellerRank": False, "enableAiAnalysis": False,
                   "alertWebhookUrl": "", "outputConnectors": [],
                   "proxyConfiguration": {"useApifyProxy": True, "apifyProxyGroups": ["RESIDENTIAL"]}},
        memory_mbytes=256, run_timeout=timedelta(seconds=180),
        max_total_charge_usd=Decimal("0.10"), restart_on_error=False,
    )
    if run is None or run.status != "SUCCEEDED":
        raise RuntimeError("No completed observation; retain the accepted baseline")
    return list(client.dataset(run.default_dataset_id).iterate_items(limit=100))

## track_amazon_products.invoke({"productUrls": ["https://www.amazon.com/dp/B0CHX3QBCH"],
##                               "amazonDomain": "amazon.com"})
```

#### LangGraph price-tracking node

Run the wrapper block above first. This node compares each valid price with its
previous accepted observation for the same marketplace and ASIN. The threshold
is a **percentage price drop**, not currency units or BSR movement. First
observations seed the candidate baseline without alerts; an explicit observed
zero stays zero, while a prior zero cannot be used as a percentage denominator.

```python
from datetime import datetime
from decimal import InvalidOperation
import re
from typing import TypedDict
from langgraph.graph import StateGraph, START, END

class PriceState(TypedDict):
    asins: list[str]
    amazonDomain: str
    price_drop_percent: float
    baseline: dict[str, dict]
    products: list[dict]
    alerts: list[dict]
    alert: bool
    stale: bool

def price_observation(item: dict, domain: str) -> dict | None:
    try:
        if (not isinstance(item, dict) or item.get("diagnostic_item") or item.get("block_detected")
                or item.get("error") or item.get("result_type") not in (None, "product_data")):
            return None
        asin, currency = str(item.get("asin", "")).upper(), str(item.get("currency", "")).upper()
        price = Decimal(str(item["price"]))
        observed = datetime.fromisoformat(item["scraped_at"].replace("Z", "+00:00"))
        if (item.get("amazon_domain") != domain or not re.fullmatch(r"[A-Z0-9]{10}", asin)
                or not re.fullmatch(r"[A-Z]{3}", currency) or not str(item.get("title") or "").strip()
                or not price.is_finite() or price < 0 or observed.utcoffset() is None):
            return None
        urls = [item[k] for k in ("url", "product_url") if item.get(k)]
        if not urls:
            return None
        for url in urls:
            parsed = urlsplit(url)
            ids = re.findall(r"/(?:dp|gp/product|product)/([A-Z0-9]{10})(?=/|$)", parsed.path, re.I)
            if (parsed.scheme not in {"http", "https"} or (parsed.hostname or "").removeprefix("www.") != domain or parsed.username or parsed.password
                    or parsed.port or any(value.upper() != asin for value in ids)):
                return None
        return dict(item, asin=asin, currency=currency, price=str(price),
                    product_key=domain + ":" + asin, scraped_at=observed.isoformat())
    except (KeyError, ValueError, TypeError, AttributeError, InvalidOperation):
        return None

def track_node(state: PriceState) -> PriceState:
    threshold = Decimal(str(state["price_drop_percent"]))
    if not threshold.is_finite() or not 0 < threshold <= 100:
        raise ValueError("price_drop_percent must be greater than zero and at most 100")
    domain = state["amazonDomain"]
    asins = list(dict.fromkeys(value.upper() for value in state["asins"]))
    if not asins or any(not re.fullmatch(r"[A-Z0-9]{10}", value) for value in asins):
        raise ValueError("Provide known 10-character ASINs")
    baseline, products, alerts, accepted = dict(state.get("baseline", {})), [], [], set()
    try:
        rows = track_amazon_products.invoke({"productUrls": [f"https://www.{domain}/dp/{a}" for a in asins],
                                             "amazonDomain": domain})
    except Exception:
        return {**state, "baseline": baseline, "products": [], "alerts": [], "alert": False, "stale": True}
    for item in rows:
        row = price_observation(item, domain)
        if row is None or row["asin"] not in asins:
            continue
        products.append(row)
        key = row["product_key"]
        old = price_observation(baseline.get(key, {}), domain)
        if old and old["product_key"] != key:
            old = None  # A mis-keyed baseline is not a previous observation of this ASIN.
        if old and (row["currency"] != old["currency"]
                    or datetime.fromisoformat(row["scraped_at"]) <= datetime.fromisoformat(old["scraped_at"])):
            continue  # Keep the accepted observation on currency changes, replays or older data.
        if old and Decimal(old["price"]) > 0:
            drop = (Decimal(old["price"]) - Decimal(row["price"])) * 100 / Decimal(old["price"])
            if drop >= threshold:
                alerts.append({"product_key": key, "previous_price": old["price"], "price": row["price"],
                               "currency": row["currency"], "price_drop_percent": str(drop), "scraped_at": row["scraped_at"]})
        baseline[key] = row
        accepted.add(row["asin"])
    return {**state, "baseline": baseline, "products": products, "alerts": alerts,
            "alert": bool(alerts), "stale": len(accepted) < len(asins)}

graph = StateGraph(PriceState)
graph.add_node("track", track_node)
graph.add_edge(START, "track")
graph.add_edge("track", END)
price_watch = graph.compile()
## result = price_watch.invoke({"asins": ["B0CHX3QBCH"], "amazonDomain": "amazon.com",
##     "price_drop_percent": 10, "baseline": {}, "products": [], "alerts": [], "alert": False, "stale": True})
```

Persist the returned candidate `baseline` in your own authorized store after
review, then pass the accepted map to the next invocation. This graph does not
schedule runs, save state between processes or send notifications. Missing,
blocked, invalid and partial observations retain prior entries; rejected or
missing observations set `stale`. Currency changes require review, never a
cross-currency percentage comparison. Keep marketplace and proxy routing
consistent. The example watches at most ten known ASINs; start with one and
verify your workload. The product-only CSV recipe above remains separate.

### Troubleshooting

**Getting 403 / 429 / CAPTCHA errors**

Amazon's WAF is blocking the request. Steps to resolve:

1. Switch to **RESIDENTIAL** proxy (`apifyProxyGroups: ["RESIDENTIAL"]`) if you're using datacenter IPs.
2. Set `apifyProxyCountry` to match the target domain (e.g. `US` for amazon.com, `DE` for amazon.de). Mismatched geo triggers harsher WAF treatment.
3. Retry the run - WAF posture varies by hour and IP batch. A fresh residential IP from the same country often succeeds.
4. If scraping `amazon.fr`, prefer direct product URLs (`productUrls`) over keyword search - search endpoints on that domain are blocked far more aggressively.

**Prices appearing in the wrong currency**

The proxy IP's geographic location determines which currency Amazon renders, not the domain. Set `apifyProxyCountry` to the matching country code (see the table in the Input section above).

**No products returned (empty dataset)**

1. Verify the `amazonDomain` matches the locale of your `searchQuery` (e.g. German keyword with `amazon.de`).
2. Test with a simple English keyword like "laptop" on `amazon.com` with `maxProducts: 5` to isolate the issue.
3. Retry with fresh proxy IPs - if the first IP batch is flagged, a new run often succeeds.
4. Check the actor log for WAF / CAPTCHA detection messages. If present, follow the 403/429 steps above.

**Webhooks not firing**

- Webhooks only fire on the **second run onward** - first run establishes the baseline, so there is no prior `rank_change` to evaluate.
- Ensure `trackBestsellerRank: true` is set (or provide `alertWebhookUrl` - it auto-enables BSR tracking).
- Confirm the URL is reachable and returns a 2xx response. Only successful dispatches are billed.
- Check the `bsr_alert_dispatch_summary` item pushed to the dataset for per-webhook results.

**BSR fields are null / rank_movement is "new"**

This is expected on the **first run** - no prior snapshot exists yet. Schedule the actor daily and deltas appear from the second run onward. Note: not all products carry a BSR - Amazon only assigns rank to products with at least one sale.

**amazon.fr returning no results**

`amazon.fr` runs the strictest AWS WAF of all supported EU domains. Workarounds: (1) retry the run, (2) use direct product URLs (`/dp/ASIN`) which succeed more reliably than keyword search, or (3) fall back to `amazon.de` / `amazon.co.uk` / `amazon.it` which share much of the EU catalog.

**amazon.co.jp prices showing in USD/EUR**

Amazon localizes pages based on requesting IP. Set `apifyProxyCountry: "JP"` to get a Japanese exit IP and genuine JPY results.

### Frequently Asked Questions

**Is this an Amazon product scraper?**
Yes. It scrapes Amazon search results and product pages for prices, ASINs, ratings, review counts, availability, seller data, images, Best Sellers Rank, and marketplace currency.

**Can I use it as an Amazon ASIN scraper?**
Yes. Provide direct product URLs or `/dp/ASIN` links to extract known products, or search by keyword and use the returned `asin` field for catalog research and tracking.

**Does it scrape Amazon reviews?**
It returns rating and review-count fields for products. It does not currently extract full individual review text, so use it for review-volume and rating analysis rather than review-body sentiment mining.

**Can it track Amazon prices over time?**
Yes. Schedule recurring runs for the same ASINs and export the resulting price, availability, and BSR fields into your own dashboard or spreadsheet.

**Can it send BSR alerts?**
Yes. Enable `trackBestsellerRank` and set `alertWebhookUrl` to send rank-movement alerts to Slack, Zapier, Make, n8n, Discord, or any HTTP endpoint.

### Legal and Compliance

This actor is provided as a technical tool. Users are responsible for ensuring their use complies with:

- Amazon's Terms of Service and Conditions of Use
- Applicable data protection laws (GDPR, CCPA, etc.)
- Local laws regarding web scraping and data collection

Amazon's Terms of Service restrict automated access to their platform. Use this tool responsibly and at your own risk. This actor does not store or process personal data - it only extracts publicly visible product catalog information.

The developer of this actor is not responsible for any misuse or legal consequences arising from its use.

# Actor input Schema

## `searchQuery` (type: `string`):

Search keyword(s) to find products on Amazon (e.g., 'iPhone 15 Pro', 'mechanical keyboard', 'air fryer').

## `query` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

## `q` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

## `search` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

## `keyword` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

## `searchTerm` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

## `productUrls` (type: `array`):

Direct Amazon product URLs to scrape (alternative to search). Example: `https://www.amazon.com/dp/B0C7BZR4DC` (the B0C7BZR4DC is the ASIN). Overrides search query if both are provided.

## `urls` (type: `array`):

CLI alias for productUrls. Hidden from Console form.

## `links` (type: `array`):

CLI alias for productUrls. Hidden from Console form.

## `amazonDomain` (type: `string`):

Which Amazon country marketplace to scrape. Different marketplaces have different ASINs, pricing in local currency, and product availability.

## `domain` (type: `string`):

CLI alias for amazonDomain. Hidden from Console form.

## `country_domain` (type: `string`):

CLI alias for amazonDomain. Hidden from Console form.

## `maxProducts` (type: `integer`):

Maximum number of products to scrape. Amazon shows ~20 per search page. Start with 10 to test before running larger jobs. Capped at 100 for PPE bill safety.

## `maxItems` (type: `integer`):

Alternative name for Max Products - accepts the same values. Use either maxProducts or maxItems.

## `sortBy` (type: `string`):

Sort order for search results. 'Relevance' (default) is Amazon's algorithmic ranking mixing sales, reviews, and keyword match. 'Price Low/High' sorts by current list price. 'Avg. Customer Review' ranks by star rating (biased toward items with many reviews). 'Newest Arrivals' surfaces recently listed products - best for trend-spotting but noisy. Only applies when using a search query, not direct product URLs.

## `fetchDetails` (type: `boolean`):

Fetch individual product detail pages to enrich results with brand, availability, description, features, and seller info. Adds ~2-3s per product but significantly improves data quality. Keep false for fast search-page-only first runs.

## `includeDetails` (type: `boolean`):

CLI alias for fetchDetails. Hidden from Console form.

## `trackBestsellerRank` (type: `boolean`):

Persist each product's Best Sellers Rank across runs and emit rank_now / rank_previous / rank_change / rank_peak / rank_movement fields on every scraped item. Pair with Apify scheduler to build a BSR history per ASIN. Forces fetchDetails=true. Adds a per-ASIN `rank-tracked` charge ($0.01) on top of the normal product-scraped charge. When combined with Enable AI Analysis, the AI report switches to a rank-movement narrative (why products are climbing or falling). First run establishes the baseline; second run onward emits deltas.

## `alertWebhookUrl` (type: `string`):

POST a compact JSON payload to this URL whenever a tracked ASIN's absolute rank_change crosses the threshold below. Turns the actor into a real-time BSR alert service - pipe into Slack/Discord/Zapier/Make/n8n/your own endpoint. Fire-and-forget: 5s timeout, 1 retry on failure, webhook errors never fail the run. Only fires on second-run-onward (needs a prior snapshot for deltas). Setting this automatically enables trackBestsellerRank. Adds an `alert-dispatched` charge ($0.002) per successful webhook. Leave empty to disable.

## `outputConnectors` (type: `array`):

Optional MCP connectors for BSR rank alerts. Choose Slack, Notion, GitHub, Sheets, CRM, or another compatible connector. When a rank alert qualifies, the actor sends a compact message plus structured payload through each connector. Connector failures never fail the scrape. Billed at $0.002 per successful connector dispatch.

## `connectorAlertTarget` (type: `string`):

Optional connector destination such as a Slack channel, Notion database ID, sheet name, table, or CRM list. Leave empty when the selected connector already has a default target.

## `alertRankThreshold` (type: `integer`):

Minimum absolute rank_change required to fire a webhook. Default 50 means a climb from #200 to #150 (or a drop from #100 to #151) triggers an alert. Lower = more alerts, higher = noisier movers only. Only applies when alertWebhookUrl is set.

## `enableAiAnalysis` (type: `boolean`):

Run AI-powered competitive pricing analysis on scraped products. Hosted providers require your own API key; their charges are separate from Actor fees and depend on model and token usage. When Track Best Sellers Rank is also ON, the AI switches to a rank-movement report (narrates why products are climbing/falling) instead of the default pricing analysis.

## `llmProvider` (type: `string`):

Which AI provider to use for analysis.

## `openrouterApiKey` (type: `string`):

API key for OpenRouter (required if LLM Provider = OpenRouter). Get one at https://openrouter.ai/keys. Or set the OPENROUTER_API_KEY env var on the actor.

## `anthropicApiKey` (type: `string`):

API key for Anthropic Claude (required if LLM Provider = Anthropic). Get one at https://console.anthropic.com/settings/keys. Or set the ANTHROPIC_API_KEY env var on the actor.

## `googleApiKey` (type: `string`):

API key for Google AI / Gemini (required if LLM Provider = Google AI). Get one at https://aistudio.google.com/app/apikey. Or set the GOOGLE_API_KEY env var on the actor.

## `openaiApiKey` (type: `string`):

API key for OpenAI (required if LLM Provider = OpenAI). Get one at https://platform.openai.com/api-keys. Or set the OPENAI_API_KEY env var on the actor.

## `ollamaBaseUrl` (type: `string`):

Base URL for a self-hosted Ollama server (only used if LLM Provider = Ollama). Default: http://localhost:11434. Install Ollama at https://ollama.com/download.

## `llmModel` (type: `string`):

Specific model to use. Leave empty for the provider default (google/gemini-3.8-flash for OpenRouter, claude-sonnet-5-5 for Anthropic, gemini-3.8-flash for Google AI, gpt-4o-mini for OpenAI, llama3.1 for Ollama).

## `proxyConfiguration` (type: `object`):

Proxy settings. Residential proxies recommended for Amazon. Tip: for consistent pricing/currency, match the proxy country to your target amazonDomain (e.g. `apifyProxyCountry=US` for amazon.com, `DE` for amazon.de, `JP` for amazon.co.jp) - Amazon localizes prices by visitor IP.

## Actor input object example

```json
{
  "searchQuery": "iPhone 15 Pro",
  "amazonDomain": "amazon.com",
  "maxProducts": 10,
  "sortBy": "relevance",
  "fetchDetails": false,
  "trackBestsellerRank": false,
  "alertRankThreshold": 50,
  "enableAiAnalysis": false,
  "llmProvider": "openrouter",
  "ollamaBaseUrl": "http://localhost:11434",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `datasetOutput` (type: `string`):

Dataset containing scraped Amazon product items, BSR rank-movement summaries, BSR alert dispatch records, and AI competitive analysis records.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQuery": "iPhone 15 Pro",
    "amazonDomain": "amazon.com",
    "maxProducts": 10,
    "ollamaBaseUrl": "http://localhost:11434",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("harvestlab/amazon-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchQuery": "iPhone 15 Pro",
    "amazonDomain": "amazon.com",
    "maxProducts": 10,
    "ollamaBaseUrl": "http://localhost:11434",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("harvestlab/amazon-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQuery": "iPhone 15 Pro",
  "amazonDomain": "amazon.com",
  "maxProducts": 10,
  "ollamaBaseUrl": "http://localhost:11434",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call harvestlab/amazon-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,harvestlab/amazon-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/JyRjxUswjrRheOdmh/builds/HJwYtdsR5EQt9nSaw/openapi.json
