# Shopify Products Scraper & Variant CSV Export (`practical_ai_lab_jp/pal-shopify-product-json-csv-export`) Actor

Shopify products scraper for permitted public storefronts: export product and variant catalogs, SKUs, published barcodes, prices, availability and images to CSV/JSON. Use for catalog migration, SKU reconciliation, merchandising QA and price snapshots. No login or hidden inventory.

- **URL**: https://apify.com/practical_ai_lab_jp/pal-shopify-product-json-csv-export.md
- **Developed by:** [Practical AI Lab](https://apify.com/practical_ai_lab_jp) (community)
- **Categories:** E-commerce, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.85 / 1,000 product row exporteds

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

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Shopify Products Scraper & Variant CSV Export

### Try two free-to-configure Shopify scraper examples

- [**Variant SKU and published barcode export**](https://apify.com/practical_ai_lab_jp/pal-shopify-product-json-csv-export/examples/shopify-scraper-variant-sku-barcode-sample) — a bounded two-product store sample with CSV/JSON output, SKU, variant barcodes when published, public availability and missing-barcode checks.
- [**Shopify product price and catalog QA**](https://apify.com/practical_ai_lab_jp/pal-shopify-product-json-csv-export/examples/shopify-scraper-product-price-catalog-qa-sample) — a two-product catalog example for listed prices, vendors, images and variant counts.

**Buyer fit:** product feed migration, vendor catalog reconciliation, CSV reporting and scheduled public-listing change checks. This is a permitted public-feed scraper, **not** a full-store search index; it does not invent currency, barcodes, hidden products or private inventory quantities.

**Current usage pricing is shown in Apify's live Pricing tab.** Pricing is pay-per-exported-record and may vary by the customer's Apify plan. No login or paid proxy service is required for ordinary permitted public feeds.

### Shopify products scraper: CSV/JSON export for catalog operations

Export products and variants from **public Shopify products.json feeds** for stores you are permitted to inspect. The Actor downloads bounded, robots-permitted public pages and produces **product/variant IDs, SKUs, listed prices, visible availability flags, and clean CSV/JSON**—without store login, a proxy subscription, or any private inventory access.

**Start with a runnable example:** [Shopify product & variant CSV sample](https://apify.com/practical_ai_lab_jp/pal-shopify-product-json-csv-export/examples/export-shopify-product-variant-csv-sample). Review and change its store URL before a live run. Use the published Pricing tab to estimate charges.

**Common commercial workflows:** Shopify catalog migration, recurring product feed exports, merchandising QA, variant/SKU reconciliation, and price snapshot comparison using stable keys and `content_hash`. This Actor does not access orders, customers, inventory quantities, unpublished products, protected endpoints or storefronts that forbid the public feed.

**If you need another operation:** [Shopify cross-store product search](https://apify.com/practical_ai_lab_jp/pal-shopify-public-product-search) helps filter published feeds by SKU, brand and title; [Shopify product/variant change monitor](https://apify.com/practical_ai_lab_jp/pal-shopify-catalog-change-tracker) compares snapshots. These are separate Actors with separate pricing.

Export the public `/products.json` feed of a Shopify store (or one collection) as clean **product** and **variant** rows, in the dataset (JSON) and as spreadsheet-safe **CSV files**. It uses no login, no proxy and no paid API.

### New: merchandising insights and store diagnostics

Each successful export now writes a **CATALOG_INSIGHTS** JSON summary in the Key-value store and embeds it in **SUMMARY**. For each store you can see:

- Distinct exported product and variant counts (both and variant-only output modes supported).
- Share of available/unavailable variants, missing SKUs, and published discount signals.
- Most frequently exported vendors and min/max **listed** prices per store.
- Error/blocked-store explanations with safe follow-up steps, without bypassing robots.txt.

This enables recurring Shopify merchandising audits and catalog migration QA **without requiring an external spreadsheet just to count missing SKUs or summarize the catalog**. The report uses the same rows already exported, so it causes **no additional storefront HTTP requests** and no new billable events. Results reflect only the bounded public feed and selected output mode; if you select product rows only, SKU-level metrics are unavailable. Product currency and private inventory quantities are not inferred.

### What makes the output useful

- **Stable identifiers**: `product_key` (`<host>/<product_id>`) and `variant_key` (`<host>/<variant_id>`) stay the same between runs, so you can join exports from different days.
- **Change detection**: `content_hash` is a short hash of tracked fields (title, SKU, options, price, compare-at price, availability, weight). Compare it between runs to find changed rows.
- **Normalised prices**: decimal strings with two places, plus a computed `discount_percent` when a compare-at price is higher.
- **Variant-level detail**: option names and values, SKU, availability, weight, shipping/tax flags, variant image and a direct `?variant=` URL.
- **Spreadsheet-safe CSV**: text starting with `=`, `+`, `-` or `@` is prefixed with `'` so it cannot run as a formula. The file starts with a UTF-8 BOM for Excel.
- **Bounded and polite**: hard caps on products and pages, a delay before every request, and a stop if the feed repeats itself.

### Safety and compliance behaviour

- Only `https://` store URLs with public DNS names are accepted. IP addresses, local names, credentials in URLs and non-default ports are rejected, and resolved addresses must be public.
- `robots.txt` is read first and every feed URL is checked against it (wildcards, `$`, longest match, `Crawl-delay` up to 30 s). If `robots.txt` cannot be read (anything other than 200, 404 or 410), nothing is requested.
- Redirects are followed only to the same host and path. Responses are size-limited.
- Only the public feed is read. No customer, order or personal data is collected.

You are responsible for having the right to read and use the data of each store you export, and for following its terms.

### Sample input

```json
{
    "storeUrls": ["https://www.allbirds.com"],
    "exportMode": "variants",
    "maxProductsPerStore": 100,
    "maxPagesPerStore": 5,
    "pageSize": 250,
    "onlyAvailableVariants": false,
    "includeDescription": false,
    "requestDelayMs": 1000
}
```

The store above is only a format example, not a statement about its policies. A collection works too, for example `https://www.allbirds.com/collections/mens`. Use the store's final public domain.

| Field | Default | Meaning |
| --- | --- | --- |
| `storeUrls` | required | 1 to 5 store or collection URLs |
| `exportMode` | `variants` | `variants`, `products` or `both` |
| `maxProductsPerStore` | 100 | 1 to 10000 |
| `maxPagesPerStore` | 5 | 1 to 100 |
| `pageSize` | 250 | 1 to 250 |
| `onlyAvailableVariants` | false | Skip variants not reported as available |
| `includeDescription` | false | Add plain-text description to product rows |
| `descriptionMaxChars` | 500 | 0 to 5000 |
| `requestDelayMs` | 1000 | 500 to 10000 |

### Output

- **Dataset**: one record per variant and/or product (`record_type`). Views for variants and products are defined in the dataset schema.
- **Key-value store**: `variants.csv` and/or `products.csv` (matching the export mode) and `SUMMARY` (per-store status, pages, counts, stop reason, truncation flag).

Variant columns include: `variant_key`, `product_key`, `sku`, `variant_title`, `option1_name`/`option1_value` (up to 3), `price`, `compare_at_price`, `discount_percent`, `available`, `grams`, `requires_shipping`, `taxable`, `image_url`, `variant_url`, `content_hash`.

Product columns include: `product_key`, `title`, `vendor`, `product_type`, `tags`, `option_names`, `variant_count`, `available_variant_count`, `price_min`, `price_max`, `image_count`, `product_url`, `published_at`, `updated_at`, `content_hash`.

### Store status values

`complete` (reached the end of the feed or your caps), `partial` (stopped on an error after some data), `blocked` (unsafe host or robots.txt), `failed`. `truncated: true` means more products probably exist beyond your caps.

### Billing behaviour in the code

The code emits one event named `product-exported` per product from stores whose status is `complete`. Partial, blocked and failed stores are not counted. This repository does not configure a payable plan or a price; any pricing must be set up separately in the platform and checked against real run cost first.

### Limitations

- Many stores disable or restrict `/products.json`, use a different domain, or rate-limit; those runs end as blocked or failed with a reason.
- The public feed has no currency, no stock quantity, no cost data and no unpublished or draft products. Prices are the displayed storefront values and may differ by market or currency.
- The feed order and content can change while paging, so products may be missed or repeated in a very active catalog. Repeats are dropped by product ID.
- DNS is checked before requests, but the HTTP library resolves the name again when connecting, so DNS rebinding is reduced rather than fully eliminated.
- A single feed page can be up to 12 MB; larger responses are rejected.
- A 429 or 503 response is retried once, then the store ends as partial or failed.
- Market-size figures for similar tools (about 105 monthly active users, free users included) say nothing about paid demand. This Actor has not been validated with paying customers, and no sample results are published here.

### Barcode and GTIN coverage for product feed exports

Variant CSV and Dataset records now include the Shopify **barcode** field when the public products.json feed provides one. It may contain a UPC, EAN or another store-defined value; the Actor does not invent GTINs or validate barcodes. The automatic **CATALOG_INSIGHTS** report also counts variant rows with missing barcodes, helping catalog migration teams identify fields that need review.

Existing variants without a published barcode remain blank. This feature uses the **same requests and event charges** as the normal export.

# Actor input Schema

## `storeUrls` (type: `array`):

HTTPS storefront URLs, one per line (maximum 5). Use a store root such as https://www.allbirds.com, or a collection URL such as https://www.allbirds.com/collections/mens. Use the store's final public domain, because redirects to other hosts are not followed. Only export stores you are allowed to read.

## `exportMode` (type: `string`):

variants: one row per variant (best for inventory and price sheets). products: one row per product. both: both row types in the dataset, with separate CSV files.

## `maxProductsPerStore` (type: `integer`):

Hard cap on products exported for each store URL. For the first run, the input form pre-fills a tiny 5-product demo to keep costs low; raise the limit for real catalog exports.

## `maxPagesPerStore` (type: `integer`):

Hard cap on feed pages requested for each store URL. For the first run, the input form pre-fills a tiny 5-product demo to keep costs low; raise the limit for real catalog exports.

## `pageSize` (type: `integer`):

Products requested per feed page. Shopify accepts at most 250. For the first run, the input form pre-fills a tiny 5-product demo to keep costs low; raise the limit for real catalog exports.

## `onlyAvailableVariants` (type: `boolean`):

Skip variants that the store reports as not available. Products without any remaining variant are skipped.

## `includeDescription` (type: `boolean`):

Add plain-text product descriptions (HTML removed) to product rows.

## `descriptionMaxChars` (type: `integer`):

Truncate descriptions to this length when descriptions are included.

## `requestDelayMs` (type: `integer`):

Minimum wait before each request to a store. A longer Crawl-delay from robots.txt (up to 30 s) is used instead when present.

## Actor input object example

```json
{
  "storeUrls": [
    "https://www.allbirds.com"
  ],
  "exportMode": "variants",
  "maxProductsPerStore": 5,
  "maxPagesPerStore": 1,
  "pageSize": 5,
  "onlyAvailableVariants": false,
  "includeDescription": false,
  "descriptionMaxChars": 500,
  "requestDelayMs": 1000
}
```

# Actor output Schema

## `results` (type: `string`):

Structured records from this Actor run; downloadable as JSON or CSV.

# 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 = {
    "storeUrls": [
        "https://www.allbirds.com"
    ],
    "maxProductsPerStore": 5,
    "maxPagesPerStore": 1,
    "pageSize": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("practical_ai_lab_jp/pal-shopify-product-json-csv-export").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 = {
    "storeUrls": ["https://www.allbirds.com"],
    "maxProductsPerStore": 5,
    "maxPagesPerStore": 1,
    "pageSize": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("practical_ai_lab_jp/pal-shopify-product-json-csv-export").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 '{
  "storeUrls": [
    "https://www.allbirds.com"
  ],
  "maxProductsPerStore": 5,
  "maxPagesPerStore": 1,
  "pageSize": 5
}' |
apify call practical_ai_lab_jp/pal-shopify-product-json-csv-export --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,practical_ai_lab_jp/pal-shopify-product-json-csv-export"
        }
    }
}
```

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/h9ZTXehRuofGnRPDR/builds/dRk0RUsZDBd6XwAlE/openapi.json
