Grainger Scraper — item numbers to prices, specs avatar

Grainger Scraper — item numbers to prices, specs

Pricing

$15.00 / 1,000 product looked ups

Go to Apify Store
Grainger Scraper — item numbers to prices, specs

Grainger Scraper — item numbers to prices, specs

Look up Grainger item numbers past DataDome and get today's web price, brand, model, tech specs, weight and the discontinued flag. No browser. Powered by xhr.dev.

Pricing

$15.00 / 1,000 product looked ups

Rating

0.0

(0)

Developer

xhrdev

xhrdev

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

3 days ago

Last modified

Categories

Share

Look up Grainger item numbers past DataDome and get today's price. Hand it the item numbers you already hold — the codes Grainger prints as "Item #" — and it returns web price, unit of measure, brand, model, full technical specifications, shipping weight, category path, images and the discontinued flag.

This is built as a lookup, not a crawl, because that is how people actually use Grainger: you have a bill of materials or a parts list, and you want current pricing and specs against it.

No browser is launched. The DataDome challenge is solved as plain HTTP by xhr.dev, so a lookup costs a few hundred milliseconds of compute instead of the several seconds and gigabyte of RAM a stealth browser needs.

Quickstart

Paste this into the input and press Run.

{
"itemIds": [
"48TK84",
"1YHL8"
]
}

Search and category URLs are rejected, deliberately. Grainger builds its result grids in the browser, so the HTML an HTTP scraper receives contains no products at all. An Actor that accepted those URLs would fetch them successfully, parse nothing, and return you empty pages that look like a bug.

Rather than do that, they are refused at the start of the run with an explanation. If you need to go from keyword to item number, do the discovery step elsewhere and bring the numbers here.

Input

FieldTypeDefaultDescription
itemIdsstring[]["48TK84"]The item numbers to look up — the code Grainger prints as "Item #". This is the main way to use this Actor.
startUrlsarray[]Grainger /product/ URLs, if you hold links rather than numbers. Search and category URLs are not accepted (see below).
maxItemsinteger200Stop once this many products have been scraped.
proxyConfigurationobjectApify RESIDENTIALWhich proxy to route through. Leave it on residential — see Proxies.
maxRetriesinteger3Attempts per page before giving up on it, 1–5. Each attempt takes a fresh proxy session.
timeoutSecsinteger120How long one page — challenge, solve and all — may take before the attempt fails. 10–300.
maxConcurrencyinteger4Pages worked on at once, 1–10.

Nothing is required — the defaults run a real search out of the box.

Output

One dataset item per product. Fields the page does not carry are omitted rather than returned as null.

FieldTypeDescription
skustringThe Grainger item number you asked for.
namestringFull product name.
urlstringProduct page URL.
brandstringManufacturer, e.g. 3M.
modelstringManufacturer model number.
productIdstringGrainger internal product ID.
descriptionstringShort product description.
pricenumberCurrent public web price. Omitted when Grainger shows no public price — discontinued items and quote-only lines. Check for its absence rather than assuming zero.
currencystringAlways USD.
priceLabelstringWhat kind of price it is, e.g. Web Price.
unitOfMeasurestringWhat the price buys — each, pk, cs. Read this before comparing prices.
isDiscontinuedbooleanWhether Grainger has discontinued the item.
shippingWeightstringShipping weight as printed, e.g. 0.11 lbs.
categorystringFull category path, e.g. Safety / Eye Protection and Accessories / Safety Glasses.
specificationsobjectThe technical spec table as key/value pairs. Contents vary by product type.
imagesstring[]Product image URLs.

Example record

Taken verbatim from a live run. Long text and image lists are trimmed here for readability; the real record carries them in full.

{
"sku": "48TK84",
"name": "3M Safety Glasses: Clear Lens Color, Frameless, Scotchgard™ Anti-Fog, Black/Green Frame Color",
"url": "https://www.grainger.com/product/48TK84",
"brand": "3M",
"model": "S1201SGAF-KT",
"productId": "46181802",
"description": "3M, Clear Lens Color, Safety Glasses",
"price": 28.91,
"currency": "USD",
"priceLabel": "Web Price",
"unitOfMeasure": "each",
"isDiscontinued": false,
"shippingWeight": "0.11 lbs",
"category": "Safety / Eye Protection and Accessories / Safety Glasses",
"specifications": {
"Item": "48TK84",
"Lens Color": "Clear",
"Frame Color": "Black/Green",
"Lens Properties": "Advanced Anti-Fog",
"Catalog Page": "1585",
"Brand": "3M"
},
"images": [
"https://static.grainger.com/rp/s/is/image/Grainger/48TK84_AS02"
]
}

A discontinued item, which carries no price

{
"sku": "1YHL8",
"name": "Locknut Plier: 2 3/8 in Jaw Lg, 7/8 in Jaw Wd, 1 1/2 in Max Jaw Opening, 8 1/2 in Overall Lg",
"url": "https://www.grainger.com/product/1YHL8",
"brand": "CHANNELLOCK",
"model": "960",
"productId": "27112115",
"description": "2 3/8 in Jaw Lg, Locknut Plier",
"currency": "USD",
"isDiscontinued": true,
"shippingWeight": "0.33 lbs",
"specifications": {
"Item": "1YHL8",
"Catalog Page": "N/A",
"Brand": "CHANNELLOCK",
"Product Description": "Notched jaws in locknut pliers provide a firm grip to tighten and loosen conduit locknuts and caps. Serrations in the jaws debur the outside of conduit, and sharp edges on the nose ream the inside of conduit. The pliers' long, slim nose reaches deep into electrical boxes and other tight spaces to grab wire and pull fish tape."
},
"images": [
"https://static.grainger.com/rp/s/is/image/Grainger/1YHL8_AS01"
]
}

