Shopify Products Scraper: Media, Currency & Related Products avatar

Shopify Products Scraper: Media, Currency & Related Products

Pricing

from $5.99 / 1,000 results

Go to Apify Store
Shopify Products Scraper: Media, Currency & Related Products

Shopify Products Scraper: Media, Currency & Related Products

Shopify Products Scraper — Scrape Shopify products with titles, prices, currency, images, videos, product media, variants, descriptions, and related products. Extract structured ecommerce data for product research, competitor analysis, catalog monitoring, and market insights.

Pricing

from $5.99 / 1,000 results

Rating

0.0

(0)

Developer

Scrapier

Scrapier

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

1

Monthly active users

a day ago

Last modified

Share

A Shopify products scraper that reads any storefront's public catalogue and returns typed JSON for every product — full pricing range, a verified currency, a typed media inventory (images, hosted video, external video and 3D models), and the store's own related-product graph. Every row is structured JSON, ready to pass straight into an LLM prompt, a spreadsheet, or a price-monitoring pipeline. No Shopify account, API key or app installation is required — just the storefront's own URL.

This Actor queries one or more Shopify storefronts — a full catalogue URL (https://rothys.com) or a single product URL (https://rothys.com/products/some-handle) — and returns a normalized JSON row per product, plus a child row per related product the store recommends. It reads the same public endpoints a browser already loads (/products.json, /products/<handle>.js, /products/<handle>.json), so no Shopify login, Storefront access token or Admin API app is needed for any store you point it at.

What sets this Actor apart from a plain product-list scrape:

  • 💱 Verified currency — reads the storefront's declared currency and cross-checks it against the currency each price is actually quoted in
  • Verified price units — recomputes every variant price from a second storefront source and flags disagreement instead of trusting one surface
  • 📊 Purchasable-only price rangepriceMin/priceMax are built only from variants a buyer can actually check out with
  • 🖼️ Typed media inventory — images, hosted video, external (YouTube/Vimeo) video and 3D models, normalized into one array with real dimensions and durations
  • 🔀 Related-product graph — the store's own product recommendations, collected as child rows with rank
  • 🌐 Optional proxy routing — direct by default, escalating through Apify Proxy groups only when a storefront starts refusing requests

The Actor returns three row types in one dataset — product rows, related-product rows, and store-status rows for stores it could not read — plus a nested media object inside every product row.

Result typeExtracted fieldsPrimary use case
Producttitle, handle, product_id, vendor, product_type, price, priceMin/priceMax, currency, storeCurrency, mediaCount, variantCount, tags, full_data (full raw record)Catalogue extraction, pricing feeds, product enrichment
Media asset (nested in media[])position, mediaType, url, alt, width, height, durationMs, previewImageUrl, variantIdsImage/video pipelines, PIM ingestion, visual search indexes
Related product (child row, type: "related")parentProductId, relatedProductId, relatedTitle, relatedPrice, relatedAvailable, rankMerchandising analysis, cross-sell mapping, recommendation audits
Store statusstoreStatus, catalogueComplete, catalogueStopReason, productsDiscoveredMonitoring which storefronts were reachable in a run

💱 Verified price, currency and typed media

Every product row carries pricing and media as verified facts, not raw pass-through. priceMin/priceMax are computed only from variants that are actually purchasable, so a sold-out placeholder variant can't drag the minimum to zero. priceUnitVerified is true only when an independently recomputed price matches the storefront's own published minor-unit price for the same variant. currencyMatchesStore is false — not hidden — when a storefront quotes a different currency than the one it declares. The media array carries every asset type Shopify supports, not just images:

{
"priceMin": "48.00",
"priceMax": "58.00",
"priceVaries": true,
"compareAtPriceMax": "68.00",
"discountPercent": 15,
"isOnSale": true,
"priceUnitVerified": true,
"priceUnitCheckedVariants": 4,
"currency": "USD",
"storeCurrency": "USD",
"currencyMatchesStore": true,
"mediaCount": 6,
"imageCount": 4,
"videoCount": 1,
"has3dModel": true,
"hasVideo": true
}

When includeRelatedProducts is on, the Actor calls the store's own recommendations endpoint for every product and emits each result as a separate type: "related" child row, ranked the way the storefront ranks them:

{
"type": "related",
"isChild": true,
"parentProductId": 7891234567,
"relatedProductId": 7891298765,
"relatedTitle": "The Original Flat",
"relatedPrice": "48.00",
"relatedAvailable": true,
"rank": 1
}

Shopify's own Storefront and Admin APIs return the same underlying product data, but only for a store you or the merchant control — this Actor reads the same public storefront JSON any visitor's browser loads, across any number of independent stores in one run.

FeatureShopify Storefront / Admin APIShopify Products Scraper
Access requirementA per-store Storefront access token or an installed Admin API app, generated by the store owner (shopify.dev, checked 2026‑08‑15)None — no token, app install, or store-owner permission needed
Cross-store queriesScoped to one store per tokenOne run accepts any number of independent storefront URLs
Media types in one callRequires querying the GraphQL media union per typeImages, hosted video, external video and 3D models normalized into one typed array automatically
Currency cross-checkNot provided by either APIstoreCurrency vs. quoted currency compared on every row
Price-unit verificationNot providedEvery variant price independently recomputed and cross-checked
Related-product graphRequires a separate productRecommendations query per product with the tokenCollected automatically per product, emitted as ranked child rows
Setup timeRegister a custom app, request scopes, store a secret token per shopEnter a URL and start the run

Use the official API when you own the store and need write access, orders, customers or inventory levels — none of which this Actor touches. Use this Actor when you need read-only catalogue, pricing, media and related-product data across stores you don't administer.

  1. Open the Actor on its Apify Store page and click Try for free (or Start, if you already have it).
  2. Add one or more storefront or product URLs to targets — for example ["https://rothys.com", "https://www.deathwishcoffee.com"].
  3. Set the query controls that matter for your job: maxProducts per store, mediaTypes to keep, maxRelatedPerProduct, and whether to verify currency and price units.
  4. Click Start to run the Actor.
  5. Download results as JSON, CSV, Excel or the other formats Apify's dataset export offers, or stream them via the API.

How to run multiple queries in one job

targets is a list — add every storefront or product URL you want in a single run, and the Actor works through them in order, applying maxProducts per store URL independently. For input files built for the original actor, startUrls and maxItems are kept as aliases: anything in startUrls is merged into targets, and maxItems is used only when maxProducts is left empty.

⬇️ Input

The Actor takes storefront or product URLs plus a set of toggles for how much detail to compute per product. Nothing is required — leaving everything blank runs the one demo store prefilled in targets.

ParameterRequiredTypeConstraintsDescription
targetsNoarrayeditor: stringListOne or more Shopify storefront URLs. A store URL (https://rothys.com) scrapes its catalogue; a single product URL (https://rothys.com/products/some-handle) scrapes just that product. Example: ["https://rothys.com", "https://www.deathwishcoffee.com"]. Default is one demo store.
startUrlsNoarrayeditor: stringListKept so input files written for the original actor keep working. Anything listed here is merged with targets. Leave empty if you filled in targets.
resolveStoreCurrencyNobooleandefault trueReads the storefront's own declared currency and reports it as storeCurrency, then compares it to the currency the prices were actually quoted in. Sets currencyMatchesStore so a storefront that quotes a different currency than it declares is visible instead of silent.
includePriceRangeNobooleandefault trueAdds priceMin, priceMax, priceVaries, compareAtPriceMax, compareAtPriceVaries, discountPercent and isOnSale. The range is built from purchasable variants only, so sold-out and placeholder variants cannot drag the minimum to zero.
verifyPriceUnitsNobooleandefault trueIndependently recomputes every variant price from a second storefront source and checks the two agree exactly, then reports the result as priceUnitVerified plus priceUnitCheckedVariants. Guards against a hundred-fold unit error that would otherwise look like ordinary data.
treatZeroCompareAtAsNullNobooleandefault trueShopify themes often store a strike-through price as an empty string or 0 when there is no sale. With this on, those become null so no row implies a 100% discount. Turn it off to receive the raw value exactly as published.
includeMediaInventoryNobooleandefault trueAdds mediaCount and a media array holding each asset's position, type, URL, alt text, dimensions and (for video) duration and preview frame, plus imageCount, videoCount, hasVideo, has3dModel and featuredImageUrl. URLs and metadata only — no files are ever fetched, stored or bundled.
mediaTypesNoarraydefault ["image", "video", "external_video", "model"]; editor: selectKeep only these asset types in the media array and in the per-type counts. Example: choose Image + Video to drop 3D models.
includeRelatedProductsNobooleandefault trueCollects the products the storefront itself recommends alongside each product and emits them as separate child rows (type = "related", isChild = true) carrying parentProductId, relatedProductId, relatedTitle, relatedPrice, relatedAvailable and rank. Also mirrored to a dedicated dataset for this run.
maxRelatedPerProductNointegerdefault 5; min 0; max 10How many related products to keep for each product, ranked as the storefront ranks them. Example: 5 on a 30-product run yields up to 150 related rows. Set 0 to turn the graph off.
maxProductsNointegermin 0; prefill 30Upper bound on how many products are returned for each store URL. Example: maxProducts=30 with 3 stores returns up to 90 product rows. Set 0 for the whole catalogue.
maxItemsNointegermin 0Kept so input files written for the original actor keep working. Used only when maxProducts is left empty.
proxyConfigurationNoobjecteditor: proxy; prefill {"useApifyProxy": false}Choose the connection route used to reach the storefronts. Leave off to connect directly. Large catalogues and stores that limit how fast they answer are more reliable with a proxy selected.

Example input:

{
"targets": [
"https://rothys.com",
"https://www.deathwishcoffee.com/products/nitro-cold-brew-coffee"
],
"maxProducts": 50,
"mediaTypes": ["image", "video"],
"includeRelatedProducts": true,
"maxRelatedPerProduct": 5,
"resolveStoreCurrency": true,
"verifyPriceUnits": true,
"proxyConfiguration": { "useApifyProxy": true }
}

Common pitfall: mixing a bare domain with www. and without it in the same run (rothys.com and www.rothys.com) is treated as two separate targets, since the Actor does not assume they resolve to the same catalogue — use whichever form the store actually redirects to, or list only one.

⬆️ Output

Results are typed, normalized JSON, one row per product plus a child row per related product and a status row for any store that couldn't be read. Export as JSON, CSV, Excel, XML, HTML table or RSS directly from the dataset, or pull it with the Apify API / apify-client.

⚠️ Every pushed row charges the row_result event — including a store_status row for a store that was locked, unreachable, or had no readable catalogue. If you're counting billable rows against expected product counts, filter on type == "product" (or != "store_status") rather than assuming only successful product rows are charged.

A few fields are deliberately null instead of 0 or false when the Actor genuinely could not determine the value — that's not the same as a confirmed negative:

  • mediaCount, imageCount, videoCount, hasVideo, has3dModel and mediaTypesPresent are null (not 0/false) when the per-product script record couldn't be read, or includeMediaInventory is off — 0 always means media was checked and confirmed absent.
  • relatedCount and relatedProductIds are null when the recommendations lookup itself failed to answer, versus an empty list when the store genuinely recommends nothing.
  • availableVariantCount is null when no surface published availability for any variant on that product.

Scraped results

[
{
"type": "product",
"isChild": false,
"scrapedAt": "2026-08-15T09:12:41Z",
"store_url": "https://rothys.com",
"product_url": "https://rothys.com/products/the-flat",
"product_id": 7891234567,
"title": "The Flat",
"vendor": "Rothy's",
"product_type": "Flats",
"price": "48.00",
"compare_at_price": "68.00",
"tags": ["bestseller", "washable"],
"total_found": 214,
"successful": 1,
"full_data": { "product": { "id": 7891234567, "...": "full raw Shopify product record" } },
"handle": "the-flat",
"storeDomain": "rothys.com",
"canonicalProductUrl": "https://rothys.com/products/the-flat",
"canonicalUrlSource": "storefront_product_record",
"productsDiscovered": 214,
"catalogueComplete": true,
"catalogueStopReason": "catalogue_exhausted",
"storeStatus": "ok",
"currency": "USD",
"currencySource": "product_price_currency",
"storeCurrency": "USD",
"currencyMatchesStore": true,
"priceUnitVerified": true,
"priceUnitCheckedVariants": 4,
"priceUnitMismatch": null,
"variantCount": 4,
"availableVariantCount": 3,
"productAvailable": true,
"publishedAt": "2023-02-01T10:00:00-05:00",
"updatedAt": "2026-08-10T14:22:03-04:00",
"createdAt": "2022-11-05T08:00:00-05:00",
"priceMin": "48.00",
"priceMax": "58.00",
"priceMinMinor": 4800,
"priceMaxMinor": 5800,
"priceVaries": true,
"compareAtPriceMax": "68.00",
"compareAtPriceMaxMinor": 6800,
"compareAtPriceVaries": false,
"discountPercent": 15,
"isOnSale": true,
"priceRangeVariants": 3,
"priceRangeExcludedUnavailable": 1,
"priceRangeUnknownAvailability": 0,
"priceOutlierFlagged": false,
"priceOutlierNote": null,
"mediaCount": 6,
"imageCount": 4,
"videoCount": 1,
"externalVideoCount": 0,
"model3dCount": 1,
"hasVideo": true,
"has3dModel": true,
"mediaTypesPresent": ["image", "model", "video"],
"media": [
{
"position": 1,
"mediaType": "image",
"url": "https://cdn.shopify.com/s/files/1/0000/0001/products/the-flat-1.jpg",
"alt": "The Flat in Black",
"width": 2048,
"height": 2048,
"aspectRatio": 1.0,
"durationMs": null,
"previewImageUrl": null,
"externalVideoHost": null,
"externalVideoId": null,
"variantIds": [40012345, 40012346]
}
],
"featuredImageUrl": "https://cdn.shopify.com/s/files/1/0000/0001/products/the-flat-1.jpg",
"relatedCount": 2,
"relatedProductIds": [7891298765, 7891298766]
},
{
"type": "related",
"isChild": true,
"scrapedAt": "2026-08-15T09:12:44Z",
"store_url": "https://rothys.com",
"parentProductId": 7891234567,
"parentHandle": "the-flat",
"relatedProductId": 7891298765,
"relatedHandle": "the-original-flat",
"relatedTitle": "The Original Flat",
"relatedPrice": "48.00",
"relatedPriceMinor": 4800,
"relatedCompareAtPrice": null,
"relatedAvailable": true,
"relatedVendor": "Rothy's",
"relatedProductType": "Flats",
"relatedProductUrl": "https://rothys.com/products/the-original-flat",
"rank": 1,
"product_id": 7891298765,
"handle": "the-original-flat",
"title": "The Original Flat",
"vendor": "Rothy's",
"product_type": "Flats",
"price": "48.00",
"product_url": "https://rothys.com/products/the-original-flat",
"currency": "USD",
"storeCurrency": "USD"
},
{
"type": "store_status",
"isChild": false,
"scrapedAt": "2026-08-15T09:13:02Z",
"store_url": "https://example-locked-store.myshopify.com",
"storeDomain": "example-locked-store.myshopify.com",
"storeStatus": "storefront_locked",
"catalogueComplete": false,
"catalogueStopReason": "listing_incomplete",
"productsDiscovered": null,
"total_found": null,
"storeCurrency": null
}
]
  • 📊 Ecommerce and pricing analysts: track priceMin, priceMax, discountPercent and isOnSale across a set of competitor storefronts to build a pricing feed that flags undercuts and sale starts.
  • 🤖 AI engineers and LLM developers: feed the structured product and media JSON directly into a retrieval-augmented catalogue agent or a shopping assistant, with full_data available for anything the normalized fields don't cover.
  • 🔬 Market researchers: compare relatedProductIds and relatedCount across stores to map how merchants cross-sell within a category.
  • 🛍️ Merchandising and product teams: audit mediaTypesPresent and variantCount across your own or a partner's catalogue to spot listings missing video or 3D assets.

How do you monitor Shopify pricing and catalogue changes over time?

Repeated-run monitoring turns a single snapshot into a change feed. Run the same targets on a schedule, keep each run's dataset, and diff the fields that actually move: priceMin/priceMax for price changes, isOnSale/discountPercent for markdown events, productAvailable/availableVariantCount for stock changes, and relatedProductIds/mediaCount for catalogue and merchandising changes. Because priceUnitVerified and currencyMatchesStore are recomputed every run, a store that silently starts publishing a mismatched currency or an unverifiable price shows up as a flag flip rather than a value you'd have to notice by eye.

A practical loop: schedule a run across your keyword set of storefronts, store each run's dataset by date, then compare the current priceMin/isOnSale/mediaCount against the previous run and alert on the deltas that matter to you — a new sale, a restock, or a currency mismatch appearing. This Actor has no built-in scheduler; use an Apify Schedule on the Actor to trigger runs automatically, and read consecutive datasets through the API to compute the diff.

Shopify Products Scraper: Media, Currency & Related Products works with any language or tool that can send an HTTP request.

REST API with Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_API_TOKEN>")
run = client.actor("<store-owner>/shopify-products-scraper-media-currency-related-products").call(
run_input={
"targets": ["https://rothys.com"],
"maxProducts": 50,
"includeRelatedProducts": True,
}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item.get("type") == "product":
print(item["title"], item.get("priceMin"), item.get("currency"))

Scheduled monitoring and delivery

The Actor has no built-in webhook delivery. Use an Apify Schedule to trigger repeated runs and an Apify webhook on the run-succeeded event to push each dataset to your own endpoint, or poll the dataset via the API on your own interval.

Scraping publicly listed product data is generally lawful — this Actor reads only what any storefront visitor's browser already loads, no login or account required. In hiQ Labs v. LinkedIn (9th U.S. Circuit Court of Appeals, 2019, reaffirmed on remand April 2022), the court held that accessing data on a public website without authentication does not violate the U.S. Computer Fraud and Abuse Act. Shopify product listings, prices and media are business and catalogue data, not personal data, so GDPR and CCPA — which govern personal data about individuals — do not attach to what this Actor returns; the relevant framework instead is each storefront's own terms of service and, in some jurisdictions, database-rights law protecting the compiled catalogue itself. Scraping for AI training and scraping for operational price monitoring carry different risk profiles under a store's terms. Consult your legal team for commercial use cases involving bulk data storage.

❓ Frequently asked questions

By default, up to 30 products per store URL (maxProducts prefill). Set maxProducts to 0 for the whole catalogue, or any other number as your own cap. Internally, catalogue paging reads 250 products per page for up to 200 pages before stopping regardless of maxProducts — a ceiling from the source code that only matters on catalogues larger than 50,000 products.

Does this Actor need a Shopify account, Storefront token or app installation?

No. It reads the same public JSON endpoints (/products.json, /products/<handle>.js, /products/<handle>.json) a storefront visitor's browser already loads. No login, Storefront access token or Admin API app is required for any store you point it at.

Yes — when includeRelatedProducts is on (default), it calls the store's own recommendations endpoint per product and emits each result as a type: "related" child row with parentProductId, relatedProductId, relatedTitle, relatedPrice, relatedAvailable and rank. If the recommendations lookup itself doesn't answer, relatedCount is null rather than 0, so "we don't know" is never reported as "there are none."

How does the Actor verify price and currency data?

verifyPriceUnits independently recomputes each variant's price from a second storefront surface and reports agreement as priceUnitVerified plus priceUnitCheckedVariants — this guards against the hundred-fold unit mix-up that occurs when a decimal price ("19.99") and a minor-unit price (1999) are confused. resolveStoreCurrency separately reads the store's declared currency and compares it to what each price is actually quoted in, exposing a mismatch through currencyMatchesStore instead of silently trusting one value.

What media types does the Actor extract?

Images, hosted video, external video (YouTube/Vimeo) and 3D models — controlled by the mediaTypes input, which defaults to all four. Each asset in the media array carries its position, type, absolute URL, alt text, dimensions, and for video, duration and a preview frame. No files are downloaded, stored or bundled — URLs and metadata only.

How does the Actor handle a locked or unreachable storefront?

A storefront redirecting to its own /password lock page is reported as storeStatus: "storefront_locked"; one that fails to answer at all is reported as store_unreachable. Either way, the Actor pushes a single store_status row for that store instead of guessing at product data, and that row still charges the row_result event.

How does the Actor handle rate limiting and anti-bot responses?

Requests are politely paced and retried up to 4 times per address with exponential backoff (capped at 8 seconds) plus jitter. On a soft refusal (HTTP 403, 406, 429, 451 or 503), the connection escalates from a direct connection to an Apify Proxy group for the rest of the run rather than retrying the same route indefinitely. A 404 or 410 is treated as permanently dead and is not retried.

How do I use this Actor to monitor a storefront's pricing and catalogue over time?

Schedule repeated runs over the same targets using an Apify Schedule, store each run's dataset, and diff priceMin, discountPercent, isOnSale, productAvailable and mediaCount against the previous run to catch price changes, sale starts, restocks or new media. See "How do you monitor Shopify pricing and catalogue changes over time?" above.

It is callable as an HTTP endpoint through the Apify API by any agent framework that can make a request, so an agent can trigger a run and receive structured product JSON to ground its answers — a scoped tool call rather than a general web search.

Can I use this Actor without managing proxies or Shopify credentials?

Yes. No Shopify login, token or app is needed for any store, and proxyConfiguration is entirely optional — the Actor connects directly by default and only routes through Apify Proxy groups when a storefront starts refusing requests, or when you turn a proxy on yourself for a large or slow catalogue.

💬 Your feedback

Found a bug or missing a field? Let us know through the Actor's Issues tab on Apify or the Scrapier support channel — reports of a wrong field or an edge case a storefront exposes help keep this Actor accurate.