# Shopify Store Scraper - Find Stores, Emails & Tech Stack (`mukeshrana90/shopify-store-scraper`) Actor

Find Shopify stores by keyword, product category, country, rating or launch date. Get verified emails, phone numbers, social profiles, real product counts, price range, store age, Shopify theme and installed apps. Export to CSV, JSON or Excel for lead generation.

- **URL**: https://apify.com/mukeshrana90/shopify-store-scraper.md
- **Developed by:** [Mukesh Kumar](https://apify.com/mukeshrana90) (community)
- **Categories:** Lead generation, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.001 / actor start

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Shopify Store Scraper - Find Stores, Emails & Tech Stack

Find Shopify stores by keyword, product category, country, rating or launch
date — then get the data that tells you whether each store is worth contacting:
**verified emails, phone numbers, social profiles, real product counts, price
range, store age, Shopify theme and installed apps.**

No store URLs needed. Give it a niche and it finds the stores for you. Export to
CSV, JSON, Excel or XML, or pull results straight from the API.

### What you get for every Shopify store

| Field group | What's included |
|---|---|
| **Contact details** | Email (with confidence rating), phone, Instagram, Facebook, TikTok, X/Twitter, YouTube, Pinterest, LinkedIn |
| **Store identity** | Store name, domain, `.myshopify.com` domain, Shopify shop ID, Shop.app profile |
| **Location** | Verified city, region and country from the store's own account, plus currency |
| **Catalog data** | Real product count, collection count, price min/median/max, in-stock rate, discount rate, top product types and vendors |
| **Store age** | Estimated launch date, months since last product published |
| **Tech stack** | Shopify theme name, custom vs off-the-shelf, and ~30 detectable apps (Klaviyo, Recharge, Gorgias, Yotpo, Judge.me, Attentive, Smile.io, PageFly and more) |
| **Quality signals** | Store rating, review count, lead score 0-100 |

That's ~70 fields per store. Most Shopify scrapers return a name, a URL and two
sample products.

### Who uses this

- **Ecommerce agencies** — find Shopify stores in your niche that have budget
  signals (custom theme, paid apps, big catalog) and a working email address.
- **Shopify app developers** — find stores already running a competing app, or
  stores that run everything *except* yours.
- **Suppliers and wholesalers** — find retailers selling in your category, sized
  by real catalog and price band.
- **Dropshipping and market research** — measure how many stores sell a product,
  where they're based, and what they charge.
- **Sales teams** — build a Shopify prospect list with verified contacts and
  skip the ones you already have in your CRM.

### How to find Shopify stores

#### By product keyword

```json
{
  "providers": ["shopapp"],
  "query": "organic coffee",
  "maxItems": 500
}
```

#### By niche, filtered to real prospects

Find established coffee stores with a working email that already pay for Klaviyo:

```json
{
  "providers": ["shopapp"],
  "query": "organic coffee",
  "minProducts": 25,
  "catalogPriceMin": 20,
  "requireEmail": true,
  "usesApps": ["Klaviyo"],
  "minLeadScore": 60
}
```

#### Newly launched Shopify stores

New stores are the highest-value segment for agencies and app developers — they
are still choosing their tools. `sortBy: "most_recent"` is how you reach them:

```json
{
  "providers": ["shopapp"],
  "query": "skincare",
  "sortBy": "most_recent",
  "maxStoreAgeMonths": 12,
  "requireEmail": true
}
```

#### Everything in a category

A single keyword runs out of stores fast. The `taxonomy` provider walks the
Shopify product category tree instead, so it keeps finding new merchants long
after keyword search is exhausted:

```json
{
  "providers": ["taxonomy"],
  "query": "coffee",
  "maxItems": 5000,
  "skipPreviouslyScraped": true
}
```

#### Enrich Shopify stores you already have

Already have a domain list? Skip discovery entirely and just enrich it — no
proxy needed, and it's the cheapest way to run this:

```json
{
  "providers": ["domains"],
  "domains": ["allbirds.com", "deathwishcoffee.com", "gymshark.com"]
}
```

#### Find stores in a specific country

```json
{
  "providers": ["shopapp"],
  "query": "candles",
  "storeCountry": "GB",
  "requireEmail": true
}
```

### Example output

```jsonc
{
  "name": "Death Wish Coffee",
  "domain": "www.deathwishcoffee.com",
  "myshopifyDomain": "deathwishcoffee.myshopify.com",

  "primaryEmail": "hey@deathwishcoffee.com",
  "primaryEmailConfidence": "medium",
  "socials": { "instagram": "...", "facebook": "...", "youtube": "..." },

  "productCount": 149,
  "priceMin": 5, "priceMedian": 32.75, "priceMax": 119.99,
  "inStockRate": 0.88,
  "estimatedStoreAgeMonths": 154,
  "topProductTypes": [{ "name": "Apparel", "count": 38 }, { "name": "Coffee", "count": 32 }],

  "verifiedAddress": { "city": "Round Lake", "region": "New York", "country": "US" },
  "currency": "USD",

  "theme": { "name": "Dawn", "isCustomTheme": true },
  "apps": [
    { "app": "Klaviyo", "category": "email" },
    { "app": "Gorgias", "category": "helpdesk" },
    { "app": "Smile.io", "category": "loyalty" }
  ],

  "rating": 4.7,
  "leadScore": 77
}
```

### Where the data comes from

Five discovery sources, switchable via `providers`:

| Provider | Finds stores by | Targeting | Volume |
|---|---|---|---|
| `shopapp` | Keyword and facet search on Shopify's own marketplace | Best | Medium |
| `taxonomy` | Walking the Shopify product category tree | Best | **Highest** |
| `domains` | Your own list — enrichment only | You choose | Your list |
| `iprange` | Reverse IP on Shopify's hosting range | None | Very high |
| `serp` | Search engines | Good | Medium |

Every store found is then read directly from its own **public storefront
endpoints** — the same data Shopify serves to any visitor. That's where the
catalog, pricing, theme and app data comes from, and it's why the output is
deeper than tools that only resell one search API.

### Filtering: only pay for leads you'd actually contact

Filters run against the enriched data, not just search results, and only stores
that pass count toward `maxItems`:

| Filter | Finds |
|---|---|
| `minProducts` / `maxProducts` | Stores sized to your offer |
| `maxStoreAgeMonths` | Newly launched Shopify stores |
| `catalogPriceMin` / `catalogPriceMax` | Stores at your price point |
| `requireEmail` / `requirePhone` | Only contactable leads |
| `usesApps` / `excludeApps` | Stores running (or missing) specific apps |
| `customThemeOnly` | Agency-built stores, i.e. stores with budget |
| `storeCountry` | Stores based in one country |
| `minRating` / `minLeadScore` | Only well-rated or high-scoring stores |

### Lead score

Every store gets a 0-100 score so a large export can be sorted by who to contact
first. It weighs contactability, catalog size, publishing activity, tech
sophistication and review reputation. It's a ranking heuristic, not a promise —
tune `minLeadScore` against leads you know converted.

### Not paying for the same lead twice

- `skipPreviouslyScraped` remembers delivered stores between runs, stored as a
  readable domain list you can inspect — not opaque internal state.
- `excludeDomains` seeds it from your CRM, so you never pay for a lead you own.
- `resetSeenStores` clears the history without losing the run.

### Keeping costs down

Residential proxy transfer is billed per gigabyte and storefront payloads are
large, so `storefrontProxyMode` defaults to `auto`: requests go direct and fall
back to a proxy only for storefronts that actually block us. That cuts the
running cost by roughly 70% versus proxying everything, with no loss of
coverage. The `domains` provider needs no proxy at all.

### Notes and limits

- **Store age is a lower bound on large catalogs.** It's derived from the oldest
  product sampled, and `/products.json` isn't ordered by creation date, so a
  partial sample can make a store look newer than it is. Each record carries
  `storeAgeConfidence` (`exact` or `lower-bound`), and when `maxStoreAgeMonths`
  is set, unverifiable ages are rejected rather than reported as new. Raise
  `catalogPages` to sample deeper, or set `allowUnverifiedAge` to keep them.
- `productCount` is capped by Shopify at 25001; records above that carry
  `productCountCapped: true` and should be read as "25,000+".
- App detection is fingerprint-based, so it finds apps that ship front-end
  assets. Purely back-office apps leave no trace on the storefront.
- Some storefronts sit behind bot protection and answer nothing. They're
  reported as `verificationStatus: "blocked"` rather than silently dropped, and
  excluded by default unless you set `keepUnverified`.

### FAQ

**Do I need store URLs?** No. Give it a keyword or category and it finds the
stores. If you *do* have URLs, the `domains` provider enriches them directly.

**Can it find stores in a specific country?** Yes — `storeCountry` matches
against each store's own verified account country, not a guess from the domain.

**Can it tell me which apps a Shopify store uses?** Yes, around 30 common apps
are detected from the storefront, including Klaviyo, Recharge, Gorgias, Yotpo,
Judge.me, Loox, Okendo, Attentive, Postscript, Smile.io, LoyaltyLion, PageFly
and Shogun.

**How many stores can I get?** Set `maxItems` up to 20,000 per run. Use the
`taxonomy` provider with `skipPreviouslyScraped` to keep pulling fresh stores
across runs.

**Are the emails verified?** Each email carries a confidence rating. `high`
means it came from a real `mailto:` link on the store's own domain. Use
`requireEmail` to drop stores without one.

**Is this allowed?** It reads publicly available storefront data — the same
pages and endpoints Shopify serves to any visitor. Use the output in line with
applicable marketing and privacy law (GDPR, CAN-SPAM) in your market.

# Actor input Schema

## `providers` (type: `array`):

Where to find stores. 'shopapp' searches Shopify's own marketplace (best targeting). 'taxonomy' walks the Shopify product category tree for far deeper coverage of a niche. 'domains' skips discovery and just enriches a list you supply. 'iprange' enumerates Shopify-hosted domains by reverse IP (high volume, no targeting). 'serp' finds stores via search engines.

## `query` (type: `string`):

What the store sells, e.g. 'organic coffee'. Optional when browsing a category.

## `categoryGid` (type: `string`):

Browse a specific Shopify product taxonomy category, e.g. 'gid://shopify/ProductCategory/251'. Combine with the 'taxonomy' provider to walk every subcategory beneath it.

## `sortBy` (type: `string`):

'Newest' surfaces recently launched stores — the highest-value segment for agencies and app developers, and something keyword-only tools cannot reach.

## `minStoreRating` (type: `string`):

Ask Shop.app for stores rated at least this many stars. Applied during discovery, so it does not waste your result budget.

## `priceMin` (type: `integer`):

Only find stores selling products above this price.

## `priceMax` (type: `integer`):

Only find stores selling products below this price.

## `inStock` (type: `boolean`):

Only find stores that currently have the matching products in stock.

## `onSale` (type: `boolean`):

Only find stores with the matching products currently discounted.

## `shipsTo` (type: `string`):

ISO country code, e.g. 'US'.

## `maxItems` (type: `integer`):

How many stores that PASS your filters to return. Stores rejected by filters do not count against this.

## `domains` (type: `array`):

Domains or myshopify handles to enrich, used with the 'domains' provider. Accepts 'acme.com', 'https://acme.com' or a bare 'acme' handle.

## `minProducts` (type: `integer`):

Real catalog size from the store's own account — not an estimate.

## `maxProducts` (type: `integer`):

Upper bound on catalog size, from the store's own account record. Useful for excluding large retailers.

## `minStoreAgeMonths` (type: `integer`):

Estimated from the oldest published product.

## `maxStoreAgeMonths` (type: `integer`):

Set to e.g. 6 to find newly launched stores.

## `allowUnverifiedAge` (type: `boolean`):

Store age comes from the oldest product we sampled. On a large catalog we may not have seen the oldest product, so the age is only a lower bound and the store could be older than it looks. By default those are rejected when a maximum age is set; enable this to keep them.

## `catalogPriceMin` (type: `integer`):

Filters on the store's actual median catalog price — a real positioning signal.

## `catalogPriceMax` (type: `integer`):

Only keep stores whose median catalog price is at or below this.

## `storeCountry` (type: `string`):

ISO code, matched against the store's own verified account country.

## `minRating` (type: `integer`):

Only keep stores with at least this average product rating (0-5).

## `requireEmail` (type: `boolean`):

Only keep stores we found at least one usable email address for.

## `requirePhone` (type: `boolean`):

Only keep stores we found at least one phone number for.

## `requireAddress` (type: `boolean`):

Only keep stores with a known business address.

## `usesApps` (type: `array`):

Only keep stores running these apps, e.g. Klaviyo, Recharge, Gorgias, Yotpo, Judge.me, Attentive, Loox, Okendo, Smile.io, PageFly.

## `usesAppsMatchAll` (type: `boolean`):

Require every app listed above rather than any one of them.

## `excludeApps` (type: `array`):

Useful for finding stores that do NOT yet run your competitor.

## `customThemeOnly` (type: `boolean`):

A store on a non-Shopify-store theme has usually paid an agency — a budget signal.

## `minLeadScore` (type: `integer`):

Only keep stores scoring at least this on the 0-100 lead score.

## `enrichCatalogue` (type: `boolean`):

Fetch /products.json for price range, product mix and store age.

## `enrichTechStack` (type: `boolean`):

Read the storefront homepage to identify the theme and installed apps.

## `enrichContactDetails` (type: `boolean`):

Crawl contact, about and policy pages for emails, phones and socials.

## `catalogPages` (type: `integer`):

250 products per page.

## `contactPages` (type: `integer`):

How many contact/about/policy pages to try per store when hunting for an email. Set to 0 to use only the homepage.

## `skipPreviouslyScraped` (type: `boolean`):

Remembers delivered stores so each run brings new ones. Stored as a readable domain list you can inspect and reuse.

## `excludeDomains` (type: `array`):

Seed from your CRM so you never pay for a lead you already own.

## `resetSeenStores` (type: `boolean`):

Clear the remembered store history before this run starts.

## `includeRejected` (type: `boolean`):

Includes rejected stores with the reasons, for tuning your filters.

## `disableBilling` (type: `boolean`):

Skip all pay-per-event charges for this run. Useful for testing the Actor without incurring lead charges.

## `keepUnverified` (type: `boolean`):

Some storefronts sit behind bot protection and answer nothing. Off by default they are dropped; on, they are returned with verificationStatus 'blocked' so you can decide.

## `enrichmentConcurrency` (type: `integer`):

How many stores to enrich in parallel. Raise for speed, lower to be gentler on storefronts.

## `shopAppDelayMs` (type: `integer`):

Shop.app rate-limits by IP. Raise this if you see throttling.

## `ipRangeMaxPages` (type: `integer`):

How many listing pages to read per Shopify IP address when using the ip-range provider.

## `serpMaxQueries` (type: `integer`):

How many distinct search-engine queries to run when using the serp provider.

## `proxyConfiguration` (type: `object`):

Residential proxies are strongly recommended — Shop.app rate-limits by IP.

## `storefrontProxyMode` (type: `string`):

Residential proxy traffic is billed per gigabyte and storefront payloads are large, so proxying every catalog fetch multiplies your cost for no benefit. 'Auto' goes direct and falls back to the proxy only for storefronts that actually block us. Shop.app search always uses the proxy regardless.

## Actor input object example

```json
{
  "providers": [
    "shopapp"
  ],
  "query": "organic coffee",
  "sortBy": "relevance",
  "inStock": false,
  "onSale": false,
  "maxItems": 50,
  "allowUnverifiedAge": false,
  "requireEmail": false,
  "requirePhone": false,
  "requireAddress": false,
  "usesAppsMatchAll": false,
  "customThemeOnly": false,
  "enrichCatalogue": true,
  "enrichTechStack": true,
  "enrichContactDetails": true,
  "catalogPages": 2,
  "contactPages": 3,
  "skipPreviouslyScraped": false,
  "resetSeenStores": false,
  "includeRejected": false,
  "disableBilling": false,
  "keepUnverified": false,
  "enrichmentConcurrency": 8,
  "shopAppDelayMs": 400,
  "ipRangeMaxPages": 10,
  "serpMaxQueries": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "storefrontProxyMode": "auto"
}
```

# Actor output Schema

## `leads` (type: `string`):

Every store that passed your filters, with emails, phones, socials, product counts, price range, store age, theme and installed apps.

## `leadsCsv` (type: `string`):

The same store leads as a CSV file, ready for a CRM or outreach tool.

## `runStats` (type: `string`):

How many stores were discovered, enriched, qualified and filtered out, why they were rejected, and how many events were billed. Use this to tune your filters.

## `datasetUrl` (type: `string`):

Base API URL of this run's dataset. Append your own ?format= and ?fields= parameters when calling it from code.

# 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 = {
    "query": "organic coffee"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mukeshrana90/shopify-store-scraper").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 = { "query": "organic coffee" }

# Run the Actor and wait for it to finish
run = client.actor("mukeshrana90/shopify-store-scraper").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 '{
  "query": "organic coffee"
}' |
apify call mukeshrana90/shopify-store-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mukeshrana90/shopify-store-scraper"
        }
    }
}

```

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/atorbqpp7OfivQPtQ/builds/p9toVehJDJpaXhqD0/openapi.json
