# PetSmart Product, Price & Promo Scraper (US) (`superslowsloth/petsmart-products`) Actor

PetSmart scraper: search terms or category, brand and offer pages to one row per product with USD price, list price, discount, price range across sizes, running promotions, rating, reviews, Autoship flag, brand and image. For pet-supply price and promo watching.

- **URL**: https://apify.com/superslowsloth/petsmart-products.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.95 / 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.

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

## PetSmart Product, Price & Promo Scraper (US)

Search [PetSmart](https://www.petsmart.com) (the US pet retailer) by keyword, or
read a category, brand or new-arrivals page, and get one flat row per product:
the current price and the **list price in USD**, the discount in dollars and
percent, the lowest and highest price across the product's sizes, the
**promotions currently running on it** (Autoship offers, code offers, shipping
and loyalty messages), rating and review count, brand, category, pet type, the
product link and the photo. Run it on a schedule and diff the dataset to get a
**pet-supply price and promo watch**: which foods, litters and treats dropped in
price, and which only advertise an Autoship discount or a code.

No account, no API key, no login, no browser. One request returns 40 products.

### Input

| Field | Notes |
|---|---|
| `searchTerms` | One or more search terms, one per line (`cat food`, `dog dental chews`, `kong`). A brand name such as `royal canin` or `friskies` is sent by the site to that brand's page and that whole list is read; any other term is read as search results. A product found by two terms is returned once, under the first. |
| `startUrls` | PetSmart pages to read in full: a category (`/dog/food/`, `/cat/food-and-treats/dry-food/`), a brand (`/featured-brands/royal-canin/`) or a collection (`/new-and-featured/f/pet/cat`). A filter kept in the address (`?brand=Royal+Canin`) is kept on every page. A search URL (`/search/?q=...`) is read as that search term. The page must list products; the home page, `/sale/` and `/store-locator/` list none and fail with a message. Give terms, URLs or both. |
| `maxItems` | Products to read per term or page (default 100, up to 10,000). 40 per page. |
| `onlyOnSale` | Return only products whose current price is below the list price. Products that are not reduced are read, not returned and not charged. |
| `sort` | `relevance` (default), `best-sellers`, `price-asc`, `price-desc`, `new-arrivals`, `top-rated`: the choices of the site's own sort menu. See the depth note below: only `relevance` goes past 1,000 products. |
| `proxyConfiguration` | Apify datacenter proxy by default. |

Example input:

```json
{
  "searchTerms": ["cat food", "dog dental chews"],
  "startUrls": [{ "url": "https://www.petsmart.com/dog/food/" }],
  "maxItems": 120,
  "onlyOnSale": true,
  "sort": "price-asc"
}
```

### Output

Prices are numbers in US dollars; `currency` is the currency the page states
(USD on every page measured).

| Field | Notes |
|---|---|
| `query`, `position` | The search term or page URL that first found the product, and its place in the site's list for that source and sort (1 is first). |
| `product_id`, `master_product_id`, `url` | PetSmart's item number (`5209028`), the number of the product page it belongs to (`20995`; sizes and flavors of one product share it) and the product link. `url` comes from the page's structured data and is null if the page did not send it. |
| `name`, `brand` | As the page shows them (HTML entities decoded). |
| `category`, `categories`, `pet_types` | The primary category (`Dry Food`), the category list the site files it under, and the pets it is for (`Cat`, `Dog`, ...). |
| `price`, `original_price` | `price` is the price now. `original_price` is the **list price**. They are equal when the product is not reduced. Both are the price of the product's **default size**, see below. |
| `on_sale`, `discount_amount`, `discount_percent` | `on_sale` is true when `price` is below `original_price`. `discount_amount` is the dollars off and `discount_percent` is worked out from the two prices (one decimal place). Both are **null when the product is not on sale**, never 0. |
| `price_min`, `price_max` | The lowest and highest price across the product's sizes and flavors, the figures behind the card's "$15.58 - $28.59". Equal for a product with one price. |
| `promotions`, `promo_text` | The promotions PetSmart attaches to the product that are running now, in the site's own order. Each has `text`, `type` (`autoship` or null), `discount` (`35%` or null), `starts` and `ends` (ISO times or null). `promo_text` is the first one's text. An empty list and a null `promo_text` mean none. |
| `variations` | The card's variation labels, e.g. `["3 Sizes", "1 Flavor"]`. |
| `rating`, `review_count` | Average star rating and number of reviews. Null for a product with no reviews. |
| `autoship_eligible` | True if the product can be put on Autoship (repeat delivery). |
| `in_store_only`, `online_only` | The site's availability flags. Null when the page does not send the flag, which is common: null is not false. |
| `discontinued` | True for a product the site marks discontinued. Null for the rest (the page only sends the flag on some). |
| `upc` | The barcode number where the page sends one. |
| `image_url` | The product photo. |

Example row (real values, 2026-10-16):

```json
{
  "query": "cat food",
  "position": 5,
  "product_id": "5209028",
  "master_product_id": 20995,
  "name": "Hill's® Science Diet® Perfect Weight Adult Dry Cat Food - Chicken",
  "brand": "Hill's Science Diet",
  "url": "https://www.petsmart.com/cat/food-and-treats/dry-food/hills-science-diet-perfect-weight-adult-dry-cat-food---chicken-20995.html",
  "image_url": "https://s7d2.scene7.com/is/image/PetSmart/5209028?$sclp-prd-main_large$",
  "category": "Dry Food",
  "categories": ["Dry Food"],
  "pet_types": ["Cat"],
  "currency": "USD",
  "price": 69.99,
  "original_price": 71.99,
  "on_sale": true,
  "discount_amount": 2.0,
  "discount_percent": 2.8,
  "price_min": 24.99,
  "price_max": 69.99,
  "promotions": [
    { "text": "Earn $10 back in savings (5,000 points) when you spend $39+ with Free Same-Day Delivery thru 10/18. One-time use.", "type": null, "discount": null, "starts": "2026-10-13T09:30:00.000Z", "ends": "2026-10-19T09:30:00.000Z" },
    { "text": "Get 40% OFF your 1st Hill's Autoship order, 20% OFF your 2nd & 10% OFF your 3rd. Place your first order by 10/31.", "type": "autoship", "discount": "40%", "starts": "2026-10-02T09:30:00.000Z", "ends": "2026-11-01T09:30:00.000Z" },
    { "text": "Save an additional 20% on your 1st Autoship order of select dog & cat food with code: EXTRA20", "type": null, "discount": null, "starts": "2026-10-08T09:30:00.000Z", "ends": "2026-10-20T09:30:00.000Z" },
    { "text": "Free Same-Day Delivery, No Order Minimum!", "type": null, "discount": null, "starts": null, "ends": null },
    { "text": "Sign In & Enjoy Free Shipping Over $49", "type": null, "discount": null, "starts": "2026-02-03T23:00:00.000Z", "ends": null }
  ],
  "promo_text": "Earn $10 back in savings (5,000 points) when you spend $39+ with Free Same-Day Delivery thru 10/18. One-time use.",
  "variations": ["3 Sizes", "1 Flavor"],
  "rating": 4.5,
  "review_count": 1144,
  "autoship_eligible": true,
  "in_store_only": false,
  "online_only": false,
  "discontinued": null,
  "upc": "052742297002"
}
```

#### What the price fields mean

PetSmart sells one product in several sizes. The page gives the product a
**default size** with a list price and a current price, and a range across all
sizes. `price` and `original_price` are the default size's two prices (they are
the `current` and `list` figures the page sends, and the card's price text agrees
with them on every product measured: 960 products checked). `price_min` and
`price_max` are the card's range. In the example, the 24.99 to 69.99 range comes
from sizes of the same food; the default size costs 69.99, so `price_max` equals
`price` here. That is common but not a rule (the default size was the top of the
range on 267 of the 342 ranged products measured). To compare one size over time,
join on `product_id` and watch `price`.

