Quick answer
If a Shopify lead-gen tool reports a product_count of 10 for a store with 4,000 SKUs, that's not a bug in the store — it's the tool reading exactly what it asked for and nothing more. Shopify's public /products.json endpoint returns however many products your limit query parameter requests, and most scrapers default that limit low and call it a catalog size. The Shopify Store Leads Scraper is honest about this — product_count reflects your maxProductsSample input, not the store's true inventory — and returns 11 typed fields per probed domain at $0.0025 per row plus a $0.005 run-start charge, $2.505 for 1,000 confirmed leads.
The product_count number that isn't what it looks like 🔢
Here's the mechanism, plainly: /products.json is called with ?limit=<maxProductsSample> — your input's product-sample cap, 10 by default, up to 250. Whatever the endpoint returns for that single request is what gets counted. product_count = len(products). There's no follow-up call, no cursor pagination through the rest of the catalog.
That's a deliberate trade-off, not an oversight — pulling a store's entire catalog to report an accurate SKU count would turn a lead-qualification probe into a full inventory scrape, at 10-100x the request volume, for a number most lead-gen use cases don't actually need precisely. But if you read product_count: 10 as "this store has 10 products," you'll misjudge every large catalog in your list. Read it as "we sampled up to maxProductsSample products and this is how many we got back" — which is exactly what sample_product_titles is for: a taste of the catalog, not a census. Raise maxProductsSample toward its 250 ceiling if you need a closer read; that's what the input exists to control.
The three-tier fallback that catches the stores fighting back 🛡️
Not every Shopify store answers /products.json politely. Gymshark, for one, returns a 403 on it. If detection stopped there, every store that blocks the products endpoint would register as "not Shopify" — which is wrong and would silently shrink your lead list.
So detection runs three tiers, in order, and stops at the first hit:
Tier one hits /products.json directly. A clean JSON response with a products array confirms Shopify and yields product_count, sample_product_titles, and a currency code pulled from the response text.
Tier two, when tier one comes back empty or blocked, inspects response headers from the homepage fetch — x-shopify-stage, x-sorting-hat-shopid, x-sorting-hat-section, or a shopify token in Server/X-Powered-By. These headers are Shopify's own infrastructure fingerprint; a store can hide its product feed but it can't easily hide its hosting stack.
Tier three scans the homepage HTML itself for cdn.shopify.com references, the Shopify.theme JS global, or a *.myshopify.com subdomain string — the last-resort signal for stores locked down at both prior tiers.
When tier one is blocked and tiers two or three confirm Shopify instead, product_count and sample_product_titles come back null — there was no products feed to sample from. That's documented behavior, not a silent gap: is_shopify: true with product_count: null tells you exactly what happened.
Email extraction skips the obvious false positives 📧
email prefers mailto: links on the homepage first. When there's no mailto link, it falls back to a plain-text regex scan of the raw HTML — and that fallback deliberately excludes addresses ending in sentry.io, cloudflare.com, shopify.com, and example.com. Those are the addresses that show up constantly in embedded JS error-tracking snippets and boilerplate templates, and without the exclusion list a naive scan would hand you a dataset full of noreply@sentry.io instead of real contact addresses.
What we handle for you 🛡️
-
Three independent detection layers, so stores that block
/products.jsonare still correctly identified via headers or HTML markers rather than falsely marked as non-Shopify. - We rotate residential proxies via Apify Proxy — fresh session and exit IP per request so a domain-list sweep doesn't read as one crawler hammering hundreds of hosts.
-
We retry with exponential backoff on
408 / 429 / 5xx, up to 5 attempts per domain, honoringRetry-After. -
We rotate browser fingerprints across Chrome, Firefox, and Safari TLS impersonation via
curl-cffifor the homepage fetch. - You pay only for confirmed results that land. Zero rows, zero charge, beyond the small run-start warm-up fee.
Full output schema 📦
Eleven fields per probed domain, extra="forbid":
| Field | Type | Notes |
|---|---|---|
domain |
string | Normalized domain probed (e.g. allbirds.com) |
is_shopify |
bool | True when any tier confirmed Shopify |
detection_method |
string \ | null |
myshopify_domain |
string \ | null |
product_count |
int \ | null |
sample_product_titles |
list[string] | Up to maxProductsSample titles |
currency |
string \ | null |
email |
string \ | null |
social_links |
list[string] | Homepage-only — inner-page-only footers aren't scanned |
homepage_title |
string \ | null |
scraped_at |
string | ISO-8601 UTC |
Who this is for
Shopify app vendor prospecting — feed a domain list from a niche directory, get back every confirmed Shopify store with email and a product sample, ready for CRM import.
DTC agency outreach — answer "does this prospect actually run Shopify?" programmatically instead of opening 200 tabs.
Competitive intelligence — track which new entrants in a category have spun up Shopify storefronts, detection_method telling you how confident that confirmation is.
Market sizing — count confirmed Shopify stores in a vertical before pitching an app or a replatform, using only_shopify: false when you also want the negative results for context.
Frequently asked questions
Is product_count the store's total number of products?
No — it's capped by your maxProductsSample input (default 10, max 250). Treat it as a sample size, not a catalog census.
Why is product_count null for some confirmed Shopify stores?
Because /products.json was blocked or unreachable and detection fell through to headers or HTML markers instead — those tiers confirm the platform but can't retrieve product data.
Does this need residential proxies?
Recommended, and prefilled by default — many Shopify stores rate-limit repeated requests from datacenter IP ranges.
What does 2,000 confirmed leads cost?
$5.005 — 2,000 × $0.0025, plus the $0.005 run-start charge.
Will I get social links for every confirmed store?
Only when they're linked from the homepage itself. Some brands only link social accounts from footers on inner pages, which this Actor doesn't crawl.
Try it
Live on the Apify Store: Shopify Store Leads Scraper.
Give it a domain list, get back confirmed Shopify stores with product samples, contact email, and social links. Pay-per-event, no subscription.
Built by Devil Scrapes — we build scrapers for the targets that fight back.
Top comments (0)