
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
14
Total users
10
Monthly users
4
Runs succeeded
>99%
Last modified
2 days ago
.gitignore
# This file tells Git which files shouldn't be added to source control
.ideadistnode_modulesstorage
# Added by Apify CLI.venv
Dockerfile
FROM apify/actor-node-puppeteer-chrome:20
USER root
# install tsxRUN yarn global add tsx
USER myuser
COPY package.json yarn.lock ./
# install dependenciesRUN yarn --production
# copy the source codeCOPY . ./
CMD yarn start
package.json
{ "name": "pinterest-ads-scraper", "version": "0.0.1", "type": "module", "description": "This is an example of a Crawlee project.", "dependencies": { "apify": "^3.2.6", "crawlee": "^3.0.0", "puppeteer": "^24.6.1" }, "devDependencies": { "@apify/tsconfig": "^0.1.0", "@types/node": "^20.0.0", "tsx": "^4.4.0", "typescript": "^5.8.3" }, "scripts": { "start": "npm run start:dev", "start:prod": "node dist/main.js", "start:dev": "tsx src/main.ts", "build": "tsc", "test": "echo \"Error: oops, the actor has no tests yet, sad!\" && exit 1" }, "author": "It's not you it's me", "license": "ISC"}
tsconfig.json
{ "extends": "@apify/tsconfig", "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", "target": "ES2022", "outDir": "dist", "noUnusedLocals": false, "lib": ["DOM"], "skipLibCheck": true }, "include": ["./src/**/*"], "exclude": ["node_modules", "dist"]}
.actor/actor.json
{ "actorSpecification": 1, "name": "pinterest-ads-scraper", "title": "Pinterest Ads Scraper", "description": "Pinterest Ads Scraper", "version": "0.0", "meta": { "templateId": "js-crawlee-puppeteer-chrome" }, "input": "./input_schema.json", "dockerfile": "./Dockerfile", "storages": { "dataset": { "actorSpecification": 1, "views": { "overview": { "title": "Overview", "transformation": { "fields": [ "pin_id", "ad_details" ] }, "display": { "component": "table", "properties": { "pin_id": { "format": "string", "label": "Pin ID" }, "ad_details": { "format": "object", "label": "Details" } } } } } } }}
.actor/input_schema.json
{ "title": "Pinterest Ads Scraper", "type": "object", "schemaVersion": 1, "properties": { "country": { "title": "Country", "type": "string", "description": "Ad Country", "editor": "select", "prefill": "FR", "enumTitles": [ "Austria", "Belgium", "Bulgaria", "Brazil", "Croatia", "Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Germany", "Greece", "Hungary", "Ireland", "Italy", "Latvia", "Lithuania", "Luxembourg", "Malta", "Netherlands", "Poland", "Portugal", "Romania", "Slovakia", "Slovenia", "Spain", "Sweden", "Turkey" ], "enum": [ "AT", "BE", "BG", "BR", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "TR" ] }, "start_date": { "title": "Start Date", "type": "string", "description": "Select absolute date in format YYYY-MM-DD, The date range must be less than 30 days.", "editor": "datepicker", "pattern": "^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$", "prefill": "2025-04-01" }, "end_date": { "title": "End Date", "type": "string", "description": "Select absolute date in format YYYY-MM-DD, The date range must be less than 30 days.", "editor": "datepicker", "pattern": "^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$", "prefill": "2025-04-12" }, "category": { "title": "Category", "type": "string", "description": "Ad Category", "editor": "select", "prefill": "ALL", "enumTitles": [ "All Categories", "Animals", "Architecture", "Art", "Beauty", "Children's fashion", "Design", "DIY and crafts", "Education", "Electronics", "Entertainment", "Event planning", "Finance", "Food and drinks", "Health", "Home decor", "Gardening", "Men's fashion", "Parenting", "Quotes", "Sport", "Travel", "Vehicles", "Wedding", "Women's fashion", "Other" ], "enum": [ "ALL", "ANIMALS", "ARCHITECTURE", "ART", "BEAUTY", "CHILDRENS_FASHION", "DESIGN", "DIY_AND_CRAFTS", "EDUCATION", "ELECTRONICS", "ENTERTAINMENT", "EVENT_PLANNING", "FINANCE", "FOOD_AND_DRINKS", "HEALTH", "HOME_DECOR", "GARDENING", "MENS_FASHION", "PARENTING", "QUOTES", "SPORT", "TRAVEL", "VEHICLES", "WEDDING", "WOMENS_FASHION", "OTHER" ] }, "gender": { "title": "Gender", "type": "string", "description": "Ad Gender", "editor": "select", "prefill": "ALL", "enumTitles": ["All Genders", "Male", "Female", "Unspecified"], "enum": ["ALL", "MALE", "FEMALE", "UNSPECIFIED"] }, "age": { "title": "Age", "type": "string", "description": "Ad Age", "editor": "select", "prefill": "ALL", "enumTitles": [ "All Ages", "18-24", "21-24", "18-20", "25-34", "35-44", "45-49", "50-54", "55-64", "65+" ], "enum": [ "ALL", "AGE_18_24", "AGE_21_24", "AGE_18_20", "AGE_25_34", "AGE_35_44", "AGE_45_49", "AGE_50_54", "AGE_55_64", "AGE_65_PLUS" ] }, "results_limit": { "title": "Results Limit", "type": "integer", "editor": "number", "description": "Limit number of results.", "prefill": 10 } }, "required": ["start_date", "end_date", "country"]}
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 url21 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',4 'Belgium',5 'Bulgaria',6 'Brazil',7 'Croatia',8 'Cyprus',9 'Czech Republic',10 'Denmark',11 'Estonia',12 'Finland',13 'France',14 'Germany',15 'Greece',16 'Hungary',17 'Ireland',18 'Italy',19 'Latvia',20 'Lithuania',21 'Luxembourg',22 'Malta',23 'Netherlands',24 'Poland',25 'Portugal',26 'Romania',27 'Slovakia',28 'Slovenia',29 'Spain',30 'Sweden',31 'Turkey',32 ];33 const values = [34 'AT',35 'BE',36 'BG',37 'BR',38 'HR',39 'CY',40 'CZ',41 'DK',42 'EE',43 'FI',44 'FR',45 'DE',46 'GR',47 'HU',48 'IE',49 'IT',50 'LV',51 'LT',52 'LU',53 'MT',54 'NL',55 'PL',56 'PT',57 'RO',58 'SK',59 'SI',60 'ES',61 'SE',62 'TR',63 ];64 return values;65}66
67export function getCategories(): string[] {68 const labels = [69 'All Categories',70 'Animals',71 'Architecture',72 'Art',73 'Beauty',74 "Children's fashion",75 'Design',76 'DIY and crafts',77 'Education',78 'Electronics',79 'Entertainment',80 'Event planning',81 'Finance',82 'Food and drinks',83 'Health',84 'Home decor',85 'Gardening',86 "Men's fashion",87 'Parenting',88 'Quotes',89 'Sport',90 'Travel',91 'Vehicles',92 'Wedding',93 "Women's fashion",94 'Other',95 ];96
97 const values = [98 'ALL',99 'ANIMALS',100 'ARCHITECTURE',101 'ART',102 'BEAUTY',103 'CHILDRENS_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 'MENS_FASHION',116 'PARENTING',117 'QUOTES',118 'SPORT',119 'TRAVEL',120 'VEHICLES',121 'WEDDING',122 'WOMENS_FASHION',123 'OTHER',124 ];125
126 return values;127}128
129export function getAges(): string[] {130 let labels = [131 'All Ages',132 '18-24',133 '21-24',134 '18-20',135 '25-34',136 '35-44',137 '45-49',138 '50-54',139 '55-64',140 '65+',141 ];142 let values = [143 'ALL',144 'AGE_18_24',145 'AGE_21_24',146 'AGE_18_20',147 'AGE_25_34',148 'AGE_35_44',149 'AGE_45_49',150 'AGE_50_54',151 'AGE_55_64',152 'AGE_65_PLUS',153 ];154 return values;155}156
157export function getGenders(): string[] {158 let labels = ['All Genders', 'male', 'female', 'unspecified'];159 let values = ['ALL', 'MALE', 'FEMALE', 'UNSPECIFIED'];160 return values;161}
src/routes.ts
1import { createCheerioRouter } from 'crawlee';2import {3 delay,4 formatDate,5 getDateDiffInDays,6 getDateSinceNumberOfDays,7} from './utils.js';8import { PinterestAdInterface } from './interfaces.js';9export const router = createCheerioRouter();10
11/* Constants */12const HEADERS = {13 Accept: 'application/json, text/javascript, */*, q=0.01',14 'x-b3-flags': '0',15 'x-app-version': '69a5e51',16 'screen-dpr': '1.25',17 'x-pinterest-pws-handler': 'sterling/ads-repository.js', // important18 'User-Agent':19 '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',20};21/* End of Constants*/22
23/* Main Handlers */24router.addHandler(25 'pinterest-ads',26 async ({ log, request, sendRequest, pushData }) => {27 let { resultLimit, start_date, end_date, country, category, age, gender } =28 request.userData;29
30 // default value for start_date31 if (!start_date) start_date = formatDate(getDateSinceNumberOfDays(7));32 // default value for end_date33 if (!end_date) end_date = formatDate();34
35 if (getDateDiffInDays(start_date, end_date) >= 30)36 throw 'The date range must be less than 30 days.';37
38 // get pinterest ads39 let numOfAds = 0;40 let bookmark: string | null = null;41 while (numOfAds < resultLimit) {42 // API PARAMS43 let options: any = {44 url: '/ads/v4/ads_repository/ad_library/',45 data: {46 start_date: start_date,47 end_date: end_date,48 country: country,49 page_size: 24,50 },51 };52 // pagination53 if (bookmark) options.data.bookmark = bookmark;54 // filter55 if (category && category !== 'ALL') options.data.vertical = category;56 if (gender && gender !== 'ALL') options.data.gender = gender;57 if (age && age !== 'ALL') options.data.age_bucket = age;58
59 const queryParams = new URLSearchParams({60 data: JSON.stringify({61 options: options,62 context: {},63 }),64 });65
66 let pinterestAdsApiUrl = `https://ads.pinterest.com/resource/ApiResource/get/`;67 pinterestAdsApiUrl = `${pinterestAdsApiUrl}?${queryParams.toString()}`;68 const { body, statusCode } = await sendRequest({69 method: 'GET',70 url: pinterestAdsApiUrl,71 headers: HEADERS,72 });73 const pinterestAdsResponse = JSON.parse(body);74 if (75 statusCode === 200 &&76 pinterestAdsResponse?.resource_response?.status === 'success'77 ) {78 let ads: PinterestAdInterface[] =79 pinterestAdsResponse.resource_response.data.pin_ids_with_metadata;80 for (let item of ads) {81 if (numOfAds < resultLimit) {82 log.info(`-> ${item.pin_id}`);83 numOfAds++;84 await pushData(item); // for handling apify dataset insertion85 }86 }87 bookmark = pinterestAdsResponse.resource_response.bookmark;88 if (!bookmark) break;89 } else {90 throw 'something went wrong while getting pinterest ads data';91 }92 await delay(1000); // delay for a second93 }94 }95);96/* 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 Helpers8export 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-based17 const day = String(date.getDate()).padStart(2, '0');18 return `${year}-${month}-${day}`;19}20
21export function getDateDiffInDays(22 date1: Date | string,23 date2: Date | string24): number {25 const d1 = new Date(date1);26 const d2 = new Date(date2);27 const diffTime = Math.abs(d2.getTime() - d1.getTime()); // difference in milliseconds28 const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));29 return diffDays;30}