Last active
March 9, 2026 15:12
-
-
Save gotexis/8908926ca9b9a86fe3d6b4a9b830694f to your computer and use it in GitHub Desktop.
Meta Facebook Ad Report Extractor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name FB Ad Expert Extractor (Row Linked) | |
| // @namespace http://tampermonkey.net/ | |
| // @version 2.5 | |
| // @description 保留 v1.2 的媒体提取能力,并利用 aria-haspopup 关联表格行数据 | |
| // @author Gemini | |
| // @match *://*.facebook.com/adsmanager/* | |
| // @grant none | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| window.savedArray = window.savedArray || []; | |
| const processedFingerprints = new Set(); | |
| // 用于存放当前鼠标悬停行的临时数据 | |
| let activeRowData = null; | |
| console.log('%c 🔗 缝合器已就绪: 正在监听预览触发并关联行数据', 'color: #00ff00; font-weight: bold;'); | |
| // --- 1. 监听触发器 (aria-haspopup) 捕捉行数据 --- | |
| document.addEventListener('mouseover', (e) => { | |
| const trigger = e.target.closest('[aria-haspopup="true"]'); | |
| if (trigger) { | |
| const rowContainer = trigger.closest('div[role="presentation"]'); | |
| if (rowContainer) { | |
| // 抓取行内的所有指标 | |
| const cells = Array.from(rowContainer.querySelectorAll('._4lg0')); | |
| activeRowData = { | |
| adNameInRow: rowContainer.querySelector('._4-b4')?.innerText || "Unknown", | |
| rowMetrics: cells.map(c => c.innerText.trim()).filter(t => t.length > 0), | |
| lockedAt: Date.now() | |
| }; | |
| // 在控制台打印,确认行数据已被锁定 | |
| console.log(`%c 📌 已锁定行: ${activeRowData.adNameInRow}`, 'color: #777'); | |
| } | |
| } | |
| }, true); | |
| // --- 2. 增强版素材提取 (基于 v1.2 逻辑) --- | |
| function extractAdData() { | |
| const container = document.querySelector('[data-testid="ad-preview-mobile-feed-standard"]'); | |
| if (!container) return; | |
| // 提取基本信息 | |
| const pageName = container.querySelector('.x1xlr1w8')?.innerText || ''; | |
| const adMessage = container.querySelector('[data-ad-preview="message"]')?.innerText || ''; | |
| const headline = container.querySelector('[data-ad-preview="headline"]')?.innerText || ''; | |
| const ctaText = container.querySelector('[data-ad-preview="cta"]')?.innerText || ''; | |
| let mediaItems = []; | |
| // --- v1.2 的媒体识别逻辑 --- | |
| const carouselCards = container.querySelectorAll('[data-ad-preview="carousel-card"]'); | |
| if (carouselCards.length > 0) { | |
| carouselCards.forEach(card => { | |
| const img = card.querySelector('img'); | |
| if (img) mediaItems.push({ type: 'image', url: img.src }); | |
| }); | |
| } else { | |
| const videoElement = container.querySelector('video'); | |
| const videoCover = container.querySelector('img[data-ad-preview="video-cover"]'); | |
| if (videoElement && videoElement.src) { | |
| mediaItems.push({ | |
| type: 'video', | |
| url: videoElement.src, | |
| cover: videoCover?.src | |
| }); | |
| } else { | |
| const singleImg = container.querySelector('img[data-ad-preview="image"]'); | |
| if (singleImg) { | |
| mediaItems.push({ type: 'image', url: singleImg.src }); | |
| } | |
| } | |
| } | |
| // --- 缝合逻辑 --- | |
| const firstMediaUrl = mediaItems[0]?.url || ''; | |
| // 增加行名称作为指纹的一部分,防止不同行但素材相同的情况 | |
| const rowName = activeRowData ? activeRowData.adNameInRow : 'NoRow'; | |
| const fingerprint = `${rowName}_${pageName}_${adMessage.slice(0, 20)}_${firstMediaUrl.slice(-30)}`; | |
| if (processedFingerprints.has(fingerprint) || mediaItems.length === 0) return; | |
| // 合并数据 | |
| const finalAdObject = { | |
| capturedAt: new Date().toLocaleTimeString(), | |
| // 来自表格行的数据 | |
| rowInfo: activeRowData ? { | |
| adName: activeRowData.adNameInRow, | |
| metrics: activeRowData.rowMetrics | |
| } : "Row Data Not Captured", | |
| // 来自预览窗的数据 | |
| previewInfo: { | |
| pageName, | |
| adMessage, | |
| headline, | |
| ctaText, | |
| mediaItems | |
| }, | |
| fingerprint: fingerprint | |
| }; | |
| window.savedArray.push(finalAdObject); | |
| processedFingerprints.add(fingerprint); | |
| const typeLabel = mediaItems[0]?.type === 'video' ? '📹 视频' : '🖼️ 图片'; | |
| console.log(`%c ✅ 关联抓取成功 [${typeLabel}]`, 'color: #42b983; font-weight: bold;', finalAdObject); | |
| // 每次抓取成功后,可以考虑不立刻清空 activeRowData,因为有时预览窗加载比 hover 慢 | |
| } | |
| // 维持 v1.2 的轮询机制 | |
| setInterval(extractAdData, 1000); | |
| // 下载功能 | |
| window.downloadAds = function() { | |
| const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(window.savedArray, null, 2)); | |
| const downloadAnchorNode = document.createElement('a'); | |
| downloadAnchorNode.setAttribute("href", dataStr); | |
| downloadAnchorNode.setAttribute("download", `fb_stitched_ads_${Date.now()}.json`); | |
| document.body.appendChild(downloadAnchorNode); | |
| downloadAnchorNode.click(); | |
| downloadAnchorNode.remove(); | |
| }; | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment