# Pinterest Shopping & Product Scraper with Prices (`apt_marble/pinterest-product-scraper`) Actor

Turn Pinterest shop-the-look pins into a product feed. One row per product with its real price, currency, sale price, discount, stock status, merchant, buy link and image — plus the pin it came from. Search by shopping keyword or paste specific pins. No Pinterest account needed.

- **URL**: https://apify.com/apt\_marble/pinterest-product-scraper.md
- **Developed by:** [Hamza](https://apify.com/apt_marble) (community)
- **Categories:** Social media, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 product scrapeds

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

## Pinterest Shopping & Product Scraper with Prices

Pinterest's best shopping content hides the interesting part one level down: a single "shop the look" pin is really a basket of separate products, each with its own name, merchant, price and buy link. This actor opens those pins and gives you **one row per product** — price, currency, the "was" price and discount where there is one, whether the merchant marks it available, the outbound buy link, the image, and the pin it was found in. Point it at shopping keywords, at specific pins, or at both.

### What you can do with it

- Build a priced product feed for a niche — "arched cabinet", "linen sofa", "gold pendant light" — without touching a single merchant site.
- Watch what a competitor's shop-the-look pins are actually selling, and at what price.
- Pull discounted items only: every row carries the original price, the discount percentage and a plain `onSale` flag.
- Collect merchant-and-link pairs for affiliate, comparison or curation work, each already tied to the image that sells it.
- Compare price ranges across a category, a market or a moment in time by scheduling the same keywords weekly.
- Enrich pins you already track: paste their addresses and get the products inside them.

### What you get

One row per product. Abridged real example:

```json
{
  "type": "product",
  "id": "47850814786426317-872220652852770625",
  "title": "Ripple Rotating Wall Lamp | Yedwo Design",
  "productName": "Ripple Rotating Wall Lamp | Yedwo Design",
  "price": 199,
  "currency": "USD",
  "currencySymbol": "$",
  "priceDisplay": "US$199.00",
  "standardPrice": null,
  "percentOff": null,
  "onSale": false,
  "inStock": true,
  "merchant": "YEDWO",
  "link": "https://yedwo.com/products/ripple-rotating-wall-lamp-yedwo-design",
  "domain": "yedwo.com",
  "imageUrl": "https://i.pinimg.com/736x/cb/fb/92/cbfb924161c8f141bc7d05c925cd1a80.jpg",
  "productPinUrl": "https://www.pinterest.com/pin/872220652852770625/",
  "productIndex": 0,
  "isCollectionProduct": true,
  "collectionSize": 1,
  "parentPinId": "47850814786426317",
  "parentPinUrl": "https://www.pinterest.com/pin/47850814786426317/",
  "parentPinTitle": "Ripple Rotating Wall Lamp | Yedwo Design",
  "parentPinCreatedAt": "2026-01-07T22:29:57.000Z",
  "parentPinSaveCount": 8995,
  "parentPinnerUsername": "ashaarty",
  "parentCreatorUsername": "yedwo",
  "parentIsPromoted": false,
  "searchQuery": null,
  "scrapedAt": "2026-08-04T12:00:00.000Z"
}
```

### Input reference

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `queries` | list of text | `["arched cabinet"]` | Shopping keywords, worded the way a shopper would type them. A Pinterest search address works too — the words are taken from it. Up to 50 per run. |
| `pins` | list of text | `[]` | Addresses of specific pins you want the products from, or plain pin numbers. Up to 1000 per run. |
| `maxProducts` | number | `500` | How many product rows to collect before the run stops. Maximum 20000. |
| `maxPinsPerQuery` | number | `60` | How deep to go through each keyword's public results before moving to the next keyword. Maximum 500. |
| `onlyWithPrice` | true/false | `true` | Keep only products that publish a price. Turn it off to also get unpriced entries, which still carry name, merchant, link and image. |
| `onlyInStock` | true/false | `false` | Keep only products the merchant currently marks as available. Products whose availability is not published count as unknown and are excluded when this is on. |
| `includePromoted` | true/false | `false` | Pinterest mixes promoted pins into results. Off keeps things organic; on keeps them and flags them with `parentIsPromoted`. |
| `country` | select | `us` | The market to browse from. Shopping results and currencies differ by market, and the choice is kept the same for the whole run. Pinterest does not always honour it, so every row also records the market it was actually served for. |

### Output fields

| Field | Type | Description |
| --- | --- | --- |
| `id` | text | Stable row identifier: the pin it came from plus the product within it. |
| `title` | text | The product's own name as published. Empty when the entry has none — it is never given the pin's caption instead. |
| `productName` | text | The merchant-side product name, when published separately from the title. |
| `description` | text | The product description, where the merchant publishes one. |
| `price` | number | The current price as a number. |
| `currency` | text | Three-letter currency code, e.g. `USD`, `GBP`, `CAD`. |
| `currencySymbol` | text | The symbol shown alongside the price, e.g. `$`. |
| `priceDisplay` | text | The price exactly as Pinterest formats it, e.g. `US$2,698.00`. |
| `standardPrice` | number | The pre-discount price, when the item is reduced. |
| `standardPriceDisplay` | text | The pre-discount price as formatted text. |
| `percentOff` | number | Discount percentage, when one is published. |
| `percentOffLabel` | text | The discount as shown, e.g. `21% off`. |
| `onSale` | true/false | True when there is a real reduction — an equal "was" price does not count. |
| `hasPriceDrop` | true/false | Whether the merchant marks this as a recent price drop. |
| `inStock` | true/false | Availability as published. Empty when the merchant does not publish it. |
| `availabilityCode`, `conditionCode` | number | Availability and condition exactly as published, unaltered. |
| `merchant` | text | The shop behind the product. |
| `link` | text | The outbound buy link. |
| `domain` | text | The destination host, handy for grouping by retailer. |
| `imageUrl`, `images` | text / object | The product image, plus every size published for it. |
| `dominantColor` | text | The image's dominant colour, as a hex value. |
| `productId`, `offerId` | text | Pinterest's own identifiers for the product and its offer. |
| `productPinId`, `productPinUrl` | text | The pin for this individual product, where one exists. |
| `productIndex` | number | The product's position inside its pin. |
| `isCollectionProduct` | true/false | True when the row came from a multi-product pin. |
| `richType` | text | How Pinterest labels the entry — `product` for genuine products. |
| `isActive` | true/false | Whether the entry is still live in the pin. |
| `collectionSize`, `collectionTitle` | number / text | How many products the parent pin holds, and its heading if it has one. |
| `parentPinId`, `parentPinUrl` | text | The pin the product was found in. |
| `parentPinTitle`, `parentPinDescription`, `parentPinAltText` | text | The parent pin's caption, description, and an automatically generated image description where there is no caption. |
| `parentPinImageUrl` | text | The parent pin's own image. |
| `parentPinLink`, `parentPinDomain` | text | Where the parent pin itself points. |
| `parentPinCreatedAt` | date | When the parent pin was published. |
| `parentPinSaveCount`, `parentPinRepinCount` | number | How many times the parent pin has been saved and repinned. These are two different measures and are reported separately. |
| `parentPinnerUsername` | text | Who pinned it. |
| `parentCreatorUsername` | text | The account credited with the original, which is often not the same person. |
| `parentIsPromoted` | true/false | Whether the parent pin is a paid placement. |
| `source`, `searchQuery`, `inputTarget`, `parentPinPosition` | text / number | Where the row came from: a keyword or a pin you supplied, and the parent pin's rank in those results. |
| `country` | text | The market the run asked to browse from. Empty when the run could not be held to it. |
| `browsedFromCountry` | text | The market Pinterest actually served the results for, which is not always the one that was asked for. Empty when Pinterest does not say. |
| `hasFullDetails` | true/false | True when the product came from the fully opened pin. False when the pin could not be opened and the product was taken from what the results page already showed. |
| `scrapedAt` | date | When the row was collected. |

### Pricing

You pay **$5.00 per 1,000 products**, which is **half a cent per product row**. Nothing else is billed: there is no charge for keywords you search, for pins that turn out to hold no products, or for entries filtered out by your own switches.

Worked example: a run over four keywords that returns 400 products costs 400 × $0.005 = **$2.00**. Set `maxProducts` to cap the bill exactly — a run capped at 500 products can never cost more than $2.50.

### Limits & what this actor cannot do

- **This is a shop-the-look and collection product extractor, not a "price for any pin" tool.** Ordinary merchant catalog pins — the single-product pins that link straight to a shop — do not publish a price on Pinterest at all, so they return no rows. Multi-product pins, and single-product pins that carry a real offer, are the ones that pay off. Expect a minority of any keyword's results to hold products; the actor tells you exactly how many did.
- Not every entry inside a collection has a price. By default those are dropped; switch `onlyWithPrice` off to keep them.
- Prices, discounts, availability and save counts are a snapshot at the moment of collection and keep changing afterwards.
- Pinterest publishes a limited number of public results for any one search, so very large targets return what Pinterest makes available rather than an unlimited history.
- Results and currencies are personalised to the market they are served for, and the market you pick is not always the market Pinterest serves. Every row records both the market that was asked for and the one Pinterest actually served, so you always know which one produced your prices.
- A pin that cannot be opened is reported as such, never silently counted as a pin that holds no products. Where the results page had already shown some of that pin's products, those are kept and marked with `hasFullDetails` as false.
- Promoted pins appear inside Pinterest's own results. They are excluded by default and flagged when you keep them.
- Pins that have been deleted or made private are reported as unavailable rather than returned empty.
- Availability and condition are passed through exactly as published, without being reinterpreted.
- Shortened `pin.it` links cannot be used — open one in a browser and paste the full address it lands on.
- Speed depends on the size of the job and on how fast Pinterest answers; no fixed speed is promised.
- Pinterest's terms govern automated access. You are responsible for using the data lawfully and in line with the source site's terms, and for handling any personal data in line with applicable privacy law.

### FAQ

**Do I need a Pinterest account?**
No. Nothing here is behind a login, and no account is ever involved.

**Does it need my login or password?**
No. Never provide them — they are not an input and would not be used.

**Why did some of my keywords return nothing?**
Because those results contained no pins with product entries. Pinterest happily fills a result page with visually similar pins even for a term nobody shops with, and none of that filler carries a price. Wording the keyword the way a shopper would — a product noun, not a mood — makes a large difference. An empty keyword always means an empty keyword: if pins could not be opened instead, the run reports that separately rather than letting it look like nothing was there.

**Is the data complete?**
It is complete for what Pinterest publishes publicly. Every price, discount, stock marker and merchant name comes from the product entry itself; nothing is estimated, and a field the merchant did not publish arrives empty rather than as a zero. What it cannot give you is a price for pins where no price exists anywhere on Pinterest — those simply produce no rows.

**Can I schedule it?**
Yes. Save your keywords as a task and run it daily or weekly to track prices and discounts over time; `scrapedAt` on every row gives you the time series.

**Why do I sometimes get two rows with the same product?**
Because the same product can appear inside two different pins. Each row records the pin it came from, so you can keep both or group by `link` to collapse them.

# Actor input Schema

## `queries` (type: `array`):

What to look for on Pinterest, worded the way a shopper would type it — "arched cabinet", "linen sofa", "gold pendant light". Each term is searched and the pins that contain product entries are opened to collect the products inside them. You can also paste a Pinterest search address and the words are taken from it. Up to 50 terms per run.

## `pins` (type: `array`):

Addresses of individual Pinterest pins you already know you want the products from, for example https://www.pinterest.com/pin/165859198772351221/. Plain pin numbers work too. Shortened pin.it links cannot be used — open one in a browser and paste the full address it lands on. Up to 1000 pins per run.

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

How many product rows to collect before the run stops. One shop-the-look pin usually contains between one and twenty products.

## `maxPinsPerQuery` (type: `integer`):

How deep to go through each keyword's public results before moving on to the next keyword. Only the pins that actually contain product entries are opened, so a higher number widens the search without multiplying the work.

## `onlyWithPrice` (type: `boolean`):

Some entries inside a shop-the-look pin are published without a price. Leave this on to keep only the priced ones. Turn it off to also get the unpriced entries, which still carry the product name, merchant, link and image.

## `onlyInStock` (type: `boolean`):

Keep only products the merchant currently marks as available. Products whose availability is not published are treated as unknown and excluded when this is on.

## `includePromoted` (type: `boolean`):

Pinterest mixes its own promoted pins into results. Leave this off for organic results only. When on, they are kept and flagged with parentIsPromoted so you can split them out later.

## `country` (type: `string`):

Pinterest personalises shopping results by location, and prices are shown in that market's currency. Pick the market you care about and it is kept the same for the whole run. Pinterest does not always serve the market it is asked for, so every row records the one it actually served.

## Actor input object example

```json
{
  "queries": [
    "arched cabinet",
    "modern floor lamp"
  ],
  "pins": [],
  "maxProducts": 100,
  "maxPinsPerQuery": 60,
  "onlyWithPrice": true,
  "onlyInStock": false,
  "includePromoted": false,
  "country": "us"
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# 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 = {
    "queries": [
        "arched cabinet",
        "modern floor lamp"
    ],
    "pins": [],
    "maxProducts": 100,
    "maxPinsPerQuery": 60,
    "country": "us"
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/pinterest-product-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 = {
    "queries": [
        "arched cabinet",
        "modern floor lamp",
    ],
    "pins": [],
    "maxProducts": 100,
    "maxPinsPerQuery": 60,
    "country": "us",
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/pinterest-product-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 '{
  "queries": [
    "arched cabinet",
    "modern floor lamp"
  ],
  "pins": [],
  "maxProducts": 100,
  "maxPinsPerQuery": 60,
  "country": "us"
}' |
apify call apt_marble/pinterest-product-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apt_marble/pinterest-product-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/EFkj4eVWwgabb6Ouj/builds/vMF0M7yzFSBzUNC11/openapi.json
