
Pinterest Ads Scraper
Pricing
$35.00/month + usage
Go to Store

Pinterest Ads Scraper
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
RentalTo 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