Pinterest Ads Scraper avatar
Pinterest Ads Scraper

Pricing

$35.00/month + usage

Go to Store
Pinterest Ads Scraper

Pinterest Ads Scraper

Developed by

Lexis Solutions

Maintained by Community

Scrape Pinterest ads to extract trending product insights, campaign performance, and audience data. Ideal for market research, competitor tracking, and digital marketing optimization. Fast, structured, and customizable ad data.

5.0 (3)

Pricing

$35.00/month + usage

12

Monthly users

4

Runs succeeded

>99%

Last modified

11 days ago

.gitignore

1# This file tells Git which files shouldn't be added to source control
2
3.idea
4dist
5node_modules
6storage
7
8# Added by Apify CLI
9.venv

Dockerfile

1FROM apify/actor-node-puppeteer-chrome:20
2
3USER root
4
5# install tsx
6RUN yarn global add tsx
7
8USER myuser
9
10COPY package.json yarn.lock ./
11
12# install dependencies
13RUN yarn --production
14
15# copy the source code
16COPY . ./
17
18CMD yarn start

package.json

1{
2  "name": "pinterest-ads-scraper",
3  "version": "0.0.1",
4  "type": "module",
5  "description": "This is an example of a Crawlee project.",
6  "dependencies": {
7    "apify": "^3.2.6",
8    "crawlee": "^3.0.0",
9    "puppeteer": "^24.6.1"
10  },
11  "devDependencies": {
12    "@apify/tsconfig": "^0.1.0",
13    "@types/node": "^20.0.0",
14    "tsx": "^4.4.0",
15    "typescript": "^5.8.3"
16  },
17  "scripts": {
18    "start": "npm run start:dev",
19    "start:prod": "node dist/main.js",
20    "start:dev": "tsx src/main.ts",
21    "build": "tsc",
22    "test": "echo \"Error: oops, the actor has no tests yet, sad!\" && exit 1"
23  },
24  "author": "It's not you it's me",
25  "license": "ISC"
26}

tsconfig.json

1{
2	"extends": "@apify/tsconfig",
3	"compilerOptions": {
4		"module": "NodeNext",
5		"moduleResolution": "NodeNext",
6		"target": "ES2022",
7		"outDir": "dist",
8		"noUnusedLocals": false,
9		"lib": ["DOM"],
10		"skipLibCheck": true
11	},
12	"include": ["./src/**/*"],
13	"exclude": ["node_modules", "dist"]
14}

.actor/actor.json

1{
2  "actorSpecification": 1,
3  "name": "pinterest-ads-scraper",
4  "title": "Pinterest Ads Scraper",
5  "description": "Pinterest Ads Scraper",
6  "version": "0.0",
7  "meta": {
8    "templateId": "js-crawlee-puppeteer-chrome"
9  },
10  "input": "./input_schema.json",
11  "dockerfile": "./Dockerfile",
12  "storages": {
13    "dataset": {
14      "actorSpecification": 1,
15      "views": {
16        "overview": {
17          "title": "Overview",
18          "transformation": {
19            "fields": [
20              "pin_id",
21              "ad_details"
22            ]
23          },
24          "display": {
25            "component": "table",
26            "properties": {
27              "pin_id": {
28                "format": "string",
29                "label": "Pin ID"
30              },
31              "ad_details": {
32                "format": "object",
33                "label": "Details"
34              }
35            }
36          }
37        }
38      }
39    }
40  }
41}

.actor/input_schema.json