Pricing

$15.00 per 1,000 lookups (product-detail, $0.015 each), charged only on products actually delivered. An item number that fails to resolve is not charged.

A discontinued item is charged: the page parsed and you got a real answer — name, brand, model, specs and isDiscontinued: true — which is usually exactly what you needed to know. It simply carries no price.

A 500-part bill of materials priced end to end costs about $7.50, plus Apify platform costs — mostly residential proxy traffic, on the order of $0.25–0.75 for a run that size.

Proxies: read this before changing anything

Set this to Apify RESIDENTIAL and leave it there. It is not a style preference, and it is the single most common reason a run comes back empty.

DataDome decides which challenge to serve based on the exit IP. We measured this from Apify against twelve DataDome-protected sites, same code, minutes apart:

Exit IPChallenge servedResult
Datacenter (Apify default, or no proxy)The hard captcha3 of 12 sites passed
ResidentialInterstitial, or no challenge at all12 of 12 sites passed, 2–5s each

A datacenter address does not make Grainger slower. It changes the problem into a different, much harder one. Every attempt already pins a fresh residential session automatically, so a burnt exit node gets a genuinely new IP rather than a retry down the same dead pipe.

If a run fails wholesale, check the proxy group before anything else.

Run it from the API or CLI

Replace <TOKEN> with your Apify API token.

Run and get the results back in one call:

curl -X POST "https://api.apify.com/v2/acts/xhrdev~grainger-scraper/run-sync-get-dataset-items?token=<TOKEN>" \
-H "Content-Type: application/json" \
-d '{"itemIds":["48TK84","1YHL8"]}'

Start a run without waiting:

curl -X POST "https://api.apify.com/v2/acts/xhrdev~grainger-scraper/runs?token=<TOKEN>" \
-H "Content-Type: application/json" \
-d '{"itemIds":["48TK84","1YHL8"]}'

With the Apify CLI:

$apify call xhrdev/grainger-scraper --input '{"itemIds":["48TK84","1YHL8"]}'

Fetching results

Every run writes to a dataset. Change format for JSON, CSV, or Excel:

# JSON
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=json"
# CSV
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=csv"
# Page through a large dataset
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&offset=1000&limit=1000"

<DATASET_ID> comes back as defaultDatasetId on the run object.

Limits and failure behaviour

100 pages per run, hard. Not configurable. It is a guard against an oversized input list turning into a bill and a hammering of Grainger. For more than that, split the work across runs.

Failures never enter the dataset. A page that could not be fetched is not written as a half-empty row — that would corrupt the clean table you are paying for. Instead every run writes a RUN_SUMMARY record to the key-value store:

{
"site": "grainger",
"itemsScraped": 2,
"pagesFetched": 2,
"pagesFailed": 0,
"failures": [],
"solverHost": "https://trial.xhr.dev"
}

Read it at https://api.apify.com/v2/key-value-stores/<STORE_ID>/records/RUN_SUMMARY. failures holds up to 50 entries, each with the URL and why it failed.

A run that scrapes nothing and failed at least one page exits as failed, rather than reporting success over an empty dataset. If everything failed, the message points at the proxy group first, because that is nearly always the cause.

Spend cap. Set maxTotalChargeUsd on the run. The Actor stops fetching once it is reached, mid-run, rather than overshooting.

Questions

A run came back empty. Why? Check the proxy group first — a datacenter exit is the cause the overwhelming majority of the time. Then read RUN_SUMMARY in the key-value store for the per-page reasons.

Is this affiliated with Grainger? No. This is an independent tool with no affiliation with, endorsement by, or connection to Grainger or any bot-protection vendor. Names and trademarks belong to their owners.

How does it get past the challenge without a browser? It is solved as HTTP, by xhr.dev. No Chrome is launched, which is why a page costs a few hundred milliseconds of compute instead of the several seconds and ~1 GB of RAM a stealth browser needs.

Can I use the solver directly, on a site that isn't Grainger? Yes — that is DataDome Unblocker, which takes any URL and hands back the HTML and the clearance cookies. There is also Akamai Unblocker for Akamai Bot Manager.

Can I run this on my own infrastructure? Yes. The solver these Actors call is a self-hosted Docker container, sold on a flat fee with no per-request pricing. See xhr.dev.

A record came back with no price. Grainger shows no public web price for it. That is usually a discontinued line — check isDiscontinued — or a quote-only item. The rest of the record is still complete.

The price looks wrong compared to what I see on the site. Check unitOfMeasure and priceLabel first. Grainger prices some items per pack or per case rather than each, and contract pricing shown to a logged-in account differs from the public web price. This Actor returns the public web price.


Built by xhr.dev. Independent tool, not affiliated with Grainger or any bot-protection vendor. Scrape only what you are permitted to access, and check the site's terms before you run anything at scale.