Pinterest Shopify Finder + Find Lead
Pricing
from $0.80 / 1,000 results
Pinterest Shopify Finder + Find Lead
Pricing
from $0.80 / 1,000 results
Rating
0.0
(0)
Developer
War tail
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Pinterest → Shopify Store Finder
Find Shopify stores that are active on Pinterest for a given set of keywords.
The actor searches Pinterest by keyword, follows the destination link of each pin (both organic and promoted pins appear in search results), de-duplicates the domains, and keeps only the ones that are detected as Shopify stores.
⚠️ Important about "ads" Pinterest has no public, keyword-searchable ads library (its Ads Transparency Center is EU-only and searchable by advertiser name, not freely by keyword). So this actor returns stores active on Pinterest for your keywords — a blend of organic and promoted pins — not a clean "paid ads only" list. That is the most reliable result obtainable from public data.
Input
| Field | Type | Default | Description |
|---|---|---|---|
keywords | string[] | — (required) | Search terms; each is searched independently. |
maxPinsPerKeyword | integer | 120 | Pins to scan per keyword before stopping. |
onlyShopify | boolean | true | Drop destination domains that are not Shopify (or below minConfidence) from the output. |
minConfidence | low|medium|high | low | Minimum Shopify confidence to keep when onlyShopify is on. See detection. |
checkProductsJson | boolean | false | Extra confirmation via <domain>/products.json (counts as a strong signal). |
extractContacts | boolean | true | For each kept store, scrape a contact email + phone (homepage first, then contact/policies/about pages only if needed). See contact extraction. |
maxConcurrency | integer | 10 | Parallel domain checks. |
keywordConcurrency | integer | 1 | Keywords searched on Pinterest in parallel. Keep low to avoid rate limiting. |
maxRequestRetries | integer | 3 | Retries with exponential backoff on 429/5xx before giving up on a request. |
proxyConfiguration | object | Apify RESIDENTIAL | Proxy settings. A fresh IP is used per request. |
Example:
{"keywords": ["home decor", "minimalist jewelry"],"maxPinsPerKeyword": 150,"onlyShopify": true,"minConfidence": "medium","checkProductsJson": true,"proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }}
Output
One dataset item per store:
{"domain": "examplestore.com","storeUrl": "https://examplestore.com/","finalDomain": "examplestore.com","isShopify": true,"confidence": "high","detectedVia": ["header:x-shopid", "body:cdn.shopify.com"],"matchedKeywords": ["home decor", "boho cushions"],"pinCount": 7,"email": "hello@examplestore.com","phone": "+15551234567","emails": ["hello@examplestore.com", "support@examplestore.com"],"phones": ["+15551234567"],"destinationLink": "https://examplestore.com/products/cushion","samplePinUrl": "https://www.pinterest.com/pin/123456789/","samplePinTitle": "Boho cushion cover","scrapedAt": "2026-06-19T10:00:00.000Z"}
pinCount and matchedKeywords are aggregated across all keywords: each store
appears once, with every keyword whose pins pointed to it and the total number of
pins seen. finalDomain is the host after following redirects. email/phone
are the best single contact picked from the de-duplicated emails/phones
lists (or null when none was found); they are present only when
extractContacts is on.
How Shopify detection works
Each domain is graded by the strength of the signals it emits:
- Strong signals →
highconfidence:- Headers/cookies —
x-shopid,x-shopify-stage,x-sorting-hat-shopid,x-shardid,powered-by: Shopify,_shopify/_secure_session_idcookies. - Redirect — the final host ends in
myshopify.com. /products.json(whencheckProductsJsonis on) — returns a validproductsarray.- Canonical body marker —
myshopify.comappears in the HTML.
- Headers/cookies —
- Weak signals (generic body markers —
cdn.shopify.com,/cdn/shop/,Shopify.theme,window.Shopify):- two or more →
mediumconfidence, - exactly one →
lowconfidence (e.g. a site merely embedding a Shopify buy-button).
- two or more →
Set minConfidence to trade recall for precision. Marketplaces and socials
(Amazon, Etsy, eBay, AliExpress, Instagram, etc.) are filtered out before detection.
Contact extraction
When extractContacts is on (default), each kept store is enriched with a
contact email and phone. Signals are graded by reliability:
mailto:/tel:links — taken first,- JSON-LD structured data (
email,telephone,contactPoint), - bare addresses in the page text, filtered hard against placeholders
(
you@example.com), platform domains (*.myshopify.com,sentry.io…), asset URLs (logo@2x.png) and junk phone runs (order numbers, years, SKUs).
It reuses the homepage HTML already downloaded during detection (no extra
request), then walks the usual Shopify contact pages (/pages/contact,
/policies/contact-information, /pages/about…) only if an email or phone
is still missing, stopping as soon as both are found. Contacts are scraped only
for stores that pass the onlyShopify / minConfidence filter, so no request
is spent on stores you're about to drop. Turn extractContacts off to run
leaner (fewer requests, lower proxy cost).
Pricing & cost control (pay-per-event)
The actor is billed per result: every store written to the dataset charges the
apify-default-dataset-item event. The Apify SDK stops pushing once the run's
maxTotalChargeUsd is reached, and the actor additionally stops detection and
contact scraping early once that result budget is exhausted — so it never
keeps spending proxy on stores it could no longer bill. Because Pinterest
requires residential proxies (the dominant cost), price the result event to
comfortably exceed your per-store cost, and consider extractContacts: false
or a lower minConfidence to control spend.
Deploy / publish on Apify
Option A — Apify CLI
npm install -g apify-cliapify logincd pinterest-shopify-finderapify push # builds and uploads the actor to your account
Then open the actor in the Apify Console → Publish tab → set category, pricing, and make it public.
Option B — GitHub import Push this folder to a GitHub repo, then in the Apify Console: Create new → Link Git repository → point it at the repo → Build.
Development
npm installnpm test # node:test unit tests (pure helpers, no network)
The pure logic — domain normalization, host filtering, response parsing,
Shopify signal grading, contact email/phone parsing, backoff timing, the
concurrency pool — is covered by unit tests in test/. The networked layers
(searchPinterestDomains, detectShopify, extractContacts) compose those
tested helpers.
Notes & limitations
- Residential proxies are strongly recommended. Pinterest blocks datacenter IPs.
A fresh proxy IP is used per request, and transient
429/5xxresponses are retried with exponential backoff. - Pinterest's internal search endpoint is unofficial and changes occasionally. If results
drop to zero, only
src/pinterest.jsneeds updating (response parsing / headers). - Detection is heuristic;
checkProductsJson: truereduces false positives. - Respect Pinterest's and target sites' terms of service and applicable law in your jurisdiction.
Project structure
pinterest-shopify-finder/├── .actor/│ ├── actor.json # actor metadata│ ├── input_schema.json # input form│ ├── dataset_schema.json # output table view│ └── output_schema.json # run output (dataset links)├── .github/│ └── workflows/ci.yml # syntax + JSON validation + unit tests on push/PR├── examples/│ └── input.example.json # sample input├── src/│ ├── main.js # orchestration + pay-per-event budget guard│ ├── pinterest.js # Pinterest keyword search → candidate domains│ ├── shopify.js # Shopify detection + confidence scoring│ ├── contacts.js # contact email / phone extraction│ ├── http.js # proxy rotation + retry/backoff│ └── pool.js # ordered concurrency pool├── test/ # node:test unit tests for the pure helpers├── Dockerfile├── .dockerignore├── .editorconfig├── .gitignore├── LICENSE├── package.json└── README.md
License
MIT — see ./LICENSE.