1{
2  "title": "Pinterest Ads Scraper",
3  "type": "object",
4  "schemaVersion": 1,
5  "properties": {
6    "country": {
7      "title": "Country",
8      "type": "string",
9      "description": "Ad Country",
10      "editor": "select",
11      "prefill": "FR",
12      "enumTitles": [
13        "Austria",
14        "Belgium",
15        "Bulgaria",
16        "Brazil",
17        "Croatia",
18        "Cyprus",
19        "Czech Republic",
20        "Denmark",
21        "Estonia",
22        "Finland",
23        "France",
24        "Germany",
25        "Greece",
26        "Hungary",
27        "Ireland",
28        "Italy",
29        "Latvia",
30        "Lithuania",
31        "Luxembourg",
32        "Malta",
33        "Netherlands",
34        "Poland",
35        "Portugal",
36        "Romania",
37        "Slovakia",
38        "Slovenia",
39        "Spain",
40        "Sweden",
41        "Turkey"
42      ],
43      "enum": [
44        "AT",
45        "BE",
46        "BG",
47        "BR",
48        "HR",
49        "CY",
50        "CZ",
51        "DK",
52        "EE",
53        "FI",
54        "FR",
55        "DE",
56        "GR",
57        "HU",
58        "IE",
59        "IT",
60        "LV",
61        "LT",
62        "LU",
63        "MT",
64        "NL",
65        "PL",
66        "PT",
67        "RO",
68        "SK",
69        "SI",
70        "ES",
71        "SE",
72        "TR"
73      ]
74    },
75    "start_date": {
76      "title": "Start Date",
77      "type": "string",
78      "description": "Select absolute date in format YYYY-MM-DD, The date range must be less than 30 days.",
79      "editor": "datepicker",
80      "pattern": "^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
81      "prefill": "2025-04-01"
82    },
83    "end_date": {
84      "title": "End Date",
85      "type": "string",
86      "description": "Select absolute date in format YYYY-MM-DD, The date range must be less than 30 days.",
87      "editor": "datepicker",
88      "pattern": "^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
89      "prefill": "2025-04-12"
90    },
91    "category": {
92      "title": "Category",
93      "type": "string",
94      "description": "Ad Category",
95      "editor": "select",
96      "prefill": "ALL",
97      "enumTitles": [
98        "All Categories",
99        "Animals",
100        "Architecture",
101        "Art",
102        "Beauty",
103        "Children's fashion",
104        "Design",
105        "DIY and crafts",
106        "Education",
107        "Electronics",
108        "Entertainment",
109        "Event planning",
110        "Finance",
111        "Food and drinks",
112        "Health",
113        "Home decor",
114        "Gardening",
115        "Men's fashion",
116        "Parenting",
117        "Quotes",
118        "Sport",
119        "Travel",
120        "Vehicles",
121        "Wedding",
122        "Women's fashion",
123        "Other"
124      ],
125      "enum": [
126        "ALL",
127        "ANIMALS",
128        "ARCHITECTURE",
129        "ART",
130        "BEAUTY",
131        "CHILDRENS_FASHION",
132        "DESIGN",
133        "DIY_AND_CRAFTS",
134        "EDUCATION",
135        "ELECTRONICS",
136        "ENTERTAINMENT",
137        "EVENT_PLANNING",
138        "FINANCE",
139        "FOOD_AND_DRINKS",
140        "HEALTH",
141        "HOME_DECOR",
142        "GARDENING",
143        "MENS_FASHION",
144        "PARENTING",
145        "QUOTES",
146        "SPORT",
147        "TRAVEL",
148        "VEHICLES",
149        "WEDDING",
150        "WOMENS_FASHION",
151        "OTHER"
152      ]
153    },
154    "gender": {
155      "title": "Gender",
156      "type": "string",
157      "description": "Ad Gender",
158      "editor": "select",
159      "prefill": "ALL",
160      "enumTitles": ["All Genders", "Male", "Female", "Unspecified"],
161      "enum": ["ALL", "MALE", "FEMALE", "UNSPECIFIED"]
162    },
163    "age": {
164      "title": "Age",
165      "type": "string",
166      "description": "Ad Age",
167      "editor": "select",
168      "prefill": "ALL",
169      "enumTitles": [
170        "All Ages",
171        "18-24",
172        "21-24",
173        "18-20",
174        "25-34",
175        "35-44",
176        "45-49",
177        "50-54",
178        "55-64",
179        "65+"
180      ],
181      "enum": [
182        "ALL",
183        "AGE_18_24",
184        "AGE_21_24",
185        "AGE_18_20",
186        "AGE_25_34",
187        "AGE_35_44",
188        "AGE_45_49",
189        "AGE_50_54",
190        "AGE_55_64",
191        "AGE_65_PLUS"
192      ]
193    },
194    "results_limit": {
195      "title": "Results Limit",
196      "type": "integer",
197      "editor": "number",
198      "description": "Limit number of results.",
199      "prefill": 10
200    }
201  },
202  "required": ["start_date", "end_date", "country"]
203}

src/interfaces.ts

1export interface InputSchema {
2  start_date: string;
3  end_date: string;
4  country: string;
5  category?: string;
6  age?: string;
7  gender?: string;
8  results_limit: number;
9}
10
11export interface PinterestAdInterface {
12  pin_id: string;
13  ad_details: {
14    advertiser_names: string | null;
15    statement_of_reasons: string | null;
16    review_status: string | null;
17    violation_source: string | null;
18    violation_decision_means: string | null;
19    start_date: string;
20    end_date: string;
21    age_buckets: object;
22    genders: object;
23    postal_codes: object;
24    metros: object;
25    regions: object;
26    countries: object;
27    content_commercial: boolean;
28    interests: object;
29    pinner_list_types: object;
30    user_count_by_country: object;
31    user_count_eu: string;
32    keywords_used: boolean;
33    negative_keywords_used: boolean;
34    image_link: string;
35    pin_data: {
36      image_link: string;
37      video_link: string | null;
38      details: string;
39      title: string;
40      content_creator_name: string | null;
41      story_pin_page_blocks: object;
42    };
43  }
44}

