SEO Data Exporter for SEM Analytics

Export organic search rankings and SEO data in multiple formats (CSV, JSON, Excel)

Size

12.9 KB

Version

1.0.1

Created

Feb 24, 2026

Updated

about 2 months ago

1// ==UserScript==
2// @name		SEO Data Exporter for SEM Analytics
3// @description		Export organic search rankings and SEO data in multiple formats (CSV, JSON, Excel)
4// @version		1.0.1
5// @match		https://*.sem.3ue.com/*
6// @icon		https://ggg-zh-g--ggg---mediasem.3ue.com/__static__/favicon.f8cd638f087a.ico
7// @require		https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
8// ==/UserScript==
9(function() {
10    'use strict';
11
12    console.log('SEO Data Exporter extension loaded');
13
14    // Debounce function to prevent excessive calls
15    function debounce(func, wait) {
16        let timeout;
17        return function executedFunction(...args) {
18            const later = () => {
19                clearTimeout(timeout);
20                func(...args);
21            };
22            clearTimeout(timeout);
23            timeout = setTimeout(later, wait);
24        };
25    }
26
27    // Extract data from the table
28    function extractTableData() {
29        console.log('Extracting table data...');
30        const data = [];
31        
32        // Find all table rows with data
33        const rows = document.querySelectorAll('table[data-ui-name="Table"] tbody tr[data-ui-name="Table.Row"]');
34        console.log(`Found ${rows.length} data rows`);
35        
36        if (rows.length === 0) {
37            console.error('No data rows found in table');
38            return data;
39        }
40
41        rows.forEach((row, index) => {
42            try {
43                const cells = row.querySelectorAll('td[data-ui-name="Table.Cell"]');
44                
45                // Extract keyword
46                const keywordCell = cells[0]?.querySelector('a[data-ui-name="Link"]');
47                const keyword = keywordCell?.textContent?.trim() || '';
48                
49                // Extract position
50                const positionCell = cells[1]?.querySelector('span[data-ui-name="Text"]');
51                const position = positionCell?.textContent?.trim() || '';
52                
53                // Extract search volume
54                const volumeCell = cells[2]?.querySelector('span[data-ui-name="Text"]');
55                const searchVolume = volumeCell?.textContent?.trim() || '';
56                
57                // Extract KD (Keyword Difficulty)
58                const kdCell = cells[3]?.querySelector('span[data-ui-name="Text"]');
59                const keywordDifficulty = kdCell?.textContent?.trim() || '';
60                
61                // Extract CPC
62                const cpcCell = cells[4]?.querySelector('span[data-ui-name="Text"]');
63                const cpc = cpcCell?.textContent?.trim() || '';
64                
65                // Extract URL
66                const urlCell = cells[5]?.querySelector('a[data-ui-name="Link"]');
67                const url = urlCell?.getAttribute('href') || '';
68                
69                // Extract traffic
70                const trafficCell = cells[6]?.querySelector('span[data-ui-name="Text"]');
71                const traffic = trafficCell?.textContent?.trim() || '';
72                
73                // Extract traffic percentage
74                const trafficPercentCell = cells[7]?.querySelector('span[data-ui-name="Text"]');
75                const trafficPercent = trafficPercentCell?.textContent?.trim() || '';
76
77                const rowData = {
78                    keyword,
79                    position,
80                    searchVolume,
81                    keywordDifficulty,
82                    cpc,
83                    url,
84                    traffic,
85                    trafficPercent
86                };
87                
88                data.push(rowData);
89                console.log(`Row ${index + 1}:`, rowData);
90            } catch (error) {
91                console.error(`Error extracting row ${index}:`, error);
92            }
93        });
94        
95        console.log(`Extracted ${data.length} rows of data`);
96        return data;
97    }
98
99    // Export to CSV
100    function exportToCSV(data) {
101        console.log('Exporting to CSV...');
102        if (data.length === 0) {
103            alert('No data to export');
104            return;
105        }
106
107        const headers = ['Keyword', 'Position', 'Search Volume', 'Keyword Difficulty', 'CPC', 'URL', 'Traffic', 'Traffic %'];
108        const csvRows = [headers.join(',')];
109        
110        data.forEach(row => {
111            const values = [
112                `"${row.keyword.replace(/"/g, '""')}"`,
113                row.position,
114                row.searchVolume,
115                row.keywordDifficulty,
116                row.cpc,
117                `"${row.url.replace(/"/g, '""')}"`,
118                row.traffic,
119                row.trafficPercent
120            ];
121            csvRows.push(values.join(','));
122        });
123        
124        const csvContent = csvRows.join('\n');
125        const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
126        const link = document.createElement('a');
127        const url = URL.createObjectURL(blob);
128        
129        link.setAttribute('href', url);
130        link.setAttribute('download', `seo_rankings_${new Date().toISOString().split('T')[0]}.csv`);
131        link.style.visibility = 'hidden';
132        document.body.appendChild(link);
133        link.click();
134        document.body.removeChild(link);
135        
136        console.log('CSV export completed');
137    }
138
139    // Export to JSON
140    function exportToJSON(data) {
141        console.log('Exporting to JSON...');
142        if (data.length === 0) {
143            alert('No data to export');
144            return;
145        }
146
147        const jsonContent = JSON.stringify(data, null, 2);
148        const blob = new Blob([jsonContent], { type: 'application/json' });
149        const link = document.createElement('a');
150        const url = URL.createObjectURL(blob);
151        
152        link.setAttribute('href', url);
153        link.setAttribute('download', `seo_rankings_${new Date().toISOString().split('T')[0]}.json`);
154        link.style.visibility = 'hidden';
155        document.body.appendChild(link);
156        link.click();
157        document.body.removeChild(link);
158        
159        console.log('JSON export completed');
160    }
161
162    // Export to Excel
163    function exportToExcel(data) {
164        console.log('Exporting to Excel...');
165        if (data.length === 0) {
166            alert('No data to export');
167            return;
168        }
169
170        // Prepare data for Excel
171        const worksheetData = [
172            ['Keyword', 'Position', 'Search Volume', 'Keyword Difficulty', 'CPC', 'URL', 'Traffic', 'Traffic %']
173        ];
174        
175        data.forEach(row => {
176            worksheetData.push([
177                row.keyword,
178                row.position,
179                row.searchVolume,
180                row.keywordDifficulty,
181                row.cpc,
182                row.url,
183                row.traffic,
184                row.trafficPercent
185            ]);
186        });
187        
188        // Create workbook and worksheet
189        const wb = XLSX.utils.book_new();
190        const ws = XLSX.utils.aoa_to_sheet(worksheetData);
191        
192        // Set column widths
193        ws['!cols'] = [
194            { wch: 30 }, // Keyword
195            { wch: 10 }, // Position
196            { wch: 15 }, // Search Volume
197            { wch: 18 }, // Keyword Difficulty
198            { wch: 10 }, // CPC
199            { wch: 50 }, // URL
200            { wch: 10 }, // Traffic
201            { wch: 12 }  // Traffic %
202        ];
203        
204        XLSX.utils.book_append_sheet(wb, ws, 'SEO Rankings');
205        XLSX.writeFile(wb, `seo_rankings_${new Date().toISOString().split('T')[0]}.xlsx`);
206        
207        console.log('Excel export completed');
208    }
209
210    // Create export button UI
211    function createExportButton() {
212        console.log('Creating export button...');
213        
214        // Check if button already exists
215        if (document.getElementById('custom-seo-exporter')) {
216            console.log('Export button already exists');
217            return;
218        }
219
220        // Find the export button container
221        const exportContainer = document.querySelector('div[data-at="export"]');
222        if (!exportContainer) {
223            console.log('Export container not found, will retry...');
224            return;
225        }
226
227        // Create custom export button container
228        const customExportDiv = document.createElement('div');
229        customExportDiv.id = 'custom-seo-exporter';
230        customExportDiv.style.cssText = 'display: inline-block; margin-left: 8px; position: relative;';
231        
232        // Create main button
233        const mainButton = document.createElement('button');
234        mainButton.textContent = '📊 Export Data';
235        mainButton.style.cssText = `
236            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
237            color: white;
238            border: none;
239            padding: 8px 16px;
240            border-radius: 6px;
241            cursor: pointer;
242            font-size: 14px;
243            font-weight: 500;
244            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
245            transition: all 0.3s ease;
246        `;
247        
248        // Create dropdown menu
249        const dropdown = document.createElement('div');
250        dropdown.style.cssText = `
251            display: none;
252            position: absolute;
253            top: 100%;
254            right: 0;
255            margin-top: 4px;
256            background: white;
257            border: 1px solid #e0e0e0;
258            border-radius: 6px;
259            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
260            z-index: 10000;
261            min-width: 180px;
262        `;
263        
264        // Create export options
265        const exportOptions = [
266            { label: '📄 Export as CSV', handler: () => exportToCSV(extractTableData()) },
267            { label: '📋 Export as JSON', handler: () => exportToJSON(extractTableData()) },
268            { label: '📊 Export as Excel', handler: () => exportToExcel(extractTableData()) }
269        ];
270        
271        exportOptions.forEach((option, index) => {
272            const optionButton = document.createElement('button');
273            optionButton.textContent = option.label;
274            optionButton.style.cssText = `
275                display: block;
276                width: 100%;
277                padding: 10px 16px;
278                border: none;
279                background: white;
280                text-align: left;
281                cursor: pointer;
282                font-size: 14px;
283                color: #333;
284                transition: background 0.2s ease;
285                ${index === 0 ? 'border-radius: 6px 6px 0 0;' : ''}
286                ${index === exportOptions.length - 1 ? 'border-radius: 0 0 6px 6px;' : ''}
287            `;
288            
289            optionButton.addEventListener('mouseenter', () => {
290                optionButton.style.background = '#f5f5f5';
291            });
292            
293            optionButton.addEventListener('mouseleave', () => {
294                optionButton.style.background = 'white';
295            });
296            
297            optionButton.addEventListener('click', () => {
298                option.handler();
299                dropdown.style.display = 'none';
300            });
301            
302            dropdown.appendChild(optionButton);
303        });
304        
305        // Toggle dropdown
306        mainButton.addEventListener('click', (e) => {
307            e.stopPropagation();
308            dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
309        });
310        
311        // Close dropdown when clicking outside
312        document.addEventListener('click', () => {
313            dropdown.style.display = 'none';
314        });
315        
316        // Hover effects for main button
317        mainButton.addEventListener('mouseenter', () => {
318            mainButton.style.transform = 'translateY(-1px)';
319            mainButton.style.boxShadow = '0 4px 8px rgba(0,0,0,0.15)';
320        });
321        
322        mainButton.addEventListener('mouseleave', () => {
323            mainButton.style.transform = 'translateY(0)';
324            mainButton.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
325        });
326        
327        customExportDiv.appendChild(mainButton);
328        customExportDiv.appendChild(dropdown);
329        
330        // Insert after the existing export button
331        exportContainer.parentNode.insertBefore(customExportDiv, exportContainer.nextSibling);
332        
333        console.log('Export button created successfully');
334    }
335
336    // Initialize the extension
337    function init() {
338        console.log('Initializing SEO Data Exporter...');
339        
340        // Wait for the page to load
341        if (document.readyState === 'loading') {
342            document.addEventListener('DOMContentLoaded', init);
343            return;
344        }
345        
346        // Try to create the button immediately
347        createExportButton();
348        
349        // Use MutationObserver to detect when the table is loaded
350        const debouncedCreateButton = debounce(createExportButton, 500);
351        
352        const observer = new MutationObserver(debouncedCreateButton);
353        observer.observe(document.body, {
354            childList: true,
355            subtree: true
356        });
357        
358        // Also try again after a delay
359        setTimeout(createExportButton, 2000);
360        setTimeout(createExportButton, 5000);
361    }
362
363    // Start the extension
364    init();
365})();
SEO Data Exporter for SEM Analytics | Robomonkey