# Zoro Scraper (`crawlerbros/zoro-scraper`) Actor

Scrape wholesale retail products from Zoro.com (Grainger's brand) - search by keyword, browse 27 categories or fetch by product URL. Prices, list prices, brands, MPNs, ratings, specs and images.

- **URL**: https://apify.com/crawlerbros/zoro-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Automation, E-commerce, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

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

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Zoro Scraper

Scrape **Zoro.com** — Grainger's wholesale retail marketplace with 10M+ industrial, safety, janitorial and office products. Search by keyword, browse 27 top-level categories, or fetch products by URL. Real prices, list prices, brands, manufacturer part numbers, ratings, specs, availability and product images. No API key, no login, no cookies.

### What this actor does

- **Three modes:** `search` (keyword), `browseByCategory` (27 curated categories or any category URL), `byUrl` (product URLs)
- **Real wholesale data:** prices (USD), list prices, price-per-unit, brands, MPNs, UPCs, ratings, review counts
- **Full detail mode:** description, spec tables, country of origin, availability, category path, weights, all product images
- **Filters:** brand dropdown, min/max price, min rating, contains-keyword
- **Empty fields are omitted**

### Data Source

**Zoro.com** (www.zoro.com), the wholesale retail marketplace owned by Grainger (GWW). Listing pages are server-rendered: the actor reads the SSR HTML directly (TLS-impersonated HTTP client, no JavaScript, no cookies). Ratings and review counts come from Zoro's public PowerReviews read-services JSON API (merchant ID and key are public constants embedded in the site's own page config).

**Why not Menards?** This actor was originally requested as a Menards.com scraper. Menards is protected by Incapsula, which hard-blocks all datacenter IPs with an HTTP 403 challenge before any page logic runs — across all browser TLS fingerprints. Because the block is IP-based, a real browser from the same IP gets the same 403, and only residential proxy IPs can pass — which would make the actor paid-only and unverifiable. Per the free-plan-friendly requirement, the actor was rebuilt against **Zoro.com**, the closest same-category replacement: a wholesale retail platform with a fully public, server-rendered surface that works from any datacenter IP with no credentials.

### Output per product (search / category modes)

- `productId` — Zoro item number (e.g. `G805612238`)
- `title`, `brand`, `mfrNo` — manufacturer part number
- `price` — USD float; `currency: "USD"`, `priceUnit` (e.g. `/ea`, `/pk 24, $7.21/ea`)
- `listPrice` — original/street price when Zoro shows a strikethrough
- `rating`, `reviewCount` — from Zoro's public ratings API
- `imageUrl` — primary product image (CDN-hosted)
- `productUrl` — canonical product page on zoro.com
- `sourceUrl` — canonical source page for the record
- `recordType: "product"`, `scrapedAt`

### Output per product detail (mode = `byUrl`, or search/category with `includeDetails`)

Everything above plus:

- `description` — long-form product description
- `specs` — key features/specs as name→value pairs (e.g. `Voltage → 20 V DC`), capped at 40
- `upc`, `countryOfOrigin`
- `availability` — `InStock` / `OutOfStock` from the page's structured data
- `weight` — e.g. `4.7 lb`
- `categoryPath` — breadcrumb trail (e.g. `Tools & Machining > Power Tools and Accessories > Cordless Drills`)
- `freeShipping` — whether Zoro advertises free shipping on the product
- `imageUrls` — all product images on the detail page
- `recordType: "productDetail"`, `scrapedAt`

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `search` | `search` / `browseByCategory` / `byUrl` |
| `searchQuery` | string | `drill` | Keyword query (mode=search) |
| `category` | string | – | One of 27 top-level categories (mode=browseByCategory) |
| `categoryUrl` | string | – | Any zoro.com category URL; overrides `category` (mode=browseByCategory) |
| `startUrls` | array | – | Zoro.com product URLs (mode=byUrl) |
| `brand` | string | – | Only emit products from this brand (case-insensitive) |
| `minPrice` | int | – | Drop products priced below this (USD) |
| `maxPrice` | int | – | Drop products priced above this (USD) |
| `minRating` | number | – | Only emit products rated at least this (0–5) |
| `containsKeyword` | string | – | Only emit products whose title/description contains this text |
| `includeDetails` | boolean | `false` | Also fetch each product's detail page (specs, description, UPC) |
| `maxItems` | int | `50` | Hard cap on records (1–1000) |
| `proxyConfiguration` | object | `AUTO` | Optional; only used as a fallback if Zoro rate-limits or blocks |

### Examples

#### Example: search wholesale products by keyword

```json
{
  "mode": "search",
  "searchQuery": "safety glasses",
  "maxItems": 20
}
```

#### Example: browse a whole category with details

```json
{
  "mode": "browseByCategory",
  "category": "tools-machining/c/z2",
  "includeDetails": true,
  "maxItems": 50
}
```

#### Example: filtered search by brand and price

```json
{
  "mode": "search",
  "searchQuery": "drill",
  "brand": "Milwaukee Tool",
  "minPrice": 100,
  "maxPrice": 300,
  "minRating": 4.0,
  "maxItems": 30
}
```

#### Example: fetch specific products by URL

```json
{
  "mode": "byUrl",
  "startUrls": [
    {"url": "https://www.zoro.com/dewalt-12-in-20v-dc-cordless-drill-kit-battery-included-dcd771c2/i/G8579697/"}
  ],
  "maxItems": 5
}
```

