您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
获取Google广告透明度中心的搜索推荐数据和搜索结果数据
// ==UserScript== // @name Google Ads Transparency Scraper // @namespace 微信:eva-mirror // @version 1.8 // @description 获取Google广告透明度中心的搜索推荐数据和搜索结果数据 // @author sheire // @match https://adstransparency.google.com/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @connect adstransparency.google.com // ==/UserScript== (function() { 'use strict'; // 存储当前页面的数据 let currentPageData = []; let searchResultsData = []; let isPanelOpen = false; let currentTab = 'recommendations'; // 'recommendations' 或 'results' let isFetching = false; // 是否正在获取数据 // 添加自定义样式 GM_addStyle(` #fetch-recommendations-btn { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); width: 300px; height: 40px; background-color: #4285f4; color: white; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; z-index: 10000; box-shadow: 0 2px 10px rgba(0,0,0,0.2); display: flex; align-items: center; justify-content: center; } #fetch-recommendations-btn:hover { background-color: #3367d6; } #fetch-recommendations-btn .loading-icon { margin-right: 8px; animation: blink 200ms infinite; } @keyframes blink { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } } #data-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 80%; max-height: 80%; background: white; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); z-index: 10001; display: none; flex-direction: column; overflow: hidden; } #data-panel-header { padding: 16px; background: #f5f5f5; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #ddd; } #data-panel-content { padding: 16px; overflow-y: auto; flex-grow: 1; } #close-panel { background: none; border: none; font-size: 24px; cursor: pointer; color: #757575; } #copy-data, #copy-selected-data, #copy-data-results, #copy-selected-data-results { background: #4285f4; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-bottom: 16px; margin-right: 10px; } #copy-data:hover, #copy-selected-data:hover, #copy-data-results:hover, #copy-selected-data-results:hover { background: #3367d6; } #data-table, #data-table-results { width: 100%; border-collapse: collapse; } #data-table th, #data-table td, #data-table-results th, #data-table-results td { border: 1px solid #ddd; padding: 8px; text-align: left; } #data-table th, #data-table-results th { background-color: #f2f2f2; position: sticky; top: 0; } #data-table th:first-child, #data-table td:first-child, #data-table-results th:first-child, #data-table-results td:first-child { width: 40px; text-align: center; } #data-table a, #data-table-results a { color: #4285f4; text-decoration: none; } #data-table a:hover, #data-table-results a:hover { text-decoration: underline; } #overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 10000; display: none; } .tab-container { margin-bottom: 16px; border-bottom: 1px solid #ddd; } .tab-button { background-color: #f1f1f1; border: none; outline: none; cursor: pointer; padding: 10px 20px; transition: 0.3s; font-size: 16px; border-radius: 4px 4px 0 0; } .tab-button:hover { background-color: #ddd; } .tab-button.active { background-color: #4285f4; color: white; } .tab-content { display: none; } .tab-content.active { display: block; } `); // 创建按钮 const button = document.createElement('button'); button.id = 'fetch-recommendations-btn'; button.innerHTML = '获取广告主结果'; document.body.appendChild(button); // 创建弹窗和遮罩 const overlay = document.createElement('div'); overlay.id = 'overlay'; const panel = document.createElement('div'); panel.id = 'data-panel'; panel.innerHTML = ` <div id="data-panel-header"> <h3>广告数据</h3> <button id="close-panel">×</button> </div> <div id="data-panel-content"> <div class="tab-container"> <button class="tab-button active" data-tab="recommendations">搜索推荐</button> <button class="tab-button" data-tab="results">搜索结果</button> </div> <div id="recommendations-tab" class="tab-content active"> <button id="copy-data">一键复制全部内容</button> <button id="copy-selected-data">复制勾选的资料库 id</button> <table id="data-table"> <thead> <tr> <th><input type="checkbox" id="select-all-header"></th> <th>广告主名</th> <th>资料库 Id or 域名</th> <th>地区</th> <th>广告数</th> </tr> </thead> <tbody></tbody> </table> </div> <div id="results-tab" class="tab-content"> <button id="copy-data-results">一键复制全部内容</button> <button id="copy-selected-data-results">复制勾选的资料库 id</button> <table id="data-table-results"> <thead> <tr> <th><input type="checkbox" id="select-all-header-results"></th> <th>资料库 Id</th> <th>广告主名</th> <th>来源</th> </tr> </thead> <tbody></tbody> </table> </div> </div> `; document.body.appendChild(overlay); document.body.appendChild(panel); // 监听按钮点击事件 button.addEventListener('click', () => { showPanel(); }); // 关闭弹窗事件 document.getElementById('close-panel').addEventListener('click', () => { hidePanel(); }); // 点击遮罩关闭弹窗 overlay.addEventListener('click', () => { hidePanel(); }); // Tab切换事件 document.querySelectorAll('.tab-button').forEach(button => { button.addEventListener('click', () => { const tabName = button.getAttribute('data-tab'); switchTab(tabName); }); }); // 搜索推荐相关事件 document.getElementById('copy-data').addEventListener('click', () => { copyAllDataToClipboard(); }); document.getElementById('copy-selected-data').addEventListener('click', () => { copySelectedDataToClipboard(); }); document.getElementById('select-all-header').addEventListener('change', function() { const checkboxes = document.querySelectorAll('#data-table .row-checkbox'); checkboxes.forEach(checkbox => { checkbox.checked = this.checked; }); }); // 搜索结果相关事件 document.getElementById('copy-data-results').addEventListener('click', () => { copyAllResultsDataToClipboard(); }); document.getElementById('copy-selected-data-results').addEventListener('click', () => { copySelectedResultsDataToClipboard(); }); document.getElementById('select-all-header-results').addEventListener('change', function() { const checkboxes = document.querySelectorAll('#data-table-results .row-checkbox'); checkboxes.forEach(checkbox => { checkbox.checked = this.checked; }); }); // 显示弹窗 function showPanel() { overlay.style.display = 'block'; panel.style.display = 'flex'; isPanelOpen = true; renderTable(); renderResultsTable(); } // 隐藏弹窗 function hidePanel() { overlay.style.display = 'none'; panel.style.display = 'none'; isPanelOpen = false; } // Tab切换 function switchTab(tabName) { currentTab = tabName; // 更新tab按钮状态 document.querySelectorAll('.tab-button').forEach(button => { if (button.getAttribute('data-tab') === tabName) { button.classList.add('active'); } else { button.classList.remove('active'); } }); // 显示对应的内容 document.querySelectorAll('.tab-content').forEach(content => { if (content.id === tabName + '-tab') { content.classList.add('active'); } else { content.classList.remove('active'); } }); } // 渲染搜索推荐表格数据 function renderTable() { const tbody = document.querySelector('#data-table tbody'); tbody.innerHTML = ''; currentPageData.forEach((item, index) => { // 检查是否为资料库ID(以AR开头)或域名 let idCellContent = item['2'] || ''; if (item['2']) { if (item['2'].startsWith('AR')) { // 资料库ID类型 idCellContent = `<a href="https://adstransparency.google.com/advertiser/${item['2']}?region=anywhere" target="_blank">${item['2']}</a>`; } else if (item['2'].includes('.')) { // 域名类型 idCellContent = `<a href="https://adstransparency.google.com/?region=anywhere&domain=${item['2']}" target="_blank">${item['2']}</a>`; } } const row = document.createElement('tr'); row.innerHTML = ` <td><input type="checkbox" class="row-checkbox" data-index="${index}"></td> <td>${item['1'] || ''}</td> <td>${idCellContent}</td> <td>${item['3'] || ''}</td> <td>${item['4'] || ''}</td> `; tbody.appendChild(row); }); // 为新添加的复选框添加事件监听器 document.querySelectorAll('#data-table .row-checkbox').forEach(checkbox => { checkbox.addEventListener('change', function() { // 检查是否所有行都被选中,以更新全选复选框状态 const allCheckboxes = document.querySelectorAll('#data-table .row-checkbox'); const allChecked = Array.from(allCheckboxes).every(cb => cb.checked); document.getElementById('select-all-header').checked = allChecked; }); }); } // 渲染搜索结果表格数据 function renderResultsTable() { const tbody = document.querySelector('#data-table-results tbody'); tbody.innerHTML = ''; searchResultsData.forEach((item, index) => { const row = document.createElement('tr'); row.innerHTML = ` <td><input type="checkbox" class="row-checkbox" data-index="${index}" checked></td> <td><a href="https://adstransparency.google.com/advertiser/${item['1']}?region=anywhere" target="_blank">${item['1'] || ''}</a></td> <td>${item['12'] || ''}</td> <td>${item['14'] || ''}</td> `; tbody.appendChild(row); }); // 设置表头全选按钮为选中状态 document.getElementById('select-all-header-results').checked = true; // 为新添加的复选框添加事件监听器 document.querySelectorAll('#data-table-results .row-checkbox').forEach(checkbox => { checkbox.addEventListener('change', function() { // 检查是否所有行都被选中,以更新全选复选框状态 const allCheckboxes = document.querySelectorAll('#data-table-results .row-checkbox'); const allChecked = Array.from(allCheckboxes).every(cb => cb.checked); document.getElementById('select-all-header-results').checked = allChecked; }); }); } // 复制搜索推荐全部数据到剪贴板 function copyAllDataToClipboard() { let csvContent = "广告主名,资料库 Id or 域名,地区,广告数\n"; currentPageData.forEach(item => { csvContent += `"${item['1'] || ''}","${item['2'] || ''}","${item['3'] || ''}","${item['4'] || ''}"\n`; }); navigator.clipboard.writeText(csvContent).then(() => { alert('数据已复制到剪贴板'); }).catch(err => { console.error('复制失败:', err); alert('复制失败'); }); } // 复制搜索推荐选中数据到剪贴板 function copySelectedDataToClipboard() { const selectedCheckboxes = document.querySelectorAll('#data-table .row-checkbox:checked'); if (selectedCheckboxes.length === 0) { alert('请至少选择一项'); return; } const selectedIds = Array.from(selectedCheckboxes).map(checkbox => { const index = parseInt(checkbox.getAttribute('data-index')); return currentPageData[index]['2'] || ''; }).filter(id => id !== ''); const clipboardText = selectedIds.join(','); navigator.clipboard.writeText(clipboardText).then(() => { alert(`已复制 ${selectedIds.length} 个资料库 Id or 域名到剪贴板`); }).catch(err => { console.error('复制失败:', err); alert('复制失败'); }); } // 复制搜索结果全部数据到剪贴板 function copyAllResultsDataToClipboard() { let csvContent = "资料库 Id,广告主名,来源\n"; searchResultsData.forEach(item => { csvContent += `"${item['1'] || ''}","${item['12'] || ''}","${item['14'] || ''}"\n`; }); navigator.clipboard.writeText(csvContent).then(() => { alert('数据已复制到剪贴板'); }).catch(err => { console.error('复制失败:', err); alert('复制失败'); }); } // 复制搜索结果选中数据到剪贴板 function copySelectedResultsDataToClipboard() { const selectedCheckboxes = document.querySelectorAll('#data-table-results .row-checkbox:checked'); if (selectedCheckboxes.length === 0) { alert('请至少选择一项'); return; } const selectedIds = Array.from(selectedCheckboxes).map(checkbox => { const index = parseInt(checkbox.getAttribute('data-index')); return searchResultsData[index]['1'] || ''; }).filter(id => id !== ''); const clipboardText = selectedIds.join(','); navigator.clipboard.writeText(clipboardText).then(() => { alert(`已复制 ${selectedIds.length} 个资料库 Id到剪贴板`); }).catch(err => { console.error('复制失败:', err); alert('复制失败'); }); } // 更新按钮文本 function updateButtonText() { if (isFetching) { button.innerHTML = '<span class="loading-icon">🚗</span> 获取中...'; } else { button.innerHTML = '👌 获取广告主结果'; } } // 解析不同类型的数据格式 function parseDataItem(item) { // 类型1: {"1": {"1": "广告主名", "2": "资料库Id", "3": "地区", "4": {"2": {"1": "广告数"}}}} if (item['1']) { const adCount = item['1']['4'] && item['1']['4']['2'] && item['1']['4']['2']['1'] ? item['1']['4']['2']['1'] : ''; return { '1': item['1']['1'] || '', '2': item['1']['2'] || '', '3': item['1']['3'] || '', '4': adCount }; } // 类型2: {"2": {"1": "域名"}} if (item['2']) { return { '1': '', // 广告主名未知 '2': item['2']['1'] || '', // 域名 '3': '', // 地区未知 '4': '' // 广告数未知 }; } return null; } // 处理搜索推荐响应数据 function processResponseData(data) { try { if (typeof data === 'string') { data = JSON.parse(data); } let parsedItems = []; // 处理主要数据结构 if (data && data['1'] && Array.isArray(data['1'])) { data['1'].forEach(item => { const parsed = parseDataItem(item); if (parsed) { parsedItems.push(parsed); } }); } // 更新当前页面数据(只保留最后一次请求的数据) currentPageData = parsedItems; isFetching = false; updateButtonText(); // 如果面板打开中,更新表格 if (isPanelOpen && currentTab === 'recommendations') { renderTable(); } } catch (err) { console.error('处理数据出错:', err); } } // 处理搜索结果响应数据 function processSearchResultsData(data) { try { if (typeof data === 'string') { data = JSON.parse(data); } let parsedItems = []; // 处理主要数据结构 if (data && data['1'] && Array.isArray(data['1'])) { data['1'].forEach(item => { // 提取需要的字段并去重 const newItem = { '1': item['1'] || '', // 资料库 id '12': item['12'] || '', // 广告主名 '14': item['14'] || '' // 来源 }; // 检查是否已存在相同的资料库 id const exists = parsedItems.some(existingItem => existingItem['1'] === newItem['1']); if (!exists && newItem['1']) { parsedItems.push(newItem); } }); } // 更新搜索结果数据 searchResultsData = parsedItems; isFetching = false; updateButtonText(); // 如果面板打开中,更新表格 if (isPanelOpen && currentTab === 'results') { renderResultsTable(); } } catch (err) { console.error('处理搜索结果数据出错:', err); isFetching = false; updateButtonText(); } } // 拦截 XMLHttpRequest 请求 const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; let lastRecommendationsRequest = null; let lastResultsRequest = null; XMLHttpRequest.prototype.open = function(method, url) { this._url = url; return originalOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function() { if (this._url && this._url.includes('/SearchService/SearchSuggestions')) { lastRecommendationsRequest = this; isFetching = true; updateButtonText(); this.addEventListener('load', function() { try { // 只处理最后一个请求 if (this === lastRecommendationsRequest) { processResponseData(this.responseText); } } catch (err) { console.error('处理XMLHttpRequest响应失败:', err); isFetching = false; updateButtonText(); } }); } else if (this._url && this._url.includes('/SearchService/SearchCreatives')) { lastResultsRequest = this; isFetching = true; updateButtonText(); this.addEventListener('load', function() { try { // 只处理最后一个请求 if (this === lastResultsRequest) { processSearchResultsData(this.responseText); } } catch (err) { console.error('处理搜索结果响应失败:', err); isFetching = false; updateButtonText(); } }); } return originalSend.apply(this, arguments); }; })();