**An Autoship or code offer is not a reduction.** "Save 35% on your first
Autoship order" and "with code: EXTRA20" do not change the shelf price: `price`
does not include them, and `on_sale` stays false when the product is not
otherwise reduced. They are in `promotions`. Filter on `promotions[].type ==
"autoship"` or on the text to see them, on `on_sale` for shelf reductions.

#### Promotions: what is in the list

The site attaches to every product its store-wide messages ("Free Same-Day
Delivery, No Order Minimum!", "Sign In & Enjoy Free Shipping Over $49",
loyalty-point offers) as well as product-specific ones, so the list is rarely
empty. The page also still sends promotions whose end date has passed and
promotions with no text (price steps of the Autoship program). The actor drops
both: a promotion is kept only if it has text and is running at the time the
page was served (judged on the page's own clock). Check `starts` and `ends` to
see how long an offer lasts.

#### Honest nulls

A field the page did not send is `null`, never `0` or `""`.

- **No rating** (`rating`, `review_count` null) means the product has no reviews.
- **`in_store_only` and `online_only`** are null when the page omits the flag.
  Do not read null as false.
- **`url`** is null if the page's structured data did not list the product.
- **`discontinued`** is only ever true or null.

### Pricing

Pay per event:

- **$0.002** per run (`actor-start`), charged only after your input is
  validated, so a run that fails on bad input costs nothing.
- **$0.00195** per product row written (`product-scraped`), i.e. $1.95 per
  1,000 products. A row that is not written is not charged: rows filtered by
  `onlyOnSale`, and repeats of a product another term already returned, are free.

### How it fetches

It reads the product list that petsmart.com's own pages embed: the first answer
of the site's search engine, as JSON in the page, plus the page's structured
data for the product links. Measured 2026-10-16: HTTP 200 from a plain home
connection and from an Apify datacenter address, no key or cookie needed. A
refusal (403, 429, 5xx), a 200 that is not a PetSmart product list, or a dropped
connection is treated as the address being blocked, and the actor rotates to a
fresh one and retries. No refusal page was ever served while this was built, so
that path is tested against a stand-in, not a recorded page.

Things worth knowing, all measured:

- **A page is about 1.5 MB** and takes 2 to 4 seconds. A run of 1,000 products is
  25 pages, around 40 MB of proxy traffic.
- **Depth depends on the sort.** `relevance` goes up to 250 pages, which is
  10,000 products (a search matching 16,912 products answered page 250 and
  refused page 251). Every other sort stops at 25 pages, **1,000 products**,
  however many match (`cat food` matches 3,478; its price-descending page 26 is
  empty). The log says so when a list was cut short, and the run summary names
  those sources. To see more than 1,000 in price order, narrow the search or use a
  category page.
- **Category and brand pages default to best sellers**, which is a sorted list and
  so also stops at 1,000 (`/dog/food/` has 1,810). The actor always sends the sort
  you chose (relevance unless you set another), so a category read with the
  default settings reads the whole list.
- **A sort the site does not know is ignored, not refused.** The site answers a
  misspelled sort with relevance order and HTTP 200. The actor sends only the
  values of the site's own menu and checks which list answered each page; if it is
  not the one asked for, that source fails with "sort was ignored" instead of
  returning the wrong order.
- **A brand name is redirected.** `royal canin`, `friskies`, `blue buffalo` and
  `hill's science diet` are sent by the site to `/featured-brands/<brand>/`, and
  the redirect drops the sort and the page. The actor reads that brand page,
  again with your sort, and continues from there; the run log names the page.
  `query` stays the term you typed. Most other terms (`cat food`, `litter` with a
  sort, `purina`) are answered in place as search results.
- **Terms are cleaned.** Spaces at the start and end and doubled spaces change the
  site's result count (`cat food` finds 3,219 and `cat food` 3,478), so the
  actor trims and collapses them. Case does not matter.
- **A search with no match is not an error.** The site answers it with HTTP 404 and
  no products; the actor reports "no results" in the log and returns nothing for
  that term, at no charge. A page that does not exist (an unknown category path)
  is also HTTP 404, but its body is the site's whole default list; the actor treats
  that as a missing page and fails the source.
- **Not offered.** The site's facet filters are not inputs. A filter kept in a
  start URL's address (`?brand=Royal+Canin`) is honoured by the site and kept by
  the actor; start from a page that already narrows the list.
- **No stock levels.** Product pages hold the stock; the list pages do not, so the
  rows say nothing about availability beyond `in_store_only` and `online_only`.

# Actor input Schema

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

What you would type into the PetSmart search box, one term per line (for example cat litter, dog dental chews or kong). Each term is read separately. A brand name (royal canin, friskies) is sent by the site to that brand's page and its whole list is read; any other term is read as search results. A product that two terms both find is returned once, under the first.

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

PetSmart pages to read in full, for example https://www.petsmart.com/dog/food/, https://www.petsmart.com/cat/food-and-treats/dry-food/ or https://www.petsmart.com/featured-brands/royal-canin/. Use a page that lists products; the home page, the sale hub and the store finder list none and are refused. A facet kept in the address (?brand=Royal+Canin) is kept. A search URL (petsmart.com/search/?q=...) is read as that search term. Leave empty to use search terms only.

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

Read at most this many products for each search term or page. The site lists 40 per page, so 100 reads three pages. Up to 10,000 are accepted, but every order other than Relevance stops at 1,000 products on the site's side.

## `onlyOnSale` (type: `boolean`):

Return only products whose current price is below the list price (the on_sale field). Products that are not reduced are read but not returned and not charged, and do not count toward Max products. An Autoship or code offer is not a reduced price; its row has on_sale false, so read the promotions field for those.

## `sort` (type: `string`):

The order PetSmart lists the products in, the same choices as its own sort menu. Relevance is the site's search default and reaches the deepest (10,000 products); every other order stops at 1,000 products. A category or brand page that is read without Relevance is limited the same way.

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

Use Apify's automatic datacenter proxy (the default). petsmart.com answered an Apify datacenter address with HTTP 200 when this actor was built; the residential group was not needed. A refused request is retried from a fresh address. Each page is about 1.5 MB, so a large run uses proxy bandwidth.

## Actor input object example

```json
{
  "searchTerms": [
    "cat food"
  ],
  "startUrls": [],
  "maxItems": 100,
  "onlyOnSale": false,
  "sort": "relevance",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

No description

# 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 = {
    "searchTerms": [
        "cat food"
    ],
    "startUrls": [],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/petsmart-products").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 = {
    "searchTerms": ["cat food"],
    "startUrls": [],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/petsmart-products").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 '{
  "searchTerms": [
    "cat food"
  ],
  "startUrls": [],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call superslowsloth/petsmart-products --silent --output-dataset

```

## MCP server setup

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

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/bnE0AZRre3J6O4y7F/builds/yaQNCbmaWkJzghV4c/openapi.json
