# Best Buy Scraper — Prices, Stock, Reviews & Search Rank (`memo23/bestbuy-scraper`) Actor

Scrape Best Buy by SKU, URL, keyword or category page: price, savings, rating, stock, UPC, full specs, dimensions and description. Keywords return the ranked grid with sponsored tiles flagged. Optional ZIP-level availability, customer reviews and brand / open-box filters.

- **URL**: https://apify.com/memo23/bestbuy-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.67 / 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

## Best Buy Scraper — Prices, Stock, Reviews & Search Rank

<p align="center">
  <img src="https://api.apify.com/v2/key-value-stores/DF1cms95gu9zhXnM3/records/how-it-works" alt="How the Best Buy Scraper works" width="820" />
</p>

**Track any Best Buy product by SKU, URL, keyword or category page.** One row per product with the current price, regular price, savings, financing terms, rating, review count, add-to-cart state, open-box count, model number, images, UPC, the full specification sheet, dimensions, description and feature bullets. Keyword and category pages return Best Buy's ranked results grid with sponsored tiles flagged. Add a ZIP code and every row also carries what a shopper *there* would be offered. Turn on reviews and each review comes through as its own row. JSON, CSV or Excel out.

### Why Use This Scraper?

- ✅ **Built for price monitoring** — give it the SKUs you care about, schedule it, and get today's price, regular price, savings %, financing and stock state for each. That is what most Best Buy scraping runs are actually doing.
- ✅ **Rank and ad position, which nothing else on the Store reports** — a keyword or category page returns Best Buy's grid in rank order with `searchRank`, `isSponsored` and `searchPage` on every row, page after page until `maxItems`.
- ✅ **Availability by ZIP code** — set `zipCode` and each row adds Best Buy's add-to-cart state for that region. It genuinely differs: one laptop came back `ADD_TO_CART` for New York and Miami and `SOLD_OUT` for Anchorage in the same minute. One small request per 20 products, no extra rows.
- ✅ **Customer reviews on the same run** — `includeReviews` adds a row per review: stars, headline, full text, author, verified-purchase badge, helpful votes, date, pros and cons. Each is a billed row, 3 per product by default.
- ✅ **Filters Best Buy applies itself** — brand, open-box, Top-Rated, sold-by-Best-Buy and a price band are sent as Best Buy's own facets, so the site narrows the results and your item budget goes further. The run log reports any filter Best Buy did not honour.
- ✅ **Best Buy's own data** — prices, ratings, stock state, model number and images are read from the data Best Buy embeds for its own front end, not scraped off the layout.
- ✅ **The full spec sheet, no key needed** — every row carries the UPC, every specification Best Buy lists (30–100 per product), dimensions and weight, the long description and the feature bullets.
- ✅ **Official API, your key** — set `bestbuyApiKey` and lookups and searches go through api.bestbuy.com instead, with no proxy and no bot wall. Your key, your terms; the actor only makes the calls.
- ✅ **Fails loudly** — a run that could not fetch anything ends `FAILED` with the reason, never as an empty "success".

### Overview

Best Buy renders its pages with Next.js and Apollo. The scraper reads the data Best Buy embeds for its own front end, so field names and values are the site's own. It fetches over a residential US connection with a browser TLS fingerprint, because Best Buy's edge refuses datacenter IPs and plain HTTP clients outright; you do not configure any of that.

Product rows (`type: "product"`) go to the run's main dataset. With reviews on, review rows (`type: "review"`) go to a second dataset of the same run, aliased `reviews`, so each table holds one kind of row. Product URLs, SKUs, keywords and category pages all yield the same product row shape.

Best Buy's bot protection blocks product pages for anything that is not a full browser, so the scraper never opens one. It looks products up through Best Buy's search, four SKUs per request, which is also why it stays cheap. Products that search no longer lists (sold out, clearance) come from Best Buy's price service instead, with their rating from the reviews service. UPC, specifications, dimensions, description and feature bullets come from the product service Best Buy's own mobile app reads, 12 products per request.

### Supported Inputs

#### Product URLs and SKUs (the reliable path)

| Form | Example |
|---|---|
| Canonical | `https://www.bestbuy.com/site/sony-wh-1000xm5/6505727.p?skuId=6505727` |
| Short | `https://www.bestbuy.com/site/-/6505727.p` |
| App-router | `https://www.bestbuy.com/product/sony-wh-1000xm5/J7XSRH5CPW/sku/6505727` |
| Bare SKU | `6505727` in the **SKUs** field |

```json
{
  "startUrls": [{ "url": "https://www.bestbuy.com/site/-/6505727.p" }],
  "skus": ["6615874", "6668413"],
  "maxItems": 100
}
```

#### Keyword search