src/main.ts

1import { Actor } from "apify";
2import { CheerioCrawler, RequestQueue } from "crawlee";
3import { InputSchema } from "./interfaces.js";
4import { router } from "./routes.js";
5
6Actor.main(async () => {
7  let defaultInput: any = {
8    start_date: '2025-04-01',
9    end_date: '2024-04-12',
10    country: 'FR',
11    category: 'ANIMALS',
12    age: 'AGE_18_24',
13    gender: 'MALE',
14    results_limit: 50,
15  };
16
17  let input: InputSchema | null = await Actor.getInput();
18  if (input == null) input = defaultInput;
19
20  // pinterest main url
21  const pinterestAdsUrl = "https://ads.pinterest.com/ads-repository/";
22
23  const requestQueue = await RequestQueue.open();
24  await requestQueue.addRequest({
25    url: pinterestAdsUrl,
26    label: "pinterest-ads",
27    userData: {
28      start_date: input?.start_date,
29      end_date: input?.end_date,
30      country: input?.country,
31      category: input?.category,
32      age: input?.age,
33      gender: input?.gender,
34      resultLimit: input?.results_limit,
35    },
36  });
37
38  const crawler = new CheerioCrawler({
39    requestHandler: router,
40    requestQueue,
41  });
42
43  await crawler.run();
44});

src/mappers.ts

1export function getCountries(): string[] {
2  const labels = [
3    "Austria", "Belgium", "Bulgaria", "Brazil", "Croatia", "Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland",
4    "France", "Germany", "Greece", "Hungary", "Ireland", "Italy", "Latvia", "Lithuania", "Luxembourg", "Malta",
5    "Netherlands", "Poland", "Portugal", "Romania", "Slovakia", "Slovenia", "Spain", "Sweden", "Turkey"
6  ];
7  const values = [
8    "AT", "BE", "BG", "BR", "HR", "CY", "CZ", "DK", "EE", "FI",
9    "FR", "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT",
10    "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "TR"
11  ];
12  return values
13}
14
15export function getCategories(): string[] {
16  const labels = [
17    "All Categories",
18    "Animals",
19    "Architecture",
20    "Art",
21    "Beauty",
22    "Children's fashion",
23    "Design",
24    "DIY and crafts",
25    "Education",
26    "Electronics",
27    "Entertainment",
28    "Event planning",
29    "Finance",
30    "Food and drinks",
31    "Health",
32    "Home decor",
33    "Gardening",
34    "Men's fashion",
35    "Parenting",
36    "Quotes",
37    "Sport",
38    "Travel",
39    "Vehicles",
40    "Wedding",
41    "Women's fashion",
42    "Other"
43  ];
44
45  const values = [
46    "ALL",
47    "ANIMALS",
48    "ARCHITECTURE",
49    "ART",
50    "BEAUTY",
51    "CHILDRENS_FASHION",
52    "DESIGN",
53    "DIY_AND_CRAFTS",
54    "EDUCATION",
55    "ELECTRONICS",
56    "ENTERTAINMENT",
57    "EVENT_PLANNING",
58    "FINANCE",
59    "FOOD_AND_DRINKS",
60    "HEALTH",
61    "HOME_DECOR",
62    "GARDENING",
63    "MENS_FASHION",
64    "PARENTING",
65    "QUOTES",
66    "SPORT",
67    "TRAVEL",
68    "VEHICLES",
69    "WEDDING",
70    "WOMENS_FASHION",
71    "OTHER"
72  ];
73
74  return values
75}
76
77export function getAges(): string[] {
78  let labels = [
79    "All Ages",
80    "18-24",
81    "21-24",
82    "18-20",
83    "25-34",
84    "35-44",
85    "45-49",
86    "50-54",
87    "55-64",
88    "65+"
89  ]
90  let values = [
91    "ALL",
92    "AGE_18_24",
93    "AGE_21_24",
94    "AGE_18_20",
95    "AGE_25_34",
96    "AGE_35_44",
97    "AGE_45_49",
98    "AGE_50_54",
99    "AGE_55_64",
100    "AGE_65_PLUS",
101  ]
102  return values
103}
104
105export function getGenders(): string[] {
106  let labels = [
107    "All Genders",
108    "male",
109    "female",
110    "unspecified"
111  ]
112  let values = [
113    "ALL",
114    "MALE",
115    "FEMALE",
116    "UNSPECIFIED"
117  ]
118  return values
119}

