
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
Total users
5
Monthly users
5
Runs succeeded
>99%
Last modified
21 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", "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 values13}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 values75}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 values103}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 values119}
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', // important13 '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 ads36 let numOfAds = 037 let bookmark: string | null = null;38 while (numOfAds < resultLimit) {39 // API PARAMS40 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: 2447 }48 }49 // pagination50 if (bookmark) options.data.bookmark = bookmark51 // filter52 if (category && category !== 'ALL') options.data.vertical = category53 if (gender && gender !== 'ALL') options.data.gender = gender54 if (age && age !== 'ALL') options.data.age_bucket = age55
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_metadata73 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 insertion78 }79 }80 bookmark = pinterestAdsResponse.resource_response.bookmark81 if (!bookmark) break;82 } else {83 throw "something went wrong while getting pinterest ads data"84 }85 await delay(1000); // delay for a second86 }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 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(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 milliseconds25 const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));26 return diffDays;27}