### Use cases

- **Price intelligence** — track wholesale pricing and list-price discounts across industrial categories for competitive benchmarking
- **Procurement research** — compare manufacturer part numbers, pack sizes and ratings before ordering
- **eCommerce enrichment** — join Zoro specs (voltage, dimensions, material) into your own product catalog via UPC or Mfr #
- **B2B lead lists** — enumerate a brand's full wholesale catalog for sales targeting
- **Inventory planning** — monitor category breadth, availability and price bands for janitorial/office/MRO spend

### FAQ

**Is this affiliated with Zoro or Grainger?**
No. This is a third-party actor that reads Zoro's public website. It is not endorsed by or affiliated with Zoro.com or Grainger.

**Why did this actor end up scraping Zoro instead of Menards?**
Menards' edge protection (Incapsula) blocks all datacenter IPs with HTTP 403 before any page loads. Bypassing it requires residential proxy (paid-only). Zoro is the closest wholesale-retail replacement with a fully public surface. See the Data Source section.

**Do I need a proxy or API key?**
No. Zoro serves all pages to regular datacenter IPs without auth. The proxy field is optional and only kicks in automatically if Zoro rate-limits a run.

**Are prices live?**
Yes. Prices are read from the server-rendered pages at scrape time. Products without a listed price are emitted without a `price` field rather than a placeholder.

**How fresh is the data?**
As fresh as the moment you run the actor — pages are fetched live, never cached.

**Why are some fields missing on search results?**
Search/category listings return compact product cards (no description/specs). Turn on `includeDetails` or use `byUrl` to get full detail records. Empty fields are always omitted.

**What does `minRating` mean for products with no reviews?**
Products without any ratings have no `rating` field and pass the filter through; only products *with* a rating below your threshold are dropped.

**Does it work outside the US?**
Zoro.com is US-focused (USD pricing, US shipping). Data is readable from anywhere, but pricing and availability reflect the US store.

**How many products are on a page?**
Up to 36 per listing page; the actor paginates automatically until `maxItems` is reached or results run out.

**Can I browse subcategories?**
Yes — pass any subcategory URL to `categoryUrl` (e.g. `https://www.zoro.com/cordless-drills/c/5861/`), or pick a top-level `category` and the actor includes all subcategories via Zoro's shop-all listing.

### Limitations

- **Ratings lag the page:** ratings/review counts come from Zoro's public ratings API and may not exist for brand-new products — those records simply omit `rating`/`reviewCount`.
- **eClass/UNSPSC not exposed:** Zoro's product pages do not carry eClass or UNSPSC classification codes, so no such field is emitted.
- **Single-word queries can return empty SSR pages** (e.g. bare `tape`) — Zoro's search engine renders no product cards for some short queries without a browser; use a more specific query (e.g. `duct tape`).
- **Occasional broken upstream image URLs:** a very small share of Zoro's CDN image URLs 404 even from a browser (observed ~0.3% of sampled products). The URL is emitted exactly as Zoro serves it; verify critical images before bulk use.

# Actor input Schema

## `mode` (type: `string`):

What to fetch.

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

Free-text keyword query, e.g. `drill`, `paper towels`, `safety glasses` (mode=search).

## `category` (type: `string`):

Top-level category to browse. All subcategories are included via Zoro's shop-all listing.

## `categoryUrl` (type: `string`):

Any zoro.com category URL, e.g. `https://www.zoro.com/cordless-drills/c/5861/`. Takes precedence over `category` and lets you browse subcategories directly.

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

One or more zoro.com product URLs to fetch, e.g. `https://www.zoro.com/.../i/G805612238/`.

## `brand` (type: `string`):

Only emit products from this brand (case-insensitive). Products without a brand name pass through.

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

Drop products priced below this. Products without a listed price pass through.

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

Drop products priced above this.

## `minRating` (type: `number`):

Only emit products rated at least this (0–5, one decimal). Products without a rating pass through.

## `containsKeyword` (type: `string`):

Only emit products whose title (or description) contains this text, case-insensitive.

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

For search/category modes: also fetch each product's detail page for description, specs, UPC, availability and category path. Increases runtime and request count.

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

Hard cap on emitted records.

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

Optional. Zoro works without a proxy on most networks; if set, Apify proxy is used automatically as a fallback when requests are rate-limited or blocked. Leave the group unset to use your account's first available proxy group (e.g. RESIDENTIAL), or pick a specific group.

## Actor input object example

```json
{
  "mode": "search",
  "searchQuery": "drill",
  "startUrls": [],
  "includeDetails": false,
  "maxItems": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Dataset containing all scraped Zoro.com products.

# 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 = {
    "mode": "search",
    "searchQuery": "drill",
    "startUrls": [],
    "includeDetails": false,
    "maxItems": 5,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/zoro-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 = {
    "mode": "search",
    "searchQuery": "drill",
    "startUrls": [],
    "includeDetails": False,
    "maxItems": 5,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/zoro-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 '{
  "mode": "search",
  "searchQuery": "drill",
  "startUrls": [],
  "includeDetails": false,
  "maxItems": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call crawlerbros/zoro-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlerbros/zoro-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/9BVVfcBXLBDc25PKn/builds/toZoa4ENfiD2pNQye/openapi.json
