# Amazon Search Scraper + Ratings Histogram (`diopside/amazon-search`) Actor

Drop-in compatible with junglee/free-amazon-product-scraper — 12x cheaper and reliable. Amazon search results as JSON: ASIN, title, price, list price, stars, exact ratings count, image, position, page — plus the 1-5 star ratings histogram on every product. No login, no captcha solver.

- **URL**: https://apify.com/diopside/amazon-search.md
- **Developed by:** [DIOPSIDE AI](https://apify.com/diopside) (community)
- **Categories:** E-commerce, Other, Agents
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 products

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Search Scraper + Ratings Histogram

**Drop-in compatible with `junglee/free-amazon-product-scraper` and `igolaizola/amazon-search` — 12× cheaper, and it doesn't fall over.** Paste your existing input, change the actor ID, keep your field names.

Scrapes Amazon search results into one record per product — ASIN, title, price, list price, stars, ratings count, image, sponsored flag, position and page — and adds the thing that is genuinely hard to get anywhere else: **the full 1–5 star ratings histogram on every product**, plus the *exact* ratings count instead of the rounded "1.6K" the search page shows.

No login. No captcha-solver add-on. No residential proxy.

***

### Why this one

The two actors this replaces are not slightly flaky — they are broken often enough that you notice. Numbers from the Apify Store, 19 September 2026:

| Actor | users / 30 d | rating | **failed runs / 30 d** | price |
|---|---|---|---|---|
| `junglee/Amazon-crawler` | 2 022 | 4.16★ | **8.4 %** | $0.005 / result + extras |
| `junglee/free-amazon-product-scraper` | 481 | 4.97★ | **17.4 %** | **$0.012 / result** |
| `igolaizola/amazon-search` | 71 | 3★ | **38.5 %** | $0.001 / result |
| **this actor** | — | — | — | **$0.001 / product** |

Roughly one run in six fails on the most popular "free" Amazon product scraper, and better than one in three on the cheap search one. If you are running these on a schedule, that is the difference between a dataset and a gap in your dataset.

#### What makes it reliable

- **A real Chrome TLS fingerprint.** Amazon fingerprints the TLS handshake, not just the IP. Plain `curl`/`requests`/`httpx` get a captcha from *any* IP tier; a genuine Chrome handshake gets the real page from an ordinary datacenter proxy. That is why this actor needs no captcha solver and no $8/GB residential traffic.
- **Amazon's bot walls arrive with a `200`.** The "Click the button below to continue shopping" interstitial is a ~2 KB body served with a 2xx status. Scrapers that only check `response.status_code` treat it as success and finish "SUCCEEDED" with an empty dataset. This actor classifies any undersized 2xx body as a block, rotates to a fresh proxy session and cookie jar, and retries.
- **Amazon A/B-tests its own markup.** Every field is tried through several selectors and degrades to `null` rather than raising, so a class-name change costs you one field, not the run. A listing page that parses zero cards is retried once on a clean session before it is believed.
- **Partial failures are reported, never hidden.** The run status message says how many products came from how many start URLs, how many carry a histogram, how much traffic was used and how many sessions had to rotate. The run only fails if *nothing* was produced.

#### What you get that the incumbents don't

| | this actor | `free-amazon-product-scraper` | `igolaizola/amazon-search` |
|---|---|---|---|
| 1–5★ histogram | ✅ `ratingsHistogram` + `starsBreakdown` | ✅ `starsBreakdown` (requires the $0.012 detail fetch) | ❌ |
| **Exact** ratings count | ✅ | ✅ (same) | ❌ rounded "1.6K" |
| Price per product | **$0.001** | $0.012 | $0.001 |
| Search position / page | ✅ | ✅ `categoryPageData` | ❌ |
| Multi-marketplace | ✅ 21 domains, verified on `.com` and `.co.uk` | ✅ | via URL |
| Needs a captcha solver | ❌ | offered as an option | ❌ |

The histogram is normally only on the 2.5 MB product page. This actor reads Amazon's own **~21 KB ratings-summary fragment** instead — the same numbers, 1/120th of the bandwidth. That is the whole reason it can charge $0.001 where a detail-page scraper has to charge $0.012.

***

### Input

Every field below is optional except that you must give *something* to search.

| Field | Type | Default | Notes |
|---|---|---|---|
| `categoryUrls` | array of `{url}` | `[{"url": "https://www.amazon.com/s?k=mechanical+keyboard"}]` | Search, category or `/dp/<ASIN>` URLs. **Same name and shape as `junglee/free-amazon-product-scraper`.** Filters in the URL (`rh=`, `s=price-asc-rank`, department nodes) are preserved; only `page` is replaced. |
| `searchTerms` | array of strings | `[]` | Plain keywords, searched on `domain`. |
| `search` | string | — | A single search URL. **`igolaizola/amazon-search` compatibility.** |
| `maxItems` | integer | — | Alias of `maxItemsPerStartUrl`. **`igolaizola` compatibility.** |
| `maxItemsPerStartUrl` | integer | `100` | Products per start URL. `0` = everything Amazon returns. |
| `maxSearchPagesPerStartUrl` | integer | `20` | Listing pages per start URL (16 results per page). |
| `domain` | select | `amazon.com` | 21 marketplaces. Start URLs keep their own marketplace, so a `amazon.co.uk` URL is never priced in USD. |
| `scrapeRatingsHistogram` | boolean | `true` | Adds `ratingsHistogram`, `starsBreakdown` and the exact `ratingsTotal`. Costs ~20 % more traffic. |
| `proxyConfiguration` | proxy | `{"useApifyProxy": true}` | Datacenter is enough for `amazon.com` — the Chrome fingerprint is what gets you in, not the IP tier. |
| `residentialFallback` | boolean | `true` | Non-US marketplaces wall off the datacenter tier, so a blocked marketplace is retried once on a residential IP **in its own country**, automatically. `amazon.com` never triggers it and never pays for it. Set `false` to pin a run to one tier. |

Unknown fields from the incumbents (`useCaptchaSolver`, `scrapeProductDetails`, `maxProductVariantsAsSeparateResults`) are accepted and ignored, so a pasted input never errors.

#### How many results can I get?

Amazon serves **16 results per listing page** and caps a search at 7–20 pages depending on the term, so one search term yields roughly **112–320 products**. To go wider, split the term across category nodes rather than asking for more pages — the same limitation the incumbents document.

***

### Output

One record per product. Fields in the first block carry `junglee/free-amazon-product-scraper`'s **exact names and shapes** — including `price` as a `{value, currency}` object — so existing downstream code keeps working.

```json
{
  "asin": "B08Z6X4NK3",
  "title": "Logitech G413 SE Full-Size Mechanical Gaming Keyboard - Black",
  "url": "https://www.amazon.com/dp/B08Z6X4NK3",
  "brand": null,
  "price":     { "value": 71.49, "currency": "$" },
  "listPrice": { "value": 89.99, "currency": "$" },
  "stars": 4.5,
  "starsBreakdown": { "5star": 0.77, "4star": 0.1, "3star": 0.06, "2star": 0.02, "1star": 0.05 },
  "reviewsCount": 1652,
  "thumbnailImage": "https://m.media-amazon.com/images/I/61thTSgqWOL._AC_UY218_.jpg",
  "isAmazonChoice": true,
  "amazonChoiceText": "Overall Pick",
  "inStock": null,
  "categoryPageData": {
    "categoryUrl": "https://www.amazon.com/s?k=mechanical+keyboard&page=1",
    "isSponsored": false,
    "productPosition": 1,
    "pageNumber": 1
  },

  "ratingsHistogram": { "5": 77, "4": 10, "3": 6, "2": 2, "1": 5 },
  "ratingsTotal": 1652,
  "reviewsCountIsApproximate": false,
  "rating": 4.5,
  "priceValue": 71.49,
  "listPriceValue": 89.99,
  "currency": "USD",
  "thumbnailUrl": "https://m.media-amazon.com/images/I/61thTSgqWOL._AC_UY218_.jpg",
  "isPrime": null,
  "isSponsored": false,
  "position": 1,
  "page": 1,
  "searchTerm": "mechanical keyboard",
  "domain": "amazon.com",
  "totalSearchResults": 37186
}
```

`ratingsHistogram` is percentages (`{"5": 77}` = 77 % of ratings are 5★); `starsBreakdown` is the identical data as fractions, in the shape the incumbent already emits. Use whichever your pipeline expects.

#### Honest nulls

A missing value is always `null` — never a guess and never a crash:

- **`brand`** — Amazon's current search card carries no brand element on most page variants. The line under the title is the *feature blurb*, not the brand, so it is not passed off as one.
- **`isPrime`** — logged-out search pages often show a "Join Prime" delivery upsell on *every* card, which distinguishes nothing. When the page variant has no real per-item Prime badge, this is `null` rather than a uniform `false`.
- **`inStock`** — only stated on product pages, so `null` for search results.
- **`reviewsCountIsApproximate`** — `true` means `reviewsCount` came from the card's rounded "1.6K". With `scrapeRatingsHistogram` on it is exact and this is `false`.

***

### Switching from `junglee/free-amazon-product-scraper`

1. Change the actor ID to `diopside/amazon-search`.
2. That's it. `categoryUrls`, `maxItemsPerStartUrl` and `maxSearchPagesPerStartUrl` mean the same thing here, and `title`, `url`, `asin`, `brand`, `price`, `listPrice`, `stars`, `starsBreakdown`, `reviewsCount`, `thumbnailImage`, `isAmazonChoice` and `categoryPageData` come back with the same names and shapes.

What changes, deliberately:

- **Cost drops from $0.012 to $0.001 per product.** This actor reads search listings plus the tiny ratings fragment; it does not open each product's 2.5 MB detail page.
- **Detail-only fields are not returned** — `description`, `features`, `attributes`, `bestsellerRanks`, `seller`, `offers`, `variantDetails` and the rest of the 57-field detail record live on the product page. If you need those, you want a product-detail scraper, not this one. If you only ever used the listing fields, you were paying 12× for pages you threw away.
- `currency` is added as an ISO code (`"USD"`), because `price.currency` is the symbol `"$"` — which is also CAD, AUD and MXN.

### Switching from `igolaizola/amazon-search`

Change the actor ID; `search` and `maxItems` work unchanged. You additionally get the ratings histogram, exact ratings counts, list prices, search position/page, Amazon's Choice badges and 21 marketplaces — at the same $0.001 per product, with a 38.5 % failure rate replaced by a proxy ladder that actually retries.

***

### Marketplaces

`amazon.com` runs on Apify's datacenter proxy: the Chrome TLS fingerprint is what gets you in, not the IP tier.

Every non-US marketplace we measured (`amazon.co.uk`, `amazon.de`) refuses the **whole** datacenter tier — including exit IPs already inside GB and DE — with a ~2 KB "continue shopping" stub served on HTTP 200/202. Session rotation cannot fix a tier-wide wall, so the actor escalates that marketplace once to a residential IP in its own country and retries the start URL. You don't configure anything; the run's status message says `escalated to RESIDENTIAL for amazon.co.uk` when it happens.

Verified end-to-end on the platform: `amazon.com` (16/16 products with histograms, no escalation) and `amazon.co.uk` (10/10 with histograms, GBP prices, automatic escalation). The other 19 domains use the same code path but have not each been run.

***

### Pricing

Pay per event:

| Event | Price |
|---|---|
| Actor start | $0.00005 |
| **Product** | **$0.001** |

You are charged once per product record pushed to the dataset. 1 000 products ≈ **$1.00**. No platform-usage surcharge, no per-offer or per-ZIP-code extras.

### Use cases

- **Price and assortment monitoring** — track a category's prices, list prices and ranking positions daily.
- **Review-quality screening** — the histogram separates a 4.3★ built on a healthy spread from a 4.3★ that is 70 % five-star and 20 % one-star. A single average hides that; this is the field most competitor research is missing.
- **Keyword / share-of-search research** — `position`, `page`, `isSponsored` and `totalSearchResults` per term show who owns a search term and how much of page one is paid.
- **Product sourcing** — filter a category by rating distribution and ratings volume before committing to a detail scrape.

### Legality and scope

Public product listings only. This actor does not read reviewer names, reviewer profiles, review text, or anything behind an Amazon login — only the aggregate rating data Amazon publishes on the product card. Scraping publicly available product data such as prices and ratings is legal; see Apify's [is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/).

### Support

Found a field that stopped parsing after an Amazon layout change? Open an issue on the actor — layout drift is treated as a bug and is what the daily canary run exists to catch.

# Actor input Schema

## `categoryUrls` (type: `array`):

Amazon search URLs, category URLs or product (`/dp/<ASIN>`) URLs. Any filters you put in the URL (`rh=`, `s=price-asc-rank`, department nodes) are preserved; only `page` is replaced. Same field name and shape as `junglee/free-amazon-product-scraper`, so an existing input works unchanged.

## `searchTerms` (type: `array`):

Plain keywords, searched on the marketplace chosen in `domain`. Easier than building URLs by hand; combined with `categoryUrls` if you give both.

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

A single Amazon search URL. Accepted for drop-in compatibility with `igolaizola/amazon-search`.

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

Alias of `maxItemsPerStartUrl`, accepted for drop-in compatibility with `igolaizola/amazon-search`. 0 = every result Amazon will return.

## `maxItemsPerStartUrl` (type: `integer`):

Maximum number of products to scrape per start URL. 0 = every result Amazon will return. Same field name as `junglee/free-amazon-product-scraper`.

## `maxSearchPagesPerStartUrl` (type: `integer`):

Stop after this many listing pages per start URL (16 results per page). Whichever of this and `maxItemsPerStartUrl` is reached first wins. Same field name as `junglee/free-amazon-product-scraper`.

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

Marketplace used for plain `searchTerms` and for product ASINs. Start URLs keep their own marketplace. Prices are reported in this marketplace's currency.

## `scrapeRatingsHistogram` (type: `boolean`):

Fetch each product's ratings summary (a ~21 KB fragment, not the 2.5 MB product page) to add `ratingsHistogram`, `starsBreakdown` and the **exact** `reviewsCount` / `ratingsTotal` — search cards only show a rounded '1.6K'. Adds roughly one fifth of the run's traffic. Turn off for the fastest, cheapest listing-only run.

## `residentialFallback` (type: `boolean`):

Non-US marketplaces (amazon.co.uk, amazon.de, ...) block Apify datacenter IPs as a tier, so a blocked marketplace is retried once on a residential IP in its own country. amazon.com does not need this and never pays for it. Turn off to keep a run strictly on the proxy tier you configured.

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

Amazon fingerprints the TLS handshake, not just the IP, and this actor already presents a real Chrome fingerprint — so the default Apify **datacenter** proxy is enough and RESIDENTIAL is not needed. Keep the proxy on; raw platform IPs are the ones Amazon knows best.

## Actor input object example

```json
{
  "categoryUrls": [
    {
      "url": "https://www.amazon.com/s?k=mechanical+keyboard"
    }
  ],
  "searchTerms": [],
  "maxItemsPerStartUrl": 100,
  "maxSearchPagesPerStartUrl": 20,
  "domain": "amazon.com",
  "scrapeRatingsHistogram": true,
  "residentialFallback": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `products` (type: `string`):

All product records. Append ?format=csv for CSV.

## `datasetUrl` (type: `string`):

The default dataset.

# 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 = {
    "categoryUrls": [
        {
            "url": "https://www.amazon.com/s?k=mechanical+keyboard"
        }
    ],
    "maxItemsPerStartUrl": 100,
    "maxSearchPagesPerStartUrl": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("diopside/amazon-search").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 = {
    "categoryUrls": [{ "url": "https://www.amazon.com/s?k=mechanical+keyboard" }],
    "maxItemsPerStartUrl": 100,
    "maxSearchPagesPerStartUrl": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("diopside/amazon-search").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 '{
  "categoryUrls": [
    {
      "url": "https://www.amazon.com/s?k=mechanical+keyboard"
    }
  ],
  "maxItemsPerStartUrl": 100,
  "maxSearchPagesPerStartUrl": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call diopside/amazon-search --silent --output-dataset

```

## MCP server setup

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

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/vSecjc3ohpyTonYXe/builds/4vfwabq0DMnbEpSLY/openapi.json
