# Lowe's Product Scraper: Prices, Stock & Specs (`dami_studio/lowes-scraper`) Actor

Search lowes.com by keyword, or paste category and product URLs, and get the product data back as rows: title, brand, item and model number, price with the was/now saving, rating, review count, and whether it ships, is in stock for pickup or can be delivered - per store, for any US ZIP.

- **URL**: https://apify.com/dami\_studio/lowes-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** E-commerce, Lead generation
- **Stats:** 4 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.40 / 1,000 product returneds

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

## Lowe's Product Scraper: prices, store stock and specs from lowes.com

Give it a search term, a Lowe's search, category or product link, or a bare product id. You get one
row per product: title, brand, item and model number, price with the was/now saving, rating and
review count, and whether it ships, sits on a shelf for pickup or can be delivered. Your own store's
price and stock need `full` detail, since the faster `listing` mode shows whatever default store and
price Lowe's picks, and one search or category stops at 744 products.

| | |
|---|---|
| **Input** | Search terms, Lowe's search, category or product links, or bare product ids |
| **Output** | One row per product |
| **Ceiling** | 5,000 products per run, and 744 per search term or category |
| **Account needed** | None |
| **Price** | $4.40 per 1,000 products, flat on every plan |

### 🔍 What Lowe's Product Scraper does

It pages through Lowe's results 24 products at a time until it has `maxItems` or reaches the 744
Lowe's shows for one term or category.

`listing` mode takes what the results page carries: title, brand, price, rating, review count and
stock flags. Some tiles say "See Product Page" instead of a price, and on some categories that is
every tile, so those rows get their product page opened and the price filled in. They are marked
`listing+price`.

`full` mode opens the product page for every row. That brings the store's own price, which is often
the one carrying the was/now saving, stock at your store, the full specifications, the barcode,
weight and dimensions. It adds one lookup per product, so a big run takes longer; 45 products took
23 seconds on a test run. The price per product is the same either way.

Prices, promotions and pickup stock differ from store to store. Give a ZIP and it finds the nearest
store: `90210` resolves to store 2714 in Los Angeles, `28202` to store 2348 in Charlotte. Give a
store number if you know it. Give neither and it uses store 0595. `priceScope` on each row says
whether the price is that store's (`STORE`) or Lowe's national one (`NETWORK`).

### 📥 What you give it

```json
{
  "searchQueries": ["smoke detector", "door knob"],
  "maxItems": 45,
  "detailLevel": "full",
  "storeNumber": "1636"
}
```

| Field | If you leave it out | What it is |
|---|---|---|
| `searchQueries` | none | Search terms, one per line, as you would type them into Lowe's search box. A model number works too. |
| `startUrls` | none | Lowe's search pages, category (`/pl/...`) pages or product (`/pd/...`) pages. A bare product id like `5013207487` works too. Mixes freely with the terms above. |
| `maxItems` | `50` | 1 to 5,000 products, across everything you asked for. You pay per product, so this is also your budget. |
| `detailLevel` | `listing` | `listing` for speed, `full` for store price, store stock, specs and dimensions. |
| `zipCode` | none | A US ZIP. The nearest Lowe's store is looked up and used. |
| `storeNumber` | store `0595`, unless a ZIP is given | A four-digit store number, such as `0595` or `1636`. Wins over the ZIP. It is used exactly as typed, so check it. |
| `proxyConfiguration` | not used | Only for your own servers. Addresses you list here are used as given. Leave it alone otherwise. |

The form opens with `cordless drill` and a cap of 50 filled in, as an example.

### 📤 What you get back

A real row from a real `full` run for store 1636, with the photo list, bullets, specifications and
dimensions cut short:

