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