Spaces:
Sleeping
Sleeping
| const axios = require('axios'); | |
| const FormData = require('form-data'); | |
| const crypto = require('crypto'); | |
| // SCRAPE BY SIPUTZX | |
| class PinterestLensScraper { | |
| constructor(authToken) { | |
| this.authToken = authToken; | |
| } | |
| getRandomHeaders() { | |
| const devices = [ | |
| { model: 'SM-G991B', manufacturer: 'samsung', name: 'Samsung Galaxy S21' }, | |
| { model: 'SM-A525F', manufacturer: 'samsung', name: 'Samsung Galaxy A52' }, | |
| { model: 'Pixel 6', manufacturer: 'Google', name: 'Google Pixel 6' }, | |
| { model: 'Pixel 7 Pro', manufacturer: 'Google', name: 'Google Pixel 7 Pro' }, | |
| { model: 'M2101K6G', manufacturer: 'Xiaomi', name: 'Xiaomi Redmi Note 10' }, | |
| { model: '2201117TG', manufacturer: 'Xiaomi', name: 'Xiaomi 11T' }, | |
| { model: 'CPH2121', manufacturer: 'OPPO', name: 'OPPO Reno5' }, | |
| { model: 'RMX3085', manufacturer: 'realme', name: 'realme 8 Pro' }, | |
| { model: 'itel S665L', manufacturer: 'ITEL', name: 'itel S665L' }, | |
| { model: 'TECNO KE5', manufacturer: 'TECNO', name: 'TECNO Spark 7' } | |
| ]; | |
| const versions = ['13.36.2', '13.35.0', '13.34.1', '13.33.0', '13.32.1']; | |
| const androidVersions = ['11', '12', '13']; | |
| const device = devices[Math.floor(Math.random() * devices.length)]; | |
| const version = versions[Math.floor(Math.random() * versions.length)]; | |
| const androidVer = androidVersions[Math.floor(Math.random() * androidVersions.length)]; | |
| const advertisingId = crypto.randomUUID(); | |
| const hardwareId = crypto.randomBytes(8).toString('hex'); | |
| const installId = crypto.randomBytes(16).toString('hex'); | |
| return { | |
| 'User-Agent': `Pinterest for Android/${version} (${device.model}; ${androidVer})`, | |
| 'accept-language': 'id-ID', | |
| 'x-pinterest-advertising-id': advertisingId, | |
| 'x-pinterest-app-type-detailed': '3', | |
| 'x-pinterest-device': device.model, | |
| 'x-pinterest-device-hardwareid': hardwareId, | |
| 'x-pinterest-device-manufacturer': device.manufacturer, | |
| 'x-pinterest-installid': installId, | |
| 'x-pinterest-webview-supported': 'false', | |
| 'x-pinterest-appstate': 'active', | |
| 'x-node-id': 'true', | |
| 'authorization': `Bearer ${this.authToken}` | |
| }; | |
| } | |
| async searchByImage(imageUrl, pageSize = 12) { | |
| const data = new FormData(); | |
| data.append('camera_type', '0'); | |
| data.append('source_type', '1'); | |
| data.append('video_autoplay_disabled', '0'); | |
| data.append('fields', this.getFields()); | |
| data.append('page_size', pageSize.toString()); | |
| data.append('image_url', imageUrl); | |
| const headers = this.getRandomHeaders(); | |
| const response = await axios.post( | |
| 'https://api.pinterest.com/v3/visual_search/lens/search/', | |
| data, | |
| { headers: { ...headers, ...data.getHeaders() } } | |
| ); | |
| return this.parseResults(response.data); | |
| } | |
| async getMoreResults(bookmark, url, pageSize = 12) { | |
| const params = new URLSearchParams({ | |
| bookmark, | |
| camera_type: '0', | |
| source_type: '1', | |
| video_autoplay_disabled: '0', | |
| fields: this.getFields(), | |
| url, | |
| page_size: pageSize.toString(), | |
| view_type: '119', | |
| view_parameter: '3064' | |
| }); | |
| const headers = this.getRandomHeaders(); | |
| const response = await axios.get( | |
| `https://api.pinterest.com/v3/visual_search/lens/search/?${params}`, | |
| { headers } | |
| ); | |
| return this.parseResults(response.data); | |
| } | |
| async scrapeAll(imageUrl, maxPages = 5) { | |
| const allResults = []; | |
| const firstPage = await this.searchByImage(imageUrl); | |
| allResults.push(...firstPage.pins); | |
| let bookmark = firstPage.bookmark; | |
| let url = firstPage.url; | |
| let page = 2; | |
| while (bookmark && page <= maxPages) { | |
| const nextPage = await this.getMoreResults(bookmark, url); | |
| allResults.push(...nextPage.pins); | |
| bookmark = nextPage.bookmark; | |
| page++; | |
| await this.delay(100); | |
| } | |
| return { | |
| total: allResults.length, | |
| pins: allResults, | |
| visualObjects: firstPage.visualObjects, | |
| searchIdentifier: firstPage.searchIdentifier | |
| }; | |
| } | |
| parseResults(response) { | |
| const pins = response.data.map(pin => ({ | |
| id: pin.id, | |
| title: pin.title || '', | |
| description: pin.description || '', | |
| imageUrl: pin.images?.['736x']?.url || pin.images?.originals?.url, | |
| thumbnailUrl: pin.images?.['236x']?.url, | |
| dominantColor: pin.dominant_color, | |
| creator: { | |
| id: pin.pinner?.id, | |
| username: pin.pinner?.username, | |
| fullName: pin.pinner?.full_name, | |
| imageUrl: pin.pinner?.image_medium_url | |
| }, | |
| board: { | |
| id: pin.board?.id, | |
| name: pin.board?.name, | |
| url: pin.board?.url | |
| }, | |
| stats: { | |
| saves: pin.aggregated_pin_data?.aggregated_stats?.saves || 0, | |
| comments: pin.comment_count || 0 | |
| }, | |
| createdAt: pin.created_at, | |
| isVideo: pin.is_video || false, | |
| link: pin.link, | |
| domain: pin.domain | |
| })); | |
| return { | |
| pins, | |
| bookmark: response.bookmark, | |
| url: response.url, | |
| visualObjects: response.visual_objects, | |
| searchIdentifier: response.search_identifier | |
| }; | |
| } | |
| getFields() { | |
| return 'pin.{id,title,description,images[736x,236x],dominant_color,pinner(),board(),aggregated_pin_data(),comment_count,created_at,is_video,link,domain},user.{id,username,full_name,image_medium_url},board.{id,name,url},aggregatedpindata.{aggregated_stats}'; | |
| } | |
| delay(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| } | |
| const handler = async (req, res) => { | |
| try { | |
| const { imageUrl, token, maxPages = 5, key } = req.query; | |
| if (!imageUrl) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: 'Missing required parameter: imageUrl' | |
| }); | |
| } | |
| if (!token) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: 'Missing required parameter: token' | |
| }); | |
| } | |
| const scraper = new PinterestLensScraper(token); | |
| const results = await scraper.scrapeAll(imageUrl, parseInt(maxPages)); | |
| res.json({ | |
| author: 'siputzx', | |
| success: true, | |
| data: results | |
| }); | |
| } catch (error) { | |
| res.status(500).json({ | |
| success: false, | |
| error: error.message | |
| }); | |
| } | |
| }; | |
| module.exports = { | |
| name: 'Pinterest Lens Scraper', | |
| description: 'Scrape Pinterest pins using image search with lens', | |
| type: 'GET', | |
| routes: ['api/tools/pinlens'], | |
| tags: ['tools', 'pinterest', 'image-search'], | |
| main: ['tools', 'Search'], | |
| parameters: ['imageUrl', 'token', 'maxPages', 'key'], | |
| enabled: true, | |
| limit: 10, | |
| handler | |
| }; |