```json
{
  "productId": "5019242437",
  "itemNumber": "9055247",
  "modelNumber": "SC2-W10-T",
  "brand": "First Alert",
  "title": "10-Year Battery, Hardwired (with Battery Backup) Photoelectric Interconnected Combination Smoke and Carbon Monoxide Detector",
  "url": "https://www.lowes.com/pd/First-Alert-FA-10YR-HW-PHOTO-COMBO-W-PLUG-10E/5019242437",
  "price": 50.15,
  "priceMax": null,
  "priceIsRange": false,
  "listPrice": 75.98,
  "savingsAmount": 25.83,
  "savingsPercent": 34,
  "promotionEndsAt": "2026-11-02T00:00:00.000Z",
  "priceScope": "STORE",
  "isDealOfTheDay": false,
  "promotions": ["Free Delivery", "Delivery"],
  "freeShipping": null,
  "availabilityMessage": null,
  "rating": 4.6,
  "reviewCount": 11,
  "fulfillment": [
    { "method": "Parcel", "available": true, "quantity": 94, "onHandQuantity": 70, "leadTimeDays": 0, "storeNumber": "2879" },
    { "method": "Pickup", "available": true, "quantity": 61, "onHandQuantity": 56, "leadTimeDays": 0, "storeNumber": "1636" },
    { "method": "Delivery", "available": true, "quantity": 90, "onHandQuantity": 0, "leadTimeDays": 2, "storeNumber": "2879" },
    { "method": "ExpeditedDelivery", "available": true, "quantity": 85, "onHandQuantity": 56, "leadTimeDays": null, "storeNumber": "1636" }
  ],
  "availableForShipping": true,
  "availableForPickup": true,
  "availableForDelivery": true,
  "pickupStoreNumber": "1636",
  "pickupQuantity": 56,
  "deliveryLeadTimeDays": 2,
  "storeNumber": "1636",
  "inStock": true,
  "imageUrl": "https://mobileimages.lowes.com/productimages/4132bf23-c263-4973-83e9-ca0c8bbae520/83540612.png",
  "images": ["https://mobileimages.lowes.com/productimages/4132bf23-c263-4973-83e9-ca0c8bbae520/83540612.png"],
  "bulletPoints": ["10-year sealed battery eliminates the need for battery replacements and low-battery chirps for the life of the alarm"],
  "specifications": [
    { "key": "Power Source", "value": "Hardwired" },
    { "key": "Alarm Sensor Type", "value": "Photoelectric" }
  ],
  "department": "ELECTRICAL",
  "category": "FIRE SAFETY",
  "subCategory": "COMBO ALARMS",
  "barcode": "194160017418",
  "detailLevel": "full",
  "searchQuery": "smoke detector",
  "sourceUrl": null,
  "position": 21,
  "scrapedAt": "2026-09-20T00:40:45.108Z",
  "weight": 0.805,
  "dimensions": {
    "height": { "uom": "Inches", "value": "7.13", "conversions": [{ "uom": "Centimeters", "value": "18.1102", "system": "Metric" }] }
  },
  "lowesExclusive": null,
  "energyStar": null,
  "maxPurchaseQuantity": null
}
```

| Field | What it is |
|---|---|
| `price`, `priceScope` | The selling price, and whether it is the store's (`STORE`) or Lowe's national figure (`NETWORK`). |
| `listPrice`, `savingsAmount`, `savingsPercent`, `promotionEndsAt` | The "was" price and the saving. `listPrice` is null when the item is not on promotion, not when a price is missing. |
| `priceIsRange`, `priceMax` | Items with variants at different prices. `price` is then the "from" figure and `priceMax` the top. |
| `fulfillment` | One entry per way Lowe's offers the item. `Parcel` is shipped, `Pickup` is in store, `Delivery` comes by truck. The `storeNumber` inside each is where Lowe's would source it; for parcel that is usually a warehouse, not your store. |
| `onHandQuantity`, `quantity` | What is physically at that location, and what Lowe's can pull in from elsewhere. |
| `availableForShipping`, `availableForPickup`, `availableForDelivery`, `pickupQuantity` | The same stock picture flattened into columns. |
| `storeNumber`, `pickupStoreNumber` | The store the row was asked for, and the store the pickup figures belong to. In `listing` mode these can differ. |
| `detailLevel` | `listing`, `listing+price` (price fetched from the product page) or `full`. |
| `barcode`, `weight`, `dimensions`, `specifications` | Filled in by `full`. `barcode` is often null on `listing` rows, and `weight` and `dimensions` only appear when the product page was read (`full` or `listing+price`). |