A keyword (or a pasted `searchpage.jsp?st=…` URL) returns Best Buy's results grid in rank order: 18 organic products per page plus the sponsored tiles, with `searchRank`, `isSponsored` and `searchPage` on every row. `searchTotalResults` carries Best Buy's own match count for the search. The scraper pages through (`cp=2`, `cp=3` …) until `maxItems`. Best Buy sends only the first few tiles of each page filled in; the rest are looked up four at a time, so a full page costs about five requests.

With your own key from [developer.bestbuy.com](https://developer.bestbuy.com/) in `bestbuyApiKey`, the same keyword goes through the official Products API instead.

```json
{
  "searchQueries": ["wireless earbuds"],
  "minPrice": 50,
  "maxPrice": 200,
  "onSaleOnly": true,
  "sortBy": "Best-Discount",
  "maxItems": 50
}
```

#### Category and browse pages

Paste a Best Buy category URL — the ones ending in `.c?id=…` — and it is walked exactly like a keyword search, same ranked grid, same sponsored flags, same pagination.

```json
{
  "startUrls": [
    { "url": "https://www.bestbuy.com/site/tvs/all-flat-screen-tvs/abcat0101001.c?id=abcat0101001" }
  ],
  "openBoxOnly": true,
  "maxItems": 60
}
```

Landing pages that have no product grid (`/site/promo/…`, `/site/shop/…`) are reported in the log and skipped rather than silently fetched.

#### Filters

| Filter | How it works |
|---|---|
| `brands` | Best Buy's `brand_facet`. Use its own labels — `HP`, `Samsung`, `Insignia™`. Several brands match any of them. A label Best Buy does not use matches nothing, and the log says so. |
| `openBoxOnly` | Best Buy's `Condition~Open-Box` facet. Searches and category pages only. It also makes Best Buy drop the sort order. Rows keep the new-item `price`; the open-box prices are in `openBoxOffers` and `openBoxLowestPrice`. |
| `soldByBestBuyOnly` | Best Buy's `bbyonly_facet` — drop marketplace sellers. It took a 7,230-result laptop search down to 1,112. Every row also carries `sellerType` and `soldByBestBuy` either way. |
| `topRatedOnly` | Best Buy's own "Top-Rated" cut, which it does not define as a number. On one test it took a 7,230-result search down to 395. |
| `minRating` | Applied to the rows (1–5). Best Buy has no numeric rating facet, only `topRatedOnly`. |
| `minPrice` / `maxPrice` | Both set → Best Buy's price facet, so the site narrows server-side. One bound → applied to the rows. |
| `onSaleOnly`, `skipOutOfStock` | Applied to the rows. |
| `sortBy` | Best Buy's own sort: best match, best selling, best discount, price up / down, customer rating, new arrivals. Changes which products land inside your item budget. Best Buy ignores it while `openBoxOnly` is on — the log says which order it used. |

#### Unsupported inputs

- ❌ Shelf counts for one named store ("3 in stock at the 23rd St store") — that sits behind Best Buy's bot wall, which no HTTP scraper gets past. `zipCode` gives you regional availability instead.
- ❌ Anything behind a Best Buy account (cart, order history, member pricing)
- ❌ Landing pages with no product grid (`/site/promo/…`, `/site/shop/…`)

### Use Cases

| Audience | Use case |
|---|---|
| **Price trackers & deal sites** | Schedule a fixed SKU list; diff `price`, `regularPrice`, `savingsPercent`, `onSale` and `buttonState` between runs |
| **Resellers & arbitrage** | Watch clearance (`NOT_AVAILABLE` at a marked-down price) and open-box counts on high-value SKUs; `openBoxOnly` turns a category into an open-box feed |
| **Regional availability checks** | Run one SKU list against several `zipCode` values to see where a product is actually buyable |
| **Brands & agencies** | Verify your products' Best Buy price, badges, rating and review count against MAP and competitors |
| **Search & merchandising analysts** | Track which products rank where for a keyword, and which tiles are sponsored |
| **Review & voice-of-customer research** | `includeReviews` gives full review text with verified-purchase badges, pros and cons, ready for sentiment work |
| **Developers with an API key** | Use the actor as a scheduled, exportable front end to the official Best Buy Products API |

### How It Works

1. **Input** — product URLs, SKUs, keywords or category URLs; optionally a Best Buy API key, a ZIP code and reviews.
2. **Route** — with a key, products come from `api.bestbuy.com`. Without one, grid pages are fetched over a residential US exit with a Chrome TLS fingerprint; SKUs are looked up four per search.
3. **Parse** — Best Buy's Apollo server-state is read and merged per SKU: the product record, its price block, review info, fulfillment states and images.
4. **Filter** — brand, open-box, Top-Rated and a full price band go to Best Buy as facets so fewer pages are fetched; price, rating, on-sale and stock filters are also applied to the rows.
5. **Enrich** — with a ZIP code, one small request per 20 products adds Best Buy's button state for that region. With reviews on, each product's reviews are fetched in parallel.
6. **Output** — one row per product to the main dataset, and one row per review to the `reviews` dataset, each exportable as JSON, CSV or Excel.

### Input Configuration

| Field | Type | Default | Notes |
|---|---|---|---|
| `startUrls` | array | `[]` | Product URLs (any form), search-page URLs, or category URLs ending in `.c?id=…`. |
| `skus` | array | `[]` | Bare SKU numbers. |
| `searchQueries` | array | `[]` | Keywords. See the scope note above. |
| `maxItems` | integer | `100` | Cap on **product** rows across all inputs. Review rows are counted separately. |
| `includeDetails` | boolean | `true` | For grid results, return every product on the page (about five requests per page). Off: only the few tiles Best Buy sends filled in, one request per page. |
| `minPrice` / `maxPrice` | integer | – | USD. With both set, sent to Best Buy as a facet; a single bound is applied to rows after fetching. |
| `brands` | array | `[]` | Brand names as Best Buy labels them. Sent as its `brand_facet`. |
| `minRating` | integer | – | 1–5. Applied to the rows. |
| `topRatedOnly` | boolean | `false` | Best Buy's own "Top-Rated" facet. |
| `openBoxOnly` | boolean | `false` | Best Buy's `Condition~Open-Box` facet. Grid pages only. Open-box prices per condition are in `openBoxOffers` on every row, with or without this filter. |
| `soldByBestBuyOnly` | boolean | `false` | Keep only products Best Buy sells and ships itself (not marketplace sellers). |
| `onSaleOnly` | boolean | `false` | Keep only `price < regularPrice`. |
| `skipOutOfStock` | boolean | `false` | Drop `SOLD_OUT` / `NOT_AVAILABLE` products. |
| `sortBy` | select | `Best-Match` | Best Buy's own sort order for grid pages. A pasted URL that already sorts keeps its own. |
| `zipCode` | string | – | US ZIP. Adds `zipButtonState` / `inStockAtZip` to every row. |
| `includeReviews` | boolean | `false` | Also write each product's reviews, one row each, to the run's `reviews` dataset. **Each review is billed as a `review` event, at the same price as a product.** |
| `maxReviewsPerProduct` | integer | `3` | Reviews per product. Best Buy serves 20 per page. Each review is a billed row, so this is what turning reviews on costs. |
| `reviewSort` | select | `MOST_RECENT` | `MOST_RECENT` or `MOST_HELPFUL` — the only two orders Best Buy honours. |
| `bestbuyApiKey` | string (secret) | – | Your Best Buy Developer API key. Switches product lookups and searches to the official API. |
| `maxConcurrency` | integer | `4` | Lookups in flight. Bandwidth is the cost, not CPU. |
| `proxy` | object | residential US | Forced residential; set only to use your own residential pool. The API path ignores it, except for ZIP availability and reviews. |

A `cp=` page number in a pasted grid URL is ignored — the walk always starts at page 1 and paginates from there.

#### Common scenarios

**Daily price watch on ten SKUs**

```json
{ "skus": ["6505727", "6615874", "6668413", "6681674", "12349291"], "maxItems": 10 }
```

**Is it buyable in Anchorage?**

```json
{ "skus": ["12349291", "6681674"], "zipCode": "99501", "maxItems": 2 }
```

**HP laptops, biggest discounts first, with reviews**

```json
{
  "searchQueries": ["laptop"],
  "brands": ["HP"],
  "sortBy": "Best-Discount",
  "includeReviews": true,
  "maxReviewsPerProduct": 10,
  "maxItems": 25
}
```

That run writes 25 product rows plus up to 250 review rows, because `maxReviewsPerProduct` is raised from its default of 3. Leave it at 3 unless you want the depth.

**Open-box TVs from the category page**

```json
{
  "startUrls": [{ "url": "https://www.bestbuy.com/site/tvs/all-flat-screen-tvs/abcat0101001.c?id=abcat0101001" }],
  "openBoxOnly": true,
  "maxItems": 60
}
```

**Deals under $200 for a keyword, full official results**

```json
{ "searchQueries": ["bluetooth speaker"], "maxPrice": 200, "onSaleOnly": true, "bestbuyApiKey": "YOUR_KEY", "maxItems": 200 }
```

**Quick, cheap look at the top of a results page (no key)**

```json
{ "searchQueries": ["gaming laptop"], "includeDetails": false, "maxItems": 30 }
```

### Output Overview

One row per product in the main dataset, plus one row per review in the `reviews` dataset when `includeReviews` is on. Prices are USD numbers. Fields Best Buy did not provide are `null`, never omitted, so CSV columns stay stable across runs.

On the run page, the Output tab shows a **Products** table and a **Reviews** table. To export reviews, open the **Storage** tab and pick the `reviews` dataset from the Dataset dropdown; the Export button at the top of the run page exports products only. Through the API, the run record lists both datasets under `storageIds.datasets` (`default` for products, `reviews` for reviews); read `GET /v2/datasets/{id}/items` on either.

> Until 28 October 2026, review rows are still written to the main dataset next to the products (told apart by `type`) and billed as regular rows. From that date they move to the `reviews` dataset automatically.

### Output Samples

#### Product row (from a keyword, with a ZIP code), trimmed

```json
{
  "type": "product",
  "sku": "10328010",
  "url": "https://www.bestbuy.com/product/hp-17-3-fhd-ips-laptop/10328010",
  "name": "HP - 17.3\" FHD IPS Laptop AMD Ryzen 5 7430U 8GB RAM 256GB SSD - Natural Silver",
  "brand": "HP",
  "condition": "new",
  "price": 479.99,
  "regularPrice": 999,
  "savings": 519.01,
  "savingsPercent": 51,
  "onSale": true,
  "financeMonths": 12,
  "financeMonthly": 40,
  "currency": "USD",
  "rating": 4.3,
  "reviewCount": 6,
  "buttonState": "ADD_TO_CART",
  "inStock": true,
  "zipCode": "99501",
  "zipButtonState": "SOLD_OUT",
  "inStockAtZip": false,
  "image": "https://pisces.bbystatic.com/image2/BestBuy_US/images/products/4a7b50…jpg",
  "images": ["…"],
  "badges": [],
  "openBoxOptions": null,
  "openBoxOffers": [],
  "openBoxLowestPrice": null,
  "upc": "199485014814",
  "description": "Performance you can trust -Multitasking feels easy and fast with the performance of an AMD processor…",
  "highlights": ["See more and enjoy flicker-free images on an FHD IPS screen…", "…"],
  "specifications": { "Screen Size": "17.3 inches", "Screen Resolution": "1920 x 1080 (Full HD)", "Processor Model": "AMD Ryzen 5 5000 Series", "…": "… 31 in total" },
  "dimensions": { "height": "0.78 inches", "width": "15.78 inches", "depth": "10.15 inches", "weight": "7.4 pounds" },
  "searchRank": 2,
  "isSponsored": false,
  "searchPage": 1,
  "searchTotalResults": 2106,
  "scrapedAt": "2026-10-13T12:11:38.420Z"
}
```

#### Review row (`reviews` dataset, `includeReviews: true`)

```json
{
  "type": "review",
  "sku": "10328010",
  "productName": "HP - 17.3\" FHD IPS Laptop AMD Ryzen 5 7430U 8GB RAM 256GB SSD - Natural Silver",
  "productUrl": "https://www.bestbuy.com/product/hp-17-3-fhd-ips-laptop/10328010",
  "reviewId": "b951a1b6-6371-314f-afd1-b63c7b1294a7",
  "rating": 5,
  "title": "As expected",
  "text": "After a bit of confusion when placing the order, the laptop arrived a day earlier than expected…",
  "author": "MarkM",
  "recommended": true,
  "submittedAt": "2026-03-06T17:33:30.000-06:00",
  "helpfulVotes": 1,
  "unhelpfulVotes": 0,
  "commentCount": 0,
  "badges": ["Incentivized", "Verified Purchaser"],
  "daysOfOwnership": 16,
  "pros": ["Setup Process"],
  "cons": [],
  "photos": [],
  "scrapedAt": "2026-10-13T12:11:41.187Z"
}
```

#### Clearance product (sold out, so it comes from the price service)

```json
{
  "sku": "6505729",
  "name": "Sony - LinkBuds S True Wireless Noise Canceling Earbuds - White",
  "price": 32.99,
  "regularPrice": 159.99,
  "savingsPercent": 79,
  "buttonState": "SOLD_OUT",
  "inStock": false,
  "rating": 4.2,
  "reviewCount": 242,
  "images": []
}
```

### Key Output Fields

#### Identity

- `sku`, `url`, `name`, `brand`, `model`, `upc`, `condition`, `isNew`, `categoryPath[]`

#### Price

- `price` — what the buyer pays now
- `regularPrice`, `savings`, `savingsPercent`, `onSale`
- `financeMonths`, `financeMonthly` — Best Buy's financing line when offered
- `currency` — always `USD`

#### Seller

- `sellerType` — Best Buy's own classification: `1P` is Best Buy, `3P` a marketplace seller
- `soldByBestBuy` — `sellerType` read as a boolean

#### Availability

- `buttonState` — Best Buy's own add-to-cart state (`ADD_TO_CART`, `PRE_ORDER`, `SOLD_OUT`, `NOT_AVAILABLE`, `COMING_SOON`, `SEE_ALL_OPTIONS`, …)
- `inStock` — `true` / `false` read from `buttonState`; `null` when Best Buy showed no button
- `openBoxOptions` — number of open-box conditions on offer
- `openBoxOffers[]` — each open-box offer, cheapest first: `condition` (Fair / Good / Excellent), `price`, and `savings` against the new price
- `openBoxLowestPrice` — the cheapest open-box price, `null` when there is none

  Example from a laptop with three open-box offers (SKU 6615874, 2026-10-14): new `price` 699, `openBoxOffers` Fair 634.99 / Good 655.99 / Excellent 676.99, `openBoxLowestPrice` 634.99.

#### Regional availability *(with `zipCode` only)*

- `zipCode` — the ZIP the check was made against
- `zipButtonState` — Best Buy's button state for that region; differs from `buttonState`, which is its default location
- `inStockAtZip` — `true` / `false` read from `zipButtonState`

#### Social proof

- `rating`, `reviewCount`, `badges[]`

#### Review rows *(`reviews` dataset, with `includeReviews` only)*

- `rating`, `title`, `text`, `author`, `recommended`, `submittedAt`
- `helpfulVotes`, `unhelpfulVotes`, `commentCount`
- `badges[]` — e.g. `Verified Purchaser`, `Incentivized`
- `pros[]`, `cons[]`, `daysOfOwnership`, `photos[]`
- `qualityRating`, `valueRating`, `easeOfUseRating` — sub-scores, on some categories only

#### Detail

- `upc`, `description`, `highlights[]` (feature bullets), `specifications{}` (every spec Best Buy lists, flattened), `dimensions{}` (`height`, `width`, `depth`, `weight`)

#### Search context *(grid rows only)*

- `searchRank` — position among the organic tiles of that results page (1-based); `null` for sponsored-only tiles, product URLs and SKUs
- `isSponsored`, `searchPage`
- `searchTotalResults` — Best Buy's own match count for that search or category, after the facets

### FAQ

**Why is a row missing its specifications or UPC?**
Rarely, Best Buy's product service does not answer for a product, or the lookup fails after three attempts. The row is still written with its price, stock and rating, and the run log says how many products had no details.

**How does `zipCode` work, and can I get per-store shelf counts?**
`zipCode` asks Best Buy what its add-to-cart button would say for a shopper in that region, which is exactly how its own site decides what to offer. Shelf counts for one named store come from an endpoint Best Buy only answers for a full browser session, so no HTTP scraper can reach them — including this one.

**Do reviews cost extra?**
Yes, at the same price as a product row: each review is charged as a `review` event. The run stops adding reviews once your spending limit is reached. At the default of 3, 50 products means 150 review rows on top of the 50 products; at 10 it would be 500. `maxReviewsPerProduct` is the dial, and `maxItems` does not bound it.

**I set a sort order and the results came back in a different one.**
Best Buy honours the sort on its own, but silently ignores it when the Open-Box filter is on — on search and category pages alike. The actor reads the order back off the page and the run log names what Best Buy actually used, so you are never guessing.

**Can I tell Best Buy's own listings from marketplace ones?**
Yes. Every row carries `sellerType` (`1P` for Best Buy, `3P` for a marketplace seller) and `soldByBestBuy`. Set `soldByBestBuyOnly` to drop the marketplace side using Best Buy's own filter. The official API path does not expose the split, so both fields are `null` there.

**Why did a brand filter return nothing?**
Best Buy matches brand labels exactly as it writes them — `Insignia™`, not `Insignia`; `HP`, not `Hewlett-Packard`. The run log prints Best Buy's own match count after the filters, so a zero shows up immediately. Open a product page on Best Buy and copy the brand as shown.

**I searched for something meaningless and still got billed rows.**
Best Buy never returns an empty search: for a keyword it does not recognise it serves a generic grid, and the actor scrapes what the site returns. Check `searchTotalResults` and the product names on the first run of a new keyword.

**Why do a few rows have no images or financing?**
Sold-out and clearance products drop out of Best Buy's search, so they are read from Best Buy's price service, which carries price, savings and stock state but no images or financing. Rating and review count are added from the reviews service.

**Is the API key required?**
No. Product URLs, SKUs, keywords and category pages all work without it. Specifications, dimensions, UPC and description come back without it. A key only switches lookups and searches to the official API. Keys are free at developer.bestbuy.com.

**What does an API key NOT do?**
Three things stay on the website. A pasted results or category URL cannot be walked through the API — it is a website construct with no browse equivalent — so those URLs are skipped and named in the log; use keywords instead, or drop the key. `openBoxOnly`, `topRatedOnly` and `soldByBestBuyOnly` are Best Buy website facets with no API counterpart and have no effect on an API-key run; the log says so. And ZIP availability and reviews read bestbuy.com either way, so a run that uses them still goes through the proxy.

**Whose terms apply when I use my API key?**
Yours. Best Buy's API terms restrict caching beyond a temporary basis; the actor simply makes the calls your key entitles you to and hands you the rows.

**Does it work outside the US?**
The actor always exits through a US residential IP, so the buyer's location does not matter. Prices are US prices.

**What does `inStock: null` mean?**
Best Buy rendered the product without an add-to-cart state (rare, usually marketplace or pre-release listings). `buttonState` will be `null` too.

**Why is `regularPrice` sometimes `null`?**
Clearance and discontinued items carry a current price but no regular price on Best Buy's side.

**How much bandwidth does a run use?**
A grid page is about 60 KB on the wire and covers four looked-up products, or a whole results page of rank data. ZIP availability and reviews are small JSON calls. A 100-product run is a few MB.

### Support

- For issues or feature requests, please use the [Issues](https://console.apify.com/actors/bestbuy-scraper/issues) section of this actor.
- If you need customization or have questions, feel free to contact the author:
  - Author's website: <https://muhamed-didovic.github.io/>
  - Email: <muhamed.didovic@gmail.com>

### Additional Services

- Request customization or whole dataset: <muhamed.didovic@gmail.com>
- If you need anything else scraped, or this actor customized, email: <muhamed.didovic@gmail.com>
- For API services of this scraper (no Apify fee, just usage fee for the API), contact: <muhamed.didovic@gmail.com>

### Explore More Scrapers

- [Etsy Scraper](https://apify.com/memo23/etsy-scraper) — live cart demand, ad slots, prices, reviews
- [Amazon Reviews Scraper](https://apify.com/memo23/amazon-reviews-scraper)
- [eBay Search Scraper](https://apify.com/memo23/ebay-search-scraper-ppe)

### 🤖 For AI Agents & LLM Apps

Compact reference for agents calling this actor through the [Apify MCP server](https://mcp.apify.com) or the Apify API (actor: `memo23/bestbuy-scraper`).

**Purpose:** current price, availability and rating for Best Buy products by SKU, URL, keyword or category page; the ranked results grid with sponsored tiles flagged; optional per-ZIP availability and customer reviews. Every row includes UPC, specifications, dimensions, description and feature bullets.

**Minimal input:**

```json
{ "skus": ["6505727"], "maxItems": 1 }
```

**Output:** product rows (`type: "product"`) — sku, url, name, brand, model, upc, condition, price, regularPrice, savings, savingsPercent, onSale, financeMonths, financeMonthly, currency, rating, reviewCount, buttonState, inStock, isNew, image, images, badges, openBoxOptions, openBoxOffers, openBoxLowestPrice, sellerType, soldByBestBuy, zipCode, zipButtonState, inStockAtZip, searchRank, isSponsored, searchPage, searchTotalResults, description, highlights, specifications, dimensions, categoryPath, scrapedAt. Review rows (`type: "review"`, in the run's separate `reviews` dataset, not the default one) — sku, productName, productUrl, reviewId, rating, title, text, author, recommended, submittedAt, helpfulVotes, unhelpfulVotes, commentCount, badges, qualityRating, valueRating, easeOfUseRating, daysOfOwnership, pros, cons, photos.

**Behaviors an agent should know:**

- Prefer `skus` / `startUrls` for exact products. A keyword or `.c?id=…` category URL returns the ranked grid (`searchRank`, `isSponsored`, `searchPage`, `searchTotalResults`). `upc`, `description`, `specifications` and `dimensions` are filled on every row without a key; a `null` there means Best Buy's product service did not answer for that product.
- `price` is the buyer's price; compare with `regularPrice` and `onSale` for discounts. `inStock` is derived from `buttonState`.
- `buttonState` is Best Buy's default location. For a region, set `zipCode` and read `zipButtonState` / `inStockAtZip`. Shelf stock for one named store is not available.
- `sellerType` / `soldByBestBuy` separate Best Buy's own listings (`1P`) from marketplace sellers (`3P`); both are `null` on the API path.
- `maxItems` caps **product** rows only. Every review is billed as a `review` event at the product price, so `includeReviews` multiplies the billed count by up to `maxReviewsPerProduct`. Reviews are not in the default dataset; take the run's `storageIds.datasets.reviews` id and read that dataset.
- `brands`, `openBoxOnly`, `topRatedOnly` and `soldByBestBuyOnly` are Best Buy's own website facets and need its exact labels; the last three have no equivalent on the API path and are reported as skipped there; `minRating`, `onSaleOnly` and `skipOutOfStock` are applied to the rows and drop them before they are billed. The log reports any facet Best Buy ignored.
- A run with nothing fetched ends `FAILED` with a status message naming the cause; an empty result set with successful fetches ends `SUCCEEDED`.

### ⚠️ Disclaimer

This scraper collects only publicly available product data from bestbuy.com. It does not bypass any login, does not access accounts or member pricing, and does not solve CAPTCHAs. When a Best Buy Developer API key is supplied, requests are made under that key holder's own agreement with Best Buy. Use the data responsibly and in line with Best Buy's terms and applicable law in your jurisdiction. Best Buy is a trademark of Best Buy Co., Inc.; this actor is not affiliated with or endorsed by Best Buy.

### SEO Keywords

best buy scraper, bestbuy scraper, best buy price tracker, best buy price monitoring, best buy product data, best buy sku lookup, best buy availability checker, best buy stock checker, best buy stock checker by zip, best buy reviews scraper, best buy customer reviews, best buy api, best buy products api, best buy search rank tracker, best buy sponsored products, best buy open box tracker, best buy category scraper, best buy deals scraper, retail price monitoring api, electronics price tracker

# Actor input Schema

## `startUrls` (type: `array`):

Any Best Buy URL. <b>Product pages</b> — <code>bestbuy.com/site/…/6505729.p</code>, <code>bestbuy.com/site/-/6505729.p</code>, <code>bestbuy.com/product/…/sku/6505729</code> — give one row per product. <b>Search pages</b> (<code>searchpage.jsp?st=…</code>) and <b>category pages</b> (<code>…/abcat0101001.c?id=abcat0101001</code>) are walked page by page for their ranked result grid; a <code>cp=</code> page number in a pasted URL is ignored, the walk starts at page 1. Bare SKUs go in the <b>SKUs</b> field below.

## `skus` (type: `array`):

Best Buy SKU numbers, one per line (<code>6505729</code>). Same row as a product URL. Handy for price monitoring on a fixed list.

## `searchQueries` (type: `array`):

Each keyword becomes a Best Buy search, walked page by page in rank order. With <b>Best Buy API key</b> set below, the keyword goes through the official Products API instead, which returns the full official result list.

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

Hard cap on product rows across all inputs. Controls billing. Reviews are counted separately — see the Reviews section.

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

Best Buy sends only the first few tiles of a results page filled in. On: the rest are looked up too, four per request, so you get the whole ranked grid. Off: only the pre-filled tiles, one request per page (a cheap preview).

## `minPrice` (type: `integer`):

Lower price bound in USD. With both bounds set, searches send Best Buy's own price facet so the site narrows results server-side; otherwise the bound is applied to rows after the fetch.

## `maxPrice` (type: `integer`):

Upper price bound in USD.

## `brands` (type: `array`):

Brand names exactly as Best Buy labels them — <code>HP</code>, <code>Samsung</code>, <code>Insignia™</code>. Several brands are combined (products from any of them). Sent as Best Buy's <code>brand_facet</code>; a label Best Buy does not use simply matches nothing, and the run log says so.

## `minRating` (type: `integer`):

Keep only products whose average customer rating is at least this. Applied to the rows: Best Buy's own rating facet has no numeric threshold, only the <b>Top-Rated only</b> switch below.

## `topRatedOnly` (type: `boolean`):

Best Buy's own "Top-Rated" filter on searches and category pages. It is the site's quality cut, which Best Buy does not define as a number — on one test it took a 7,230-result search down to 395. Narrows server-side, so it is the cheap way to skim the good stuff. Website filter only: it has no effect on a run that sets a Best Buy API key.

## `openBoxOnly` (type: `boolean`):

Best Buy's <code>Condition~Open-Box</code> facet: open-box listings only. Applies to searches and category pages, not to pasted SKUs or product URLs. Best Buy drops the sort order while this is on, and its own open-box counts move around during the day. Website filter only: it has no effect on a run that sets a Best Buy API key.

## `soldByBestBuyOnly` (type: `boolean`):

Drop marketplace sellers and keep only what Best Buy sells and ships itself — its own "Sold & shipped by Best Buy" filter, which took a 7,230-result laptop search down to 1,112. Every row also carries <code>sellerType</code> (<code>1P</code> Best Buy, <code>3P</code> marketplace) and <code>soldByBestBuy</code>, so you can see the split without filtering. Website filter only: it has no effect on a run that sets a Best Buy API key, which does not expose the seller split.

## `onSaleOnly` (type: `boolean`):

Keep only products whose current price is below the regular price.

## `skipOutOfStock` (type: `boolean`):

Drop products Best Buy marks sold out or not available online.

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

Order of the result pages, using Best Buy's own sort options. Changes which products land in your item budget — "Best discount" first is the usual choice for deal hunting. A pasted URL that already carries a sort keeps its own. Best Buy ignores the sort when <b>Open-Box only</b> is on; the run log reports the order it actually used.

## `zipCode` (type: `string`):

A US ZIP code, e.g. <code>10001</code>. Every product then also carries Best Buy's add-to-cart state <i>for that ZIP</i> in <code>zipButtonState</code> / <code>inStockAtZip</code> — which genuinely differs by region: one laptop came back <code>ADD_TO_CART</code> for 10001 and Miami and <code>SOLD_OUT</code> for Anchorage in the same minute. Costs one small JSON request per 20 products, no extra dataset rows.

## `includeReviews` (type: `boolean`):

Write each product's customer reviews, one row each, to the run's separate <b>Reviews</b> dataset (<code>type: "review"</code>): rating, title, full text, author, verified-purchase badge, helpful votes, submission date, pros and cons. <b>Each review is billed</b> at the same price as a product row — so 50 products at the default 3 reviews each is 150 extra rows on top of the 50 products. Raise <b>Max reviews per product</b> deliberately.

## `maxReviewsPerProduct` (type: `integer`):

Reviews per product. Best Buy serves 20 per page, so a value above 20 means more requests per product. Each review is billed, so this is the dial that decides what turning reviews on costs.

## `reviewSort` (type: `string`):

Best Buy honours these two orders only; the rating-based orders it advertises elsewhere silently return its default order.

## `bestbuyApiKey` (type: `string`):

Your own key from <a href='https://developer.bestbuy.com/' target='_blank' rel='noopener'>developer.bestbuy.com</a>. When set, every lookup and keyword search goes through the official Best Buy Products API, official JSON, no proxy, no bot wall. Specifications, UPC and description come back without a key too. The key and the API's terms of use are yours; this actor only makes the calls you are entitled to make. Leave empty to scrape the website.

## `maxConcurrency` (type: `integer`):

Lookups run in parallel through the residential proxy. 4 is a good default; more does not help much, bandwidth is the cost, not CPU.

## `proxy` (type: `object`):

Best Buy rejects datacenter IPs at the TLS layer, so a <b>residential US</b> exit is required and is used automatically. Set this only to point at your own residential pool (proxy URLs). The API path ignores it, except for ZIP availability and reviews.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.bestbuy.com/site/-/6505729.p"
    },
    {
      "url": "https://www.bestbuy.com/site/-/6615874.p"
    }
  ],
  "skus": [],
  "searchQueries": [],
  "maxItems": 100,
  "includeDetails": true,
  "brands": [],
  "topRatedOnly": false,
  "openBoxOnly": false,
  "soldByBestBuyOnly": false,
  "onSaleOnly": false,
  "skipOutOfStock": false,
  "sortBy": "Best-Match",
  "includeReviews": false,
  "maxReviewsPerProduct": 3,
  "reviewSort": "MOST_RECENT",
  "maxConcurrency": 4,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

One row per product (type "product"): sku, name, brand, model, upc, condition, price, regularPrice, savings, savingsPercent, onSale, financeMonths, financeMonthly, rating, reviewCount, buttonState, inStock, isNew, image, images, badges, openBoxOptions, sellerType, soldByBestBuy, zipCode, zipButtonState, inStockAtZip, searchRank, isSponsored, searchPage, searchTotalResults, description, highlights, specifications, dimensions, categoryPath, scrapedAt.

## `reviews` (type: `string`):

Only with "Also scrape customer reviews" on: each review is its own row (type "review"): sku, productName, productUrl, reviewId, rating, title, text, author, recommended, submittedAt, helpfulVotes, unhelpfulVotes, commentCount, badges, qualityRating, valueRating, easeOfUseRating, daysOfOwnership, pros, cons, photos.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.bestbuy.com/site/-/6505729.p"
        },
        {
            "url": "https://www.bestbuy.com/site/-/6615874.p"
        }
    ],
    "skus": [],
    "searchQueries": [],
    "brands": [],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/bestbuy-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 = {
    "startUrls": [
        { "url": "https://www.bestbuy.com/site/-/6505729.p" },
        { "url": "https://www.bestbuy.com/site/-/6615874.p" },
    ],
    "skus": [],
    "searchQueries": [],
    "brands": [],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/bestbuy-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 '{
  "startUrls": [
    {
      "url": "https://www.bestbuy.com/site/-/6505729.p"
    },
    {
      "url": "https://www.bestbuy.com/site/-/6615874.p"
    }
  ],
  "skus": [],
  "searchQueries": [],
  "brands": [],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call memo23/bestbuy-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/bestbuy-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/Td9WHBuvfDeHEEQki/builds/MCdQhpvDBz5Sc9Hxw/openapi.json
