Shopify Products Scraper — Descriptions, Specs & Tags
Pricing
from $1.55 / 1,000 results
Shopify Products Scraper — Descriptions, Specs & Tags
Shopify Products Scraper extracts detailed product data from Shopify stores. Collect product titles, prices, variants, SKUs, images, descriptions, and availability for eCommerce research, price tracking, and catalog automation.
Pricing
from $1.55 / 1,000 results
Rating
0.0
(0)
Developer
Scrapio
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Shopify Products Scraper — Descriptions, Specs & Tags
Most Shopify product scrapers hand you body_html exactly as the merchant typed it and tags as one comma-joined string. This Shopify product scraper decomposes both: plain-text descriptions, bullet lists, headings and Label: value spec pairs pulled out of the description, plus a real key → values tag attribute map built from the shop's own tag taxonomy — four incompatible tag conventions handled in the same run. Every row comes back as typed JSON, ready to filter, pivot or push into a database without a parsing step. This guide covers every field it returns and how catalogue, PIM and research teams actually deploy it.
🧭 What does this Shopify Products Scraper do?
It reads one or more Shopify storefronts' public product catalogues and returns one row per product, decomposing three things a raw catalogue export leaves as unstructured text: the merchant's description, the tag taxonomy, and the fields (product_type, vendor) that Shopify's own admin makes optional. No Shopify account, API key, or store login is required — it reads the same storefront catalogue data a browser sees.
- Extracts core product identity and pricing:
title,vendor,product_type,price,compare_at_price,handle - Turns
body_htmlintodescriptionText,descriptionWordCount,descriptionBullets,descriptionHeadings,descriptionLinks,descriptionImageUrls - Pulls
Label: valuelines out of the description intodescriptionSpecs - Parses the shop's tags into a
tagAttributeskey → values map plus aplainTagslist, across four tag-naming conventions - Filters the catalogue on description content, description length, tag keys, tag attribute values, vendor and product type
- Flags data-quality gaps explicitly:
missingDescription,missingProductType,missingVendor,isUntagged - Reports whether each store's catalogue was read in full:
total_found,catalogueComplete
⚡ Features & Capabilities
Three things set this apart from a plain catalogue export: description decomposition, tag taxonomy parsing, and content-aware filtering — all built from data the storefront already publishes, with no extra page loaded per product beyond the product record itself.
Core features
- Description decomposition —
descriptionText(clean plain text, HTML entities decoded, markup stripped),descriptionWordCount,descriptionBullets[],descriptionHeadings[],descriptionLinks[{text, url}],descriptionImageUrls[], plus structure flagshasBulletList,hasHeading,hasTable,hasEmbeddedImage,hasOutboundLink - Spec-pair extraction —
descriptionSpecs[{label, value}]pulled from lines likeMaterial: 100% organic cotton. A candidate line only qualifies as a label if it is 5 words or fewer, under 40 characters, contains a letter, and doesn't end in punctuation or look like a URL — so ordinary sentences aren't misread as specs - Tag taxonomy parsing —
tagAttributes(key → list of values),tagAttributeKeys[],plainTags[],tagFormatsSeen[]. Handleskey:value,key:sub:value,namespace::key => valueandkey_valuein the same run under the defaultautosetting. The underscore convention only fires on a lowercase key of 3+ characters followed by exactly one underscore, specifically so merchant grouping codes likeYGroup_*orFULL_PRICEaren't misread as attributes - Content and taxonomy filters —
descriptionKeywords,excludeDescriptionKeywords,minDescriptionWords,requireTagKeys,tagAttributeFilters,vendors,productTypes, all combining with AND; every kept row lists which filters it matched inmatchedFilters - Data-quality flags on every row —
missingDescription,missingProductType,missingVendor,isUntagged, so gaps in the catalogue are visible instead of silently returned as empty strings
💡 This Shopify Products Scraper within the Scrapio data stack
This actor covers Shopify products — descriptions, specs and tag attributes. For merchant-level shipping and currency data on a Shopify store, use Shopify Merchant Scraper. For related-product and price-comparison data on a Shopify storefront, use Shopify Scraper. For a store's contact details — emails, phone numbers and social links — use Shopify Store Scraper. All three sit alongside this one in the Scrapio Shopify lineup and can be run against the same store list.
Why do developers and data teams scrape Shopify product catalogues?
🏢 Ecommerce, PIM and merchandising teams
Feed a list of competitor or supplier storefronts into shopDomains and get back descriptionSpecs as real {label, value} pairs instead of re-typing them out of marketing copy, and tagAttributes as a structured taxonomy instead of a comma string to regex against. productTypes and tagAttributeFilters narrow a run to one category before it ever reaches your PIM import script. missingProductType and isUntagged flag which SKUs need manual categorisation before a migration, rather than surfacing as silent blanks after import.
📊 AI training data and RAG indexing
descriptionText is markup-free, entity-decoded plain text — the highest-information field for embedding into a RAG index of product knowledge, and descriptionSpecs gives structured attribute facts a model can cite directly rather than re-extract from prose. For training data, tagAttributes and descriptionSpecs are the most consistently shaped fields across a run: same key structure whether the source tag was Type:CORE TRUNK or storefront:category:Womens Shoes. Both feed a Q&A or attribute-extraction dataset without a normalisation pass.
📱 Competitive and market intelligence
Run the same shopDomains list on a schedule and track price, compare_at_price and tagAttributes over time to see when a competitor discounts, relaunches under a new season tag, or adds a merchandising attribute you don't yet use. descriptionWordCount and descriptionSpecCount turn "their copy looks more detailed" into a number you can chart across a catalogue.
🔬 Research and academic use
Public product-description and tag-taxonomy data across many storefronts supports research into ecommerce merchandising conventions, description-quality variation, or category-taxonomy practices — scoped to what each storefront already publishes publicly, with no account or login involved.
🎥 Product and SaaS development
descriptionSpecs and tagAttributes are structured enough to sit behind a catalogue-enrichment API, a competitor-monitoring dashboard, or a directory product without a scraping-and-parsing layer of your own. full_data carries the complete raw product record alongside the parsed fields, so a field this actor doesn't parse out separately is still available.
🍚 Input Parameters
Read directly from the Actor's input schema. Nothing is required — leave everything but a store list on its default.
| Parameter | Type | Description | Example Value |
|---|---|---|---|
shopDomains | array | One or more Shopify storefronts. Accepts a full URL (https://rothys.com), a bare hostname (rothys.com) or a myshopify domain (example.myshopify.com). Every store is processed in turn. Takes priority over startUrls when both are filled. | ["https://www.tentree.com", "https://rothys.com"] |
startUrls | array | The original store-URL field, kept so an existing input file keeps working unchanged. Used only when shopDomains is empty. | ["https://rothys.com"] |
parseDescription | boolean | Turn the merchant's description into plain text plus structure fields. Off skips description parsing entirely, and the description filters below then have nothing to work on. Default true. | true |
descriptionOutput | enum | text (plain text only) · structure (text + bullets, headings, links, images) · full (text + structure + spec pairs). Default "full". | "full" |
extractSpecPairs | boolean | Pull Label: value lines out of the description into descriptionSpecs. Only applies when descriptionOutput is "full". Default true. | true |
specLabelSeparators | array | Characters that separate a spec label from its value. A colon binds directly to the label (Fit: Oversized); a dash or pipe must stand alone between spaces. Default `[":", "-", " | "]`. |
keepDescriptionHtml | boolean | Add a descriptionHtml column holding the description exactly as published, alongside the parsed fields. Adds several kilobytes per row. Default false. | false |
parseTagAttributes | boolean | Split the shop's tags into a key/value attribute map (tagAttributes) and an unstructured list (plainTags). Off returns tags as a plain array only. Default true. | true |
tagFormat | enum | auto (all four conventions, recommended) · pair (key:value only) · three (key:sub:value only) · arrow (namespace::key => value only) · underscore (key_value only) · none (no parsing). Default "auto". | "auto" |
mergeTagKeyCase | boolean | Merge tag keys that differ only by case (Category: and category:) into one lower-case key whose value list is the union of both. Default false. | false |
emitPlainTags | boolean | Return the plainTags list of tags with no key/value structure. plainTagCount is returned either way, so attributeTagCount + plainTagCount always equals tagCount. Default true. | true |
descriptionKeywords | array | Keep only products whose description contains at least one of these phrases (case-insensitive). Empty means no keyword filter. | ["organic cotton"] |
excludeDescriptionKeywords | array | Drop products whose description contains any of these phrases (case-insensitive). | ["gift card"] |
minDescriptionWords | integer | Keep only products whose description reaches this many words; minimum 0. Products with no description are dropped when this is above 0. Default 0. | 20 |
requireTagKeys | array | Keep only products whose tag taxonomy contains every one of these keys (case-insensitive). | ["storefront:category"] |
tagAttributeFilters | array | Keep only products whose tag taxonomy holds a specific value, written as key=value. Several entries combine with AND. Case-insensitive. | ["Type=CORE TRUNK"] |
vendors | array | Keep only products from these vendors (case-insensitive exact match). | ["Rothy's"] |
productTypes | array | Keep only products with one of these product types (case-insensitive exact match). Many shops leave product_type blank and put the category in a tag instead — use the tag filters above for those. | ["Sneakers"] |
maxProductsPerShop | integer | How many products to read per store; 0 means the whole catalogue; minimum 0. Input-form default (prefill) is 50. If the field is omitted entirely — for example when the input is built programmatically without it — the Actor falls back to 100. | 50 |
maxItems | integer | The original per-store limit, kept for input-file compatibility. Used only when maxProductsPerShop is not set. Minimum 0. | 50 |
proxyConfiguration | object | Which proxies to use. Default is no proxy. If a store does not answer on the default route, the run automatically escalates to Apify Proxy and, if that also fails, to a residential proxy group. | { "useApifyProxy": false } |
{"shopDomains": ["https://www.tentree.com", "https://rothys.com"],"parseDescription": true,"descriptionOutput": "full","extractSpecPairs": true,"specLabelSeparators": [":", "-", "|"],"keepDescriptionHtml": false,"parseTagAttributes": true,"tagFormat": "auto","mergeTagKeyCase": false,"emitPlainTags": true,"descriptionKeywords": ["organic cotton"],"minDescriptionWords": 20,"requireTagKeys": ["storefront:category"],"tagAttributeFilters": ["Type=CORE TRUNK"],"maxProductsPerShop": 50,"proxyConfiguration": { "useApifyProxy": false }}
Supported URL types and input formats
- Full URL:
https://rothys.comorhttps://www.tentree.com— scheme and anywww.prefix are both accepted and normalised - Bare hostname:
rothys.com— a scheme is added automatically - Myshopify domain:
example.myshopify.com— read directly, no redirect resolution needed - Duplicate stores in the list are read once. A store the Actor cannot reach — password-gated, unavailable, or answering with something that isn't a catalogue payload — is skipped with a logged reason and produces no rows for that store, never a row claiming zero products
📦 Output Format
Typed JSON, one row per product, pushed to the dataset the moment that product is ready — no waiting for the whole store to finish. Field presence depends on input: description fields only appear when parseDescription is on, spec pairs only when descriptionOutput is "full" and extractSpecPairs is on, descriptionHtml only when keepDescriptionHtml is on. Download the dataset as JSON, CSV, Excel, XML or RSS from the Apify Console or API — standard for every Apify dataset.
Output for a product row
{"store_url": "https://chubbiesshorts.com","product_url": "https://chubbiesshorts.com/products/the-domingos-7-inch-lined-classic-swim-trunk","product_id": 7856421273694,"handle": "the-domingos-7-inch-lined-classic-swim-trunk","title": "The Domingos 7\" (Lined Classic Swim Trunk)","vendor": "Chubbies","product_type": null,"price": "69.50","compare_at_price": null,"tags": ["Type:CORE TRUNK", "Fabric:4-WAY STRETCH", "Season:SS26", "ALLSWIM"],"tagCount": 4,"tagAttributes": {"Type": ["CORE TRUNK"],"Fabric": ["4-WAY STRETCH"],"Season": ["SS26"]},"tagAttributeKeys": ["Fabric", "Season", "Type"],"attributeTagCount": 3,"plainTags": ["ALLSWIM"],"plainTagCount": 1,"tagFormatsSeen": ["key:value"],"tagAttributeCoverage": 1.0,"descriptionText": "Lined Classic Swim Trunk\nOur original swim trunk...\nInseam: 7 inches","descriptionWordCount": 63,"descriptionHtmlLength": 812,"descriptionIsEmpty": false,"descriptionBullets": ["Quick-dry 4-way stretch fabric", "Boxer-brief mesh liner"],"descriptionHeadings": [],"descriptionLinks": [],"descriptionImageUrls": [],"hasBulletList": true,"hasHeading": false,"hasTable": false,"hasEmbeddedImage": false,"hasOutboundLink": false,"descriptionSpecs": [{ "label": "Inseam", "value": "7 inches" }],"descriptionSpecCount": 1,"missingDescription": false,"missingProductType": true,"missingVendor": false,"isUntagged": false,"matchedFilters": [],"total_found": 250,"catalogueComplete": false,"scrapedAt": "2026-08-08T00:00:00Z","full_data": { "product": { "…": "the complete raw product record from the storefront" } }}
| Field | Description |
|---|---|
store_url, product_url, product_id, handle | Product identity |
title, vendor, product_type | Core product fields; product_type/vendor are null when the shop leaves them blank, never an empty string |
price, compare_at_price | First variant's prices, exactly as the shop publishes them |
tags | Real array of tags, normalised whether the source returned an array or a comma-joined string |
tagCount, attributeTagCount, plainTagCount | attributeTagCount + plainTagCount always equals tagCount; all three are null when the shop publishes no tag field at all, or parseTagAttributes is off |
tagAttributes | Key → list-of-values map parsed from the tags |
tagAttributeKeys | Sorted attribute keys present on this product |
plainTags | Tags carrying no key/value structure (present when emitPlainTags is on) |
tagFormatsSeen | Which of the four conventions this product's tags used |
tagAttributeCoverage | Fraction of this store's read catalogue publishing any tag attribute — the same value on every row from that store |
descriptionText, descriptionWordCount, descriptionHtmlLength | Clean plain text and its size; text/word-count are null when there is no description |
descriptionIsEmpty | true when the description renders no visible text |
descriptionBullets, descriptionHeadings | List items and headings, in document order (present when descriptionOutput is "structure" or "full") |
descriptionLinks, descriptionImageUrls | Outbound links ({text, url}) and <img> sources inside the description |
hasBulletList, hasHeading, hasTable, hasEmbeddedImage, hasOutboundLink | Structure flags |
descriptionSpecs, descriptionSpecCount | {label, value} pairs found in the description (present when descriptionOutput is "full" and extractSpecPairs is on) |
descriptionHtml | The description exactly as published (present only when keepDescriptionHtml is on) |
missingDescription, missingProductType, missingVendor, isUntagged | Catalogue gap flags |
matchedFilters | Which active filters this row satisfied |
total_found | How many products the catalogue walk had read by the time it stopped for this store — the true catalogue size only when catalogueComplete is true |
catalogueComplete | Whether the walk stopped because the catalogue ran out (true), or because of a product cap, page ceiling, or a read error (false) |
scrapedAt | ISO-8601 UTC timestamp of when this row was built |
full_data | The complete raw product record — {"product": {...}} — so a field not parsed out separately is still available |
Schema stability and export options
Field names in this output are the Actor's own — descriptionSpecs, tagAttributes and the rest are constructed by this Actor's parser, not passed through raw from Shopify, so they stay stable even if a store's theme changes how it renders body_html. full_data carries Shopify's raw product JSON verbatim underneath, as a fallback for anything the parser doesn't surface as its own column. Export the dataset as JSON, CSV, Excel (XLSX), XML or RSS directly from the Apify Console, or pull it programmatically with the Apify API or apify_client.
💡 Shopify Products Scraper Strategy Guide
🎯 Strategy 1: Real-time enrichment pipeline
Trigger a run when a new SKU or store enters your pipeline — a webhook, a queue message, or a manual API call. Run this Actor against that one shopDomains entry with a small maxProductsPerShop, then read descriptionSpecs, tagAttributes and the gap flags (missingProductType, isUntagged) off the dataset and write them back into your PIM record. Because the Actor pushes each row as soon as that product is parsed, a single-store enrichment run can be consumed before the whole run finishes.
🎯 Strategy 2: Scheduled monitoring and alerting
Run the same shopDomains list on an Apify Schedule — daily or weekly. Compare each new run's price, compare_at_price and tagAttributes against the previous run on product_url as the join key, and alert on the deltas that matter: a price drop, a new Season tag, or a product that went from tagged to isUntagged. scrapedAt timestamps each comparison point.
🎯 Strategy 3: Bulk dataset build
List every store you need in shopDomains (or split across parallel runs, one store list per run) and set maxProductsPerShop to 0 for the whole catalogue. Within a store, product detail pages are fetched with bounded concurrency rather than all at once, and the catalogue walk stops at a fixed page ceiling per store (see Honest limitations below) — plan very large single-store pulls with that in mind. Aggregate the resulting dataset to CSV or load it into a database for research or training-set construction.
Strategy comparison at a glance
| Strategy | Best for | Run pattern | Output format |
|---|---|---|---|
| Real-time enrichment | PIM / catalogue-record enrichment | On-demand trigger, single store, small maxProductsPerShop | Dataset row consumed via API as it's pushed |
| Scheduled monitoring | Price and tag-taxonomy change tracking | Apify Schedule, recurring run over a fixed store list | Dataset per run, diffed externally on product_url |
| Bulk dataset build | Research or training-set construction | Many shopDomains, or parallel runs across stores | Dataset exported to CSV/JSON |
🌴 Related Shopify Scrapers & Tools
| Scraper | What it extracts |
|---|---|
| Shopify Merchant Scraper | A Shopify store's currency and shipping configuration |
| Shopify Scraper | Related products and price comparison across a Shopify storefront |
| Shopify Store Scraper | A store's contact details — emails, phone numbers and social links |
| Amazon Product Details & Variant ASINs Scraper | Amazon product detail pages and their variant ASINs |
| eBay Product Listing Scraper | eBay listing item specifics, condition and photos |
| Amazon Price Tracker | Amazon price history, deals, coupons and lowest recorded price |
How to integrate this Shopify Products Scraper with your stack
This Actor works with any language or tool that can make an HTTP request through the Apify API, or the official apify_client / apify-client SDKs.
Python
import csvfrom apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run_input = {"shopDomains": ["https://www.tentree.com", "https://rothys.com"],"maxProductsPerShop": 100,"descriptionOutput": "full",}run = client.actor("YOUR_USERNAME/shopify-products-scraper-descriptions-specs-tags").call(run_input=run_input)rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())with open("shopify_products.csv", "w", newline="", encoding="utf-8") as f:writer = csv.writer(f)writer.writerow(["store_url", "title", "vendor", "price", "descriptionSpecCount", "tagAttributeKeys"])for row in rows:writer.writerow([row.get("store_url"),row.get("title"),row.get("vendor"),row.get("price"),row.get("descriptionSpecCount"),",".join(row.get("tagAttributeKeys") or []),])print(f"Wrote {len(rows)} products to shopify_products.csv")
Node.js
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const runInput = {shopDomains: ['https://www.tentree.com', 'https://rothys.com'],maxProductsPerShop: 100,descriptionOutput: 'full',};const run = await client.actor('YOUR_USERNAME/shopify-products-scraper-descriptions-specs-tags').call(runInput);const { items } = await client.dataset(run.defaultDatasetId).listItems();for (const row of items) {console.log(row.title, row.price, row.tagAttributeKeys);}
Async and scheduled pipelines
For a large multi-store pull, start the run and poll it rather than waiting synchronously — client.actor(...).call() blocks until the run finishes, or use start() and check client.run(runId).get() on your own interval. For recurring pulls, use an Apify Schedule against a fixed input, and read the resulting dataset from your own job once the run completes; the Apify platform can also fire a webhook on run completion for a fire-and-forget large job.
🎯 Who needs a Shopify product, description and tag scraper?
🏢 Ecommerce, PIM and merchandising teams
Import descriptionSpecs directly as structured attribute pairs into a product feed or PIM instead of re-keying them from marketing copy, and use missingProductType/isUntagged to flag the SKUs a migration needs to touch by hand.
📊 AI/ML and RAG engineering teams
Index descriptionText for retrieval and cite descriptionSpecs as structured facts in generated answers, without a separate HTML-cleaning step in the ingestion pipeline.
📱 Competitive intelligence and market research analysts
Track price, compare_at_price and tagAttributes for a fixed store list over time to see discounting patterns and merchandising-attribute changes as they happen, not after the fact.
🔬 Researchers
Public description and tag-taxonomy data across many storefronts, scoped to what each store already publishes — suited to academic or market research into ecommerce merchandising practices.
🎥 SaaS and product builders
tagAttributes and descriptionSpecs are structured enough to sit behind a catalogue-enrichment API or competitor-monitoring product without building a scraper first.
Is it legal to scrape Shopify product catalogues?
Scraping publicly accessible data is generally lawful in the United States — courts have held that accessing data a website makes available without a login is not a computer-fraud violation (hiQ Labs, Inc. v. LinkedIn Corp., 9th Cir. 2019, on remand 2022). That precedent concerned a social platform, but its core holding — that scraping public pages is not "unauthorized access" — is widely cited for public-data scraping generally.
Two separate questions apply here. Scraping public data is generally legal; violating a site's Terms of Service is a matter of civil contract risk between the scraper and the site operator, not a criminal one. This Actor returns product and business-record data — titles, descriptions, prices, vendors and merchant-assigned tags — not personal data about individuals, so data-protection frameworks like GDPR and CCPA, which govern personal data, do not attach to its output. Product catalogue content itself may carry copyright or database-rights protection in some jurisdictions, which is a separate question from scraping method.
This Shopify Products Scraper returns only publicly accessible data. What you do with that data is your responsibility — consult legal counsel for commercial applications, especially where a store's own Terms of Service restrict automated access.
❓ Frequently asked questions
Does this Shopify Products Scraper work without a Shopify account?
Yes. It reads the same public storefront catalogue data a browser sees — no Shopify account, API key, or store login is required or accepted as input.
How does it handle Shopify's anti-scraping measures?
By default it connects directly. When a store returns a retriable status (429, 403, 503, and similar), the connection automatically escalates from a direct connection to Apify Proxy, and then to a residential proxy group if that still fails — each attempt with a growing backoff delay and some randomised jitter, up to four attempts per request. A status that can never succeed (400, 401, 404, 410) is treated as terminal and not retried.
Can I run it at scale without getting blocked?
The retry-and-escalation behaviour above applies per request. There is no published concurrency or rate-limit figure beyond what's documented here: product detail pages within one store are fetched with bounded concurrency rather than all at once, and each store's catalogue walk is capped at a fixed number of pages (see Honest limitations).
How fresh is the data this Actor returns?
Live. Every run re-fetches the storefront's current catalogue JSON — nothing is cached or served from a previous run.
Which fields work best for AI training and RAG indexing?
For RAG, descriptionText is the high-information field — clean plain text with markup and entities stripped. For training data, tagAttributes and descriptionSpecs are the most consistently shaped fields across a run, since both are built to the same {key: [values]} and {label, value} structure regardless of which tag convention or spec-separator the merchant used. All fields return as typed primitives — strings, numbers, booleans, arrays, or null — needing no further normalisation.
Does this Actor collect personal data?
No. It returns product and catalogue fields — titles, descriptions, prices, vendors and tags — that Shopify stores publish as part of their public product listings, not data about individuals.
Does it work with Claude, ChatGPT, and other AI agent tools?
It's callable as an HTTP endpoint by any agent framework through the Apify API — a run returns typed JSON that needs no parsing before it's placed in a model's context window.
Can I get one row per variant or SKU?
Not with this Actor. It returns one row per product, with price and compare_at_price taken from the first variant only.
What is a "spec pair"?
A Label: value line the merchant wrote inside the description — for example Material: 100% organic cotton, Compatibility: Handheld Bidet Sprayer. Returned as {label, value} objects in descriptionSpecs so you can pivot on them without re-parsing prose.
Two shops use Category: and category: — will they collide?
Only if you ask them to. By default they stay two separate keys exactly as written. Set mergeTagKeyCase to true to merge them into one lower-case key whose value list is the union of both.
ℹ️ Honest limitations
- ⚠️ Each store's catalogue walk is capped. The Actor reads up to 40 pages of 250 products per page — 10,000 products per store — before it stops regardless of how much catalogue remains. When that happens,
catalogueCompleteisfalseandtotal_foundreflects only what was read, not the store's true product count. - One row per product, not per variant.
priceandcompare_at_pricecome from the first variant only; there is no per-SKU output. - Not every shop publishes a tag taxonomy. A store with no structured tags returns
tagAttributes: {}andtagAttributeCoverage: 0.0— never an invented key. - Description structure is theme-dependent. Whether a product's description carries bullet lists, headings, or spec-parseable lines depends entirely on how that store's theme and merchant wrote it; the per-row structure flags tell you which is which rather than assuming a shape.
maxProductsPerShop's effective default can differ from its input-form default. The form pre-fills50; if the field is left out of the input object entirely, the Actor falls back to100.- Nothing is invented. A value the shop doesn't publish comes back as
null, never as0or an empty string standing in for a real measurement, andattributeTagCount + plainTagCountequalstagCounton every row so a parsing gap is visible rather than hidden. - A store that isn't reachable produces no rows and a logged reason — password-gated, unavailable, or an unreadable payload — never a row claiming the store has zero products.
- No AI, no model calls. Every field is produced by deterministic text and string parsing.
Disclaimer
This Shopify Products Scraper extracts only publicly available data from Shopify storefronts. This tool is intended for lawful use cases only. Users are responsible for complying with each store's terms of service and applicable data protection laws in their jurisdiction.