### 🧾 Reading the output

Real product rows have no `charged` field. The free rows carry `charged: false`, so dropping those
leaves only products.

| Row | How to spot it | Charged |
|---|---|---|
| A product | a `productId` and no `charged` field | yes |
| The sample | `_sample: true` | no |
| A note | `_diagnostic: true`, with an `errorCode`, an `error` and a `hint` | no |

The sample row appears only when a run has no search terms and no links. It looks like a full
product row, so filter on `_sample` or `charged` rather than on the fields.

| `errorCode` | What it means |
|---|---|
| `REFUSED` | Lowe's did not answer that search, category or product this time. Re-running usually gets it. |
| `NO_RESULTS` | Lowe's returned no products for that term or category. Try a broader term or check the spelling. |
| `PRODUCT_NOT_FOUND` | No product behind that id. Ids change when an item is discontinued or merged. |
| `BAD_URL` | Not a Lowe's search, category or product link, and not a product id. |
| `STORE_NOT_FOUND` | No store came back for that ZIP. The run carries on with store 0595. |

### ▶️ How to run it

1. Open [Lowe's Product Scraper](https://apify.com/dami_studio/lowes-scraper) and click **Try for
   free**.
2. Type what you want into **Search terms**, or paste pages into **Lowe's URLs or product IDs**.
3. Set **Maximum products**.
4. For your store's price and stock, set **How much detail per product** to Full and fill in
   **ZIP code** or **Store number**.
5. Click **Start**, then download the dataset as JSON, CSV or Excel.

### 💰 How much does it cost?

**$4.40 per 1,000 products**, flat on every Apify plan. `full` costs the same per product as
`listing`.

Not charged: the sample row, every note above, and a product that turns up under two of your
search terms, which is delivered once. A run that finds nothing charges for no products.

### 💡 What people use it for

- Tracking prices by model number on a schedule. The was/now pair comes through, so a promotion
  shows up as a promotion rather than as a lower number.
- Pulling a whole category. `/pl/Drills/4294857977` gave 320 products when it was tested.
- Checking pickup stock at one store before a job, with `full` and that store's ZIP.
- Matching Lowe's items to a product list by brand and model number, with the barcode from `full`.

### 🚧 What it does not do

- **US only.** lowes.com. Lowe's Canada is a separate site.
- **744 products per search term or category.** That is how deep Lowe's results go. Split a big pull
  into narrower terms, or use category links, which each have their own 744.
- **No review text.** Star rating and review count only.
- **No sorting or filtering.** Rows come back in Lowe's default order. Sort after export.
- **No MyLowe's or Pro pricing.** These are signed-out prices, with no member or cart discounts.
- **Some items have no price anywhere.** Special orders, quote-only items and installed services come
  back with `price: null` rather than a guess.
- **A store number is not checked.** Lowe's answers for any four-digit number, so a typo gives you a
  store that does not exist. A ZIP is looked up, and a wrong one gets a free note.

### 🧭 Which home and retail scraper do you need?

| If you want | Use |
|---|---|
| Lowe's products with store price, stock and specs | This one |
| Target products with promotions and store pickup stock | [Target.com Product Scraper](https://apify.com/dami_studio/target-scraper) |
| Amazon products by keyword or ASIN | [Amazon Scraper](https://apify.com/dami_studio/amazon-scraper) |
| An alert when any product page changes price or stock | [Product Price and Stock Monitor](https://apify.com/dami_studio/structured-product-price-stock-monitor) |

### ❓ Questions people ask

**Do I need a Lowe's account or an API key?** No. Nothing to sign up for and nothing to paste.

**How do I get prices for my local store?** Put your ZIP in `zipCode` and set `detailLevel` to
`full`. Check `priceScope` on each row: `STORE` is your store's price.

**How many products can I get for one search term?** 744. For more, use several narrower terms or
category links.

**Why is `price` null on some rows?** Lowe's has no public price on that item, usually a special
order, a quote-only product or an installed service.

**Can I sort by price or filter by brand?** Not inside the run. Every row has `price`, `brand` and
`rating`, so sort after export.

**Is this affiliated with Lowe's?** No. It reads what lowes.com shows anyone signed out. "Lowe's" is a
trademark of Lowe's Companies, Inc., used here only to say what the actor reads. On the legal side,
Apify's write-up on [the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/) is
a good starting point. We are not lawyers.

### 🆘 If something breaks

Open the **Issues** tab on the actor page and send the input you used and the run ID. The note rows
in the dataset usually name the reason already.

# Actor input Schema

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

What to look for on lowes.com, one term per line — "cordless drill", "kitchen faucet", "KDD 2024A-03". Lowe's serves 24 products a page and stops at 744 per term, so split a big pull across several terms rather than one broad one.

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

Paste Lowe's search pages (https://www.lowes.com/search?searchTerm=drill), category pages (https://www.lowes.com/pl/Drills/4294857977) or product pages (https://www.lowes.com/pd/.../5013207487). A bare product id such as 5013207487 works too. Mixes freely with the search terms above.

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

Hard ceiling on products returned across everything you asked for. You are charged per product returned, so this is also your budget.

## `detailLevel` (type: `string`):

Listing gives you what the results page shows — title, brand, price, rating, stock — and quietly opens the product page for any row Lowe's listed without a price, so the price column is not full of blanks. Full opens the product page for every row instead, which adds the store-level price, the was/now saving, stock at your store, specs, weight and dimensions. Full is slower; the price per product is the same either way.

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

A US ZIP, used to pick the nearest Lowe's store. Prices, promotions and pickup stock differ store to store, so this changes what you get back.

## `storeNumber` (type: `string`):

A four-digit Lowe's store number such as 0595 or 1636, if you already know it. Wins over the ZIP code above.

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

Optional. Leave this alone unless you need the run to go out through a particular network. Your own proxy servers are used exactly as given.

## Actor input object example

```json
{
  "searchQueries": [
    "cordless drill"
  ],
  "startUrls": [],
  "maxItems": 50,
  "detailLevel": "listing",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One row per Lowe's product: title, brand, item and model number, price with the was/now saving, promotion labels, rating and review count, shipping, store pickup and delivery availability with quantities and lead times, images, department and category, and the product URL. Full detail adds store-level pricing, stock at your store, specifications and dimensions. Empty input, a URL Lowe's does not recognise, or a request Lowe's turns away writes an uncharged sample or diagnostic row instead.

# 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 = {
    "searchQueries": [
        "cordless drill"
    ],
    "startUrls": [],
    "maxItems": 50,
    "detailLevel": "listing",
    "zipCode": "",
    "storeNumber": "",
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/lowes-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 = {
    "searchQueries": ["cordless drill"],
    "startUrls": [],
    "maxItems": 50,
    "detailLevel": "listing",
    "zipCode": "",
    "storeNumber": "",
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/lowes-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 '{
  "searchQueries": [
    "cordless drill"
  ],
  "startUrls": [],
  "maxItems": 50,
  "detailLevel": "listing",
  "zipCode": "",
  "storeNumber": "",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call dami_studio/lowes-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/lowes-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/Hchq3U0V6uJTxJax0/builds/2AW3Lb3MRUH0F9SJT/openapi.json
