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

25 days 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})();