src/routes.ts

1import { createCheerioRouter } from "crawlee";
2import { delay, formatDate, getDateDiffInDays, getDateSinceNumberOfDays } from "./utils.js";
3import { PinterestAdInterface } from "./interfaces.js";
4export const router = createCheerioRouter();
5
6/* Constants */
7const HEADERS = {
8  'Accept': 'application/json, text/javascript, */*, q=0.01',
9  'x-b3-flags': '0',
10  'x-app-version': '69a5e51',
11  'screen-dpr': '1.25',
12  'x-pinterest-pws-handler': 'sterling/ads-repository.js', // important
13  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0',
14}
15/* End of Constants*/
16
17/* Main Handlers */
18router.addHandler("pinterest-ads", async ({ log, request, sendRequest, pushData }) => {
19  let {
20    resultLimit,
21    start_date,
22    end_date,
23    country,
24    category,
25    age,
26    gender } = request.userData;
27
28  // default value for start_date 
29  if (!start_date) start_date = formatDate(getDateSinceNumberOfDays(7))
30  // default value for end_date 
31  if (!end_date) end_date = formatDate()
32
33  if (getDateDiffInDays(start_date, end_date) >= 30) throw "The date range must be less than 30 days."
34
35  // get pinterest ads
36  let numOfAds = 0
37  let bookmark: string | null = null;
38  while (numOfAds < resultLimit) {
39    // API PARAMS
40    let options: any = {
41      url: '/ads/v4/ads_repository/ad_library/',
42      data: {
43        start_date: start_date,
44        end_date: end_date,
45        country: country,
46        page_size: 24
47      }
48    }
49    // pagination
50    if (bookmark) options.data.bookmark = bookmark
51    // filter
52    if (category && category !== 'ALL') options.data.vertical = category
53    if (gender && gender !== 'ALL') options.data.gender = gender
54    if (age && age !== 'ALL') options.data.age_bucket = age
55
56    const queryParams = new URLSearchParams({
57      data: JSON.stringify({
58        options: options,
59        context: {}
60      })
61    });
62
63    let pinterestAdsApiUrl = `https://ads.pinterest.com/resource/ApiResource/get/`;
64    pinterestAdsApiUrl = `${pinterestAdsApiUrl}?${queryParams.toString()}`
65    const { body, statusCode } = await sendRequest({
66      method: "GET",
67      url: pinterestAdsApiUrl,
68      headers: HEADERS,
69    });
70    const pinterestAdsResponse = JSON.parse(body)
71    if (statusCode === 200 && pinterestAdsResponse?.resource_response?.status === 'success') {
72      let ads: PinterestAdInterface[] = pinterestAdsResponse.resource_response.data.pin_ids_with_metadata
73      for (let item of ads) {
74        if (numOfAds < resultLimit) {
75          log.info(`-> ${item.pin_id}`);
76          numOfAds++;
77          await pushData(item); // for handling apify dataset insertion
78        }
79      }
80      bookmark = pinterestAdsResponse.resource_response.bookmark
81      if (!bookmark) break;
82    } else {
83      throw "something went wrong while getting pinterest ads data"
84    }
85    await delay(1000); // delay for a second
86  }
87});
88/* End of Main Handlers */

src/utils.ts

1export async function delay(time: number): Promise<void> {
2  return new Promise(function (resolve) {
3    setTimeout(resolve, time);
4  });
5}
6
7// Date Helpers
8export function getDateSinceNumberOfDays(days: number = 0): Date {
9  const date = new Date();
10  return new Date(date.getFullYear(), date.getMonth(), date.getDate() - days);
11};
12
13export function formatDate(dateInput: Date | string = new Date()): string {
14  const date = new Date(dateInput);
15  const year = date.getFullYear();
16  const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are 0-based
17  const day = String(date.getDate()).padStart(2, '0');
18  return `${year}-${month}-${day}`;
19}
20
21export function getDateDiffInDays(date1: Date | string, date2: Date | string): number {
22  const d1 = new Date(date1);
23  const d2 = new Date(date2);
24  const diffTime = Math.abs(d2.getTime() - d1.getTime()); // difference in milliseconds
25  const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
26  return diffDays;
27}

Pricing

Pricing model

Rental 

To use this Actor, you have to pay a monthly rental fee to the developer. The rent is subtracted from your prepaid usage every month after the free trial period. You also pay for the Apify platform usage.

Free trial

1 day

Price

$35.00