Chewy Product Catalog: Prices, Autoship and Pet Food SKUs
Pricing
Pay per usage
Chewy Product Catalog: Prices, Autoship and Pet Food SKUs
Daily prices, Autoship prices, ratings and stock of Chewy dog food and treats, plus every product id from the public sitemaps, also outside these categories. No browser, no proxy.
Pricing
Pay per usage
Rating
0.0
(0)
Developer
Sergey Faraday
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
0
Monthly active users
15 minutes ago
Last modified
Categories
Share
Tracks Chewy dog food and treats: price, Autoship price, list price, rating, review count, stock and the seller's key benefits, every day if you like. It reads the same catalog data the category pages use, with plain HTTP requests, no browser and no proxy.
A second mode lists the product ids in the public sitemaps, including products outside the dog food categories.
How to use
- Keep the default mode
pricesand setmaxProductsto try it on a small sample. - Give each run a
snapshotId(for example the date) and compare snapshots to follow prices. Use onesnapshotIdper mode: both modes write the same row key for a product. - To find new products, compare
parentIdbetween full prices passes. Use modesitemapwhen you need ids outside these categories.
Input
| Field | Default | Meaning |
|---|---|---|
mode | prices | prices: prices and ratings per product family. sitemap: all product ids and names, no prices |
scope | dog_food_and_treats | dog_food, dog_food_and_treats; sitemap mode also has all_dog, pet_food_all_species, all |
maxProducts | all | stop after this many rows. This is a cut, not a pause, see below |
startFrom | from the start | prices mode: listing position to start from, from nextCursor in the MANIFEST of a run that was cut |
startGroup | first | prices mode: the category startFrom belongs to (dog_food, dog_treats). Categories before it are not read, categories after it start at their beginning |
delayMs | 1000 | pause between catalog pages |
includeCanada, maxSitemapFiles | sitemap mode only | |
snapshotId | <actor>.<runId> | your id for this snapshot; re-running with the same id does not create duplicates |
{ "mode": "prices", "scope": "dog_food_and_treats", "maxProducts": 72, "snapshotId": "2026-11-02" }
Output
One product row per product family:
{"recordType": "product","platform": "chewy","sourceRecordId": "3969182","canonicalUrl": "https://www.chewy.com/pedigree-complete-nutrition-grilled/dp/3969182","time": { "observedAt": "2026-11-02T08:00:00.000Z", "collectedAt": "2026-11-02T08:00:00.120Z" },"metrics": {"price": { "value": 25.97, "unit": "USD", "sourceName": "advertisedPrice", "priceCondition": "advertised" },"priceAutoship": { "value": null, "nullReason": "not_provided_by_source" },"priceList": { "value": 26.98, "unit": "USD" },"rating": { "value": 4.6356, "unit": "stars_1_5", "scope": "family" },"reviewCount": { "value": 11248, "unit": "count", "scope": "family" },"pricePerKg": {"value": 1.43,"unit": "USD/kg","history": "derived","basis": { "priceMetric": "price", "priceSource": "advertisedPrice", "priceCondition": "advertised", "sku": "3969182", "currency": "USD", "netWeightG": 18143.69, "packCount": 1, "weightSource": "title_and_ppu" }}},"data": {"name": "Pedigree Complete Nutrition Grilled Steak & Vegetable Flavor Dog Kibble Adult Dry Dog Food, 40-lb bag","brand": "Pedigree","parentId": "141433","productType": "food","productTypeSource": "title_head_noun","inStock": true,"foodFormat": "dry","netWeightG": 18143.69,"weightResolvedBy": "title_and_ppu","sellerStatements": [{ "text": "Enriched with omega-6 fatty acid...", "status": "seller_statement" }]},"sampling": { "group": "dog_food", "position": 7, "sort": "byNewest" }}
The MANIFEST record in the key-value store reports the status of each category (ok, partial, blocked, failed), the total Chewy reports, pages read, any truncation, and rows by product type and by how the weight was resolved.
How to read the data
One row is one variant of a family.
- Prices mode returns one variant per product family, the one shown in the newest-first listing. For about a quarter of families this is another size than the default listing shows, for example a 1-lb bag instead of a 22-lb bag.
- The history key is
sourceRecordId, notparentId:sourceRecordIdis the variant,parentIdthe family. When another variant takes over the row of a family, that is a change of pack, not a change of price. Group byparentId, compare price per kg rather than price across families.hasMoreVariantssays when other sizes exist. ratingandreviewCountbelong to the whole family (scope: "family",data.ratingScope): a variant flaggedisNewcan show thousands of reviews.isNewis a source flag on this variant, not a launch date: a new size of an old product carries it too, and which date stands behind it is not established. To find a new product, compareparentIdbetween full passes: two passes 3.5 hours apart differed by 6 of 14,482 families and lost none. Do not compare sitemap snapshots: the sitemap changes between fetches, and 2,107 of about 19,000 ids differed within 34 minutes.- Newest-first is a sort, not a sales or popularity rank, and
positionis a place in that sort. Chewy's default order changes between requests, so a pass on it skips products.
A limit is a cut, not a pause.
maxProductsends the run.nextFromin theMANIFESTis the position after the last row taken,visibleSizethe positions taken by this run, andnextCursorwhere to go on;nextCursoris null when the category was read to its end.- To continue, start a run with the same
snapshotIdandstartFromset tonextCursor(andstartGroupwhen the cut fell indog_treats). The rows of both runs together are the rows of one longer run, with the same positions, as long as the listing did not move in between. A repeat with a larger limit and nostartFromgets you there too, it just reads the first rows again. - A category that the limit cut before its first row is
failed, withnextCursor0. A run started withstartFromreports only what it read: the earlier positions are in the run before it. - Sitemap mode has no cursor:
applied.cutAtFilenames the file of the cut,matchedwhat the files offered,matchedDeliveredwhat reached the dataset. A repeat with a largermaxProductsunder the samesnapshotIdcompletes the snapshot.maxSitemapFilesalso endspartialwith reasonmaxSitemapFiles, so a repeat with a larger limit under the samesnapshotIdcompletes the snapshot. An empty or short page far below the total leavesnextCursorat that position, and the error says the listing ended early.
Restarts.
- A restart reads the page or file in flight once more, without using the limit, so the MANIFEST says what one clean run says. Out of time it cannot, then
nextFrommoves by the rows read back and a note says what the counts leave out.
Time.
observedAtis taken after the response of the page or file arrived,collectedAtwhen this actor built the row, so "what did we know at time T" usescollectedAt.runStartedAtis the platform start, the same on every row of a run.- Prices mode: the catalog JSON has no date of its own, so
sourceUpdatedAtis null andsourceUpdatedAtBasissays why. Sitemap mode:sourceUpdatedAtis the entry'slastmod, else theLast-Modifiedof the sitemap file, and the basis says which.availableAtstays null with a reason in both modes.
Records and the total.
- A category is
partialwith reasonlisting_shiftedwhen more than 0.5% of the total Chewy reports was served twice within the category or never read. - Some products are listed in both categories, such as veterinary treats and bundles of food and treats. They get one row, under
dog_food, sorecordsofdog_treatsstays below the total by that many: 25 of 9,089 positions on a full pass.applied.listedInEarlierCategorycounts them andapplied.listedInEarlierCategoryIdsnames them, a note says so, and the status staysok. - The total is not exact.
dog_treatsserved 9,090 positions under a total of 9,088, so reading stops at a short page and not at the total. The total also moves: on 2026-11-02 it read 9,087, 9,092 and 9,088 within about 8 hours.applied.totalMinandtotalMaxreport it within a pass. A product removed above the reading position during a pass can skip one row without a trace.
Product type.
productTypecomes from the words the title ends on, so line names like "Daily Dish" or "Farm to Bowl" do not count, with the catalog category filling gaps. Values:food,treat,supplement,medication,kit,accessory,non_food,other.kitis a toy, gear or supplement sold together with food or treats, out of the food and treat filters; the parts are inproductTypeEvidence.productTypeSourcesays how the type was found andproductTypeDoubtfulwhy it may be wrong. Exclude rows withproductTypeDoubtfulwhen you need a clean set: 332 of 14,476 rows of a full pass carry it. A title that names gear or a kit beside the food (with Puzzle Toy, Treats & Pouch, Cake Mold Kit) keeps its type and carriesproductTypeDoubtful: 40 rows of a full pass. A title with a food part and a treat part carries it too: 5 rows.speciesisotherwith evidence for food of other animals (Snake Food,Fish Food). The dog food category holds such rows.species: nullmeans neither the title nor the URL names a species. When only the URL names it,speciesEvidencestarts withslug:, as on compounded drugs whose title names no animal.- Format and life stage come from the product name, at about 95% precision on a manual sample. Check the
*Evidencefields when it matters. Format, life stage, dog size and claims come from the product name; the URL is read for them only when a row has no name. A claim quotes the name word for word.
Weight and price per kg.
netWeightGis set only when one reading of the title survives.2.12-oz bag, 3 countcan mean three bags or three pieces in a bag, so it stays null withweightAmbiguous: true,weightAmbiguityReasonandunitWeightG, until the source's price per unit settles it.weightResolvedBysays what the weight rests on:title,title_and_ppu,ppu,single_serve_container.- The source's price per unit is a witness, not the truth: Chewy computes it from size times count, even where that gives a 53.8 kg bag for $18.99.
impliedQuantityGshows what it implies and never becomesnetWeightG. pricePerKgis null with a reason:ambiguous(a weight is there, but not one),no_weight(no weight in the title),sold_by_volume. Itsbasisnames the price and the weight behind the value. It uses the advertised price, not the Autoship price.ambiguousalso covers a title that names gear or a kit, because the price can pay for more than the food;metrics.pricePerKg.notesays so. 24 rows of a full pass.bundlePartCountis the number of part numbers behind the listing: 2 or 3 on a bundle of different products (isBundle: true), 1 on the same product in multiples. A title that names several products or weights gets no net weight.inStockis the flag of this variant for the US without a delivery ZIP (data.stockScope); which warehouses it covers is unknown.- Key benefits are the seller's own words, stored as seller statements, not verified claims.
When the source changes.
- Rows were read but what defines a row is missing or unreadable: id, name, link, price. On every row at any count, or on 10% or more of the rows from 10 rows on, per page, per sitemap file or for the source: error
SCHEMA_CHANGED, statuspartialwhen rows hold data,failedwhen none does. - In prices mode the pass stops at that page,
nextCursorstands after the delivered rows and the error names the position to read again. Such a row hasstatus: fetch_errorwithstatusReason, and a repeat under the samesnapshotIdreplaces it. - A value that is there and not readable (From $12.99, a range, another currency mark, a bare number) is null with
unparseableandraw. A price of 0 is null withzero_not_confirmed_as_real. A rating outside 1 to 5 is null withunparseable. - Fields every row sends (parent id, brand, Autoship and strike price, rating and count, price per unit, key benefits, stock and the other flags) gone or of another shape on that share of rows also give
SCHEMA_CHANGEDandpartial, but the pass goes on. A response withoutrecordSetTotalcounts too. applied.rowsInspected,rowsNotReadable,unreadableFieldsandshapeChangedFieldsshow what was seen.
FAQ
Where are the ingredients and guaranteed analysis? Not in this data. The companion Unified Scraper Adapter actor has them in module chewy_details, all variants in chewy_products and reviews in chewy_reviews.
What happens if Chewy blocks access? The run stops and reports blocked. No bypass is attempted. In sitemap mode the first blocked file stops the reading, applied.filesNotAttempted names the rest. A page answered with HTTP 200 instead of a sitemap counts as a block, and 429 reads as blocked in both modes.
Why is a zero rating shown as null? A count of 0 means nobody rated the product, which is not a rating of 0: rating is null with reason no_ratings and reviewCount stays 0.
Is this allowed? Check Chewy's terms for your use case. Collecting content for third parties may need Chewy's written consent.
Cost
A full prices pass reads about 400 catalog pages in about 20 minutes for about $0.11 of platform usage at 256 MB of memory, which is enough for this mode. A full sitemap pass reads about 270,000 entries in under a minute for about $0.08 of platform usage.