Анализирует показатели продуктов (показы, клики, конверсии, заказы, ДРР) с помощью AI и дает рекомендации по улучшению
Size
31.4 KB
Version
1.0.1
Created
Dec 3, 2025
Updated
9 days ago
1// ==UserScript==
2// @name AI Аналитика продуктов Ozon
3// @description Анализирует показатели продуктов (показы, клики, конверсии, заказы, ДРР) с помощью AI и дает рекомендации по улучшению
4// @version 1.0.1
5// @match https://*.seller.ozon.ru/*
6// @icon https://st.ozone.ru/s3/seller-ui-static/icon/favicon32.png
7// @grant GM.xmlhttpRequest
8// ==/UserScript==
9(function() {
10 'use strict';
11
12 console.log('AI Аналитика продуктов Ozon запущена');
13
14 // Функция для парсинга значения и дельты из текста ячейки
15 function parseValueAndDelta(text) {
16 if (!text || text === '—' || text.trim() === '') {
17 return { value: null, delta: null };
18 }
19
20 // Убираем пробелы и символы валюты для парсинга
21 const cleanText = text.trim();
22
23 // Ищем число (может быть с пробелами как разделителями тысяч)
24 const valueMatch = cleanText.match(/^([\d\s]+(?:[.,]\d+)?)/);
25 const value = valueMatch ? parseFloat(valueMatch[1].replace(/\s/g, '').replace(',', '.')) : null;
26
27 // Ищем дельту (процент со знаком + или -)
28 const deltaMatch = cleanText.match(/([+-]?\d+(?:[.,]\d+)?)%/);
29 const delta = deltaMatch ? parseFloat(deltaMatch[1].replace(',', '.')) : null;
30
31 return { value, delta };
32 }
33
34 // Функция для извлечения данных о продуктах из таблицы
35 function extractProductData() {
36 console.log('Начинаем извлечение данных о продуктах...');
37 const products = [];
38
39 // Находим все строки таблицы с продуктами
40 const rows = document.querySelectorAll('table.ct590-a tbody tr.ct590-c0');
41 console.log(`Найдено строк в таблице: ${rows.length}`);
42
43 rows.forEach((row, index) => {
44 try {
45 const cells = row.querySelectorAll('td');
46 if (cells.length === 0) return;
47
48 // Извлекаем название продукта и артикул
49 const productCell = cells[0];
50 const productLink = productCell.querySelector('a[href*="ozon.ru/product"]');
51 const productName = productLink ? productLink.getAttribute('title') || productLink.textContent.trim() : 'Неизвестный товар';
52 const articleElement = productCell.querySelector('.styles_productCaption_7MqtH');
53 const article = articleElement ? articleElement.textContent.trim() : '';
54
55 // Извлекаем метрики согласно структуре таблицы
56 // Индексы ячеек (из анализа DOM):
57 // 2: Заказано на сумму
58 // 4: Позиция в поиске
59 // 5: Показы всего
60 // 6: Уникальные посетители всего
61 // 8: Показы в поиске и каталоге
62 // 12: Конверсия из показа в заказ
63 // 13: Показы в поиске и каталоге (добавления)
64 // 15: Конверсия из поиска в корзину
65 // 18: Посещения карточки
66 // 20: Уникальные посетители карточки
67 // 28: Средняя цена
68 // 32: Общая ДРР
69
70 const orderSum = parseValueAndDelta(cells[2]?.textContent || '');
71 const position = parseValueAndDelta(cells[4]?.textContent || '');
72 const impressionsTotal = parseValueAndDelta(cells[5]?.textContent || '');
73 const visitorsTotal = parseValueAndDelta(cells[6]?.textContent || '');
74 const conversionShowToOrder = parseValueAndDelta(cells[12]?.textContent || '');
75 const ordersCount = parseValueAndDelta(cells[13]?.textContent || '');
76 const conversionSearchToCart = parseValueAndDelta(cells[15]?.textContent || '');
77 const cardVisits = parseValueAndDelta(cells[18]?.textContent || '');
78 const cardVisitorsUnique = parseValueAndDelta(cells[20]?.textContent || '');
79 const avgPrice = parseValueAndDelta(cells[28]?.textContent || '');
80 const drr = parseValueAndDelta(cells[32]?.textContent || '');
81
82 const product = {
83 name: productName,
84 article: article,
85 orderSum: orderSum.value,
86 orderSumDelta: orderSum.delta,
87 position: position.value,
88 positionDelta: position.delta,
89 impressionsTotal: impressionsTotal.value,
90 impressionsTotalDelta: impressionsTotal.delta,
91 visitorsTotal: visitorsTotal.value,
92 visitorsTotalDelta: visitorsTotal.delta,
93 conversionShowToOrder: conversionShowToOrder.value,
94 conversionShowToOrderDelta: conversionShowToOrder.delta,
95 ordersCount: ordersCount.value,
96 ordersCountDelta: ordersCount.delta,
97 conversionSearchToCart: conversionSearchToCart.value,
98 conversionSearchToCartDelta: conversionSearchToCart.delta,
99 cardVisits: cardVisits.value,
100 cardVisitsDelta: cardVisits.delta,
101 cardVisitorsUnique: cardVisitorsUnique.value,
102 cardVisitorsUniqueDelta: cardVisitorsUnique.delta,
103 avgPrice: avgPrice.value,
104 avgPriceDelta: avgPrice.delta,
105 drr: drr.value,
106 drrDelta: drr.delta
107 };
108
109 console.log(`Продукт ${index + 1}:`, product);
110 products.push(product);
111
112 } catch (error) {
113 console.error(`Ошибка при обработке строки ${index}:`, error);
114 }
115 });
116
117 console.log(`Всего извлечено продуктов: ${products.length}`);
118 return products;
119 }
120
121 // Функция для фильтрации продуктов со значительными изменениями
122 function filterSignificantChanges(products) {
123 console.log('Фильтруем продукты со значительными изменениями...');
124
125 const significantProducts = products.filter(product => {
126 // Критерии значительных изменений:
127
128 // 1. Сильное падение выручки (более -20%)
129 const revenueDropped = product.orderSumDelta !== null && product.orderSumDelta <= -20;
130
131 // 2. Сильный рост выручки (более +20%)
132 const revenueGrew = product.orderSumDelta !== null && product.orderSumDelta >= 20;
133
134 // 3. Сильный рост ДРР (более +10% или если ДРР стал выше 25%)
135 const drrGrew = (product.drrDelta !== null && product.drrDelta >= 10) ||
136 (product.drr !== null && product.drr >= 25);
137
138 // 4. Сильное падение конверсии (более -15%)
139 const conversionDropped = product.conversionShowToOrderDelta !== null &&
140 product.conversionShowToOrderDelta <= -15;
141
142 // 5. Сильное падение показов (более -30%)
143 const impressionsDropped = product.impressionsTotalDelta !== null &&
144 product.impressionsTotalDelta <= -30;
145
146 // 6. Сильное падение заказов (более -25%)
147 const ordersDropped = product.ordersCountDelta !== null &&
148 product.ordersCountDelta <= -25;
149
150 return revenueDropped || revenueGrew || drrGrew || conversionDropped ||
151 impressionsDropped || ordersDropped;
152 });
153
154 console.log(`Найдено продуктов со значительными изменениями: ${significantProducts.length}`);
155 return significantProducts;
156 }
157
158 // Функция для анализа продуктов с помощью AI
159 async function analyzeProductsWithAI(products) {
160 console.log('Начинаем AI-анализ продуктов...');
161
162 if (products.length === 0) {
163 return { error: 'Нет продуктов со значительными изменениями для анализа' };
164 }
165
166 // Сортируем продукты по абсолютному значению изменения выручки
167 const sortedProducts = [...products].sort((a, b) => {
168 const deltaA = Math.abs(a.orderSumDelta || 0);
169 const deltaB = Math.abs(b.orderSumDelta || 0);
170 return deltaB - deltaA;
171 });
172
173 // Берем топ-15 продуктов для анализа
174 const topProducts = sortedProducts.slice(0, 15);
175
176 const prompt = `Ты эксперт по e-commerce аналитике на маркетплейсе Ozon. Проанализируй следующие данные о продуктах, которые показали ЗНАЧИТЕЛЬНЫЕ ИЗМЕНЕНИЯ по сравнению с предыдущим периодом.
177
178Данные о продуктах (отсортированы по величине изменения выручки):
179${JSON.stringify(topProducts, null, 2)}
180
181Метрики и их дельты (изменения в %):
182- orderSum: сумма заказов (₽)
183- orderSumDelta: изменение суммы заказов (%)
184- impressionsTotal: показы всего
185- impressionsTotalDelta: изменение показов (%)
186- visitorsTotal: уникальные посетители
187- visitorsTotalDelta: изменение посетителей (%)
188- conversionShowToOrder: конверсия из показа в заказ (%)
189- conversionShowToOrderDelta: изменение конверсии (%)
190- ordersCount: количество заказов
191- ordersCountDelta: изменение количества заказов (%)
192- cardVisits: посещения карточки товара
193- cardVisitsDelta: изменение посещений (%)
194- avgPrice: средняя цена (₽)
195- avgPriceDelta: изменение цены (%)
196- drr: ДРР - доля рекламных расходов (%)
197- drrDelta: изменение ДРР (%)
198
199ВАЖНО! Фокусируйся на ИЗМЕНЕНИЯХ (дельтах):
200
2011. Если выручка УПАЛА (orderSumDelta отрицательная):
202 - Найди причину: упали показы? упала конверсия? выросла цена? вырос ДРР?
203 - Дай конкретные рекомендации как вернуть продажи
204
2052. Если выручка ВЫРОСЛА (orderSumDelta положительная):
206 - Найди причину роста: выросли показы? улучшилась конверсия? снизилась цена?
207 - Дай рекомендации как закрепить и усилить рост
208
2093. Если ДРР сильно ВЫРОС (drrDelta положительная или drr > 25%):
210 - Это КРИТИЧНО! Реклама стала неэффективной
211 - Объясни почему это произошло и как снизить ДРР
212
2134. Если конверсия УПАЛА:
214 - Проблемы с карточкой товара, ценой или конкурентами
215 - Дай рекомендации по улучшению
216
217Для каждого продукта укажи:
218- Название продукта (кратко)
219- Что произошло (рост/падение и почему)
220- Конкретные действия для исправления или усиления
221- Приоритет (критический/высокий/средний)`;
222
223 try {
224 const response = await RM.aiCall(prompt, {
225 type: "json_schema",
226 json_schema: {
227 name: "product_changes_analysis",
228 schema: {
229 type: "object",
230 properties: {
231 summary: {
232 type: "string",
233 description: "Общий вывод по всем изменениям"
234 },
235 products: {
236 type: "array",
237 items: {
238 type: "object",
239 properties: {
240 name: { type: "string" },
241 change: {
242 type: "string",
243 description: "Что произошло с товаром"
244 },
245 reasons: {
246 type: "array",
247 items: { type: "string" },
248 description: "Причины изменений"
249 },
250 actions: {
251 type: "array",
252 items: { type: "string" },
253 description: "Конкретные действия"
254 },
255 priority: {
256 type: "string",
257 enum: ["критический", "высокий", "средний"]
258 }
259 },
260 required: ["name", "change", "reasons", "actions", "priority"]
261 }
262 }
263 },
264 required: ["summary", "products"]
265 }
266 }
267 });
268
269 console.log('AI-анализ завершен:', response);
270 return response;
271
272 } catch (error) {
273 console.error('Ошибка при AI-анализе:', error);
274 return { error: 'Ошибка при анализе: ' + error.message };
275 }
276 }
277
278 // Функция для отображения результатов анализа
279 function displayAnalysisResults(analysis) {
280 console.log('Отображаем результаты анализа...');
281
282 // Удаляем предыдущую панель, если есть
283 const existingPanel = document.getElementById('ai-analysis-panel');
284 if (existingPanel) {
285 existingPanel.remove();
286 }
287
288 // Создаем панель с результатами
289 const panel = document.createElement('div');
290 panel.id = 'ai-analysis-panel';
291 panel.style.cssText = `
292 position: fixed;
293 top: 80px;
294 right: 20px;
295 width: 500px;
296 max-height: 85vh;
297 background: #ffffff;
298 border: 2px solid #005bff;
299 border-radius: 12px;
300 box-shadow: 0 8px 32px rgba(0, 91, 255, 0.2);
301 z-index: 10000;
302 overflow: hidden;
303 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
304 `;
305
306 // Заголовок панели
307 const header = document.createElement('div');
308 header.style.cssText = `
309 background: linear-gradient(135deg, #005bff 0%, #0041b8 100%);
310 color: white;
311 padding: 16px 20px;
312 font-weight: 600;
313 font-size: 16px;
314 display: flex;
315 justify-content: space-between;
316 align-items: center;
317 `;
318 header.innerHTML = `
319 <span>🤖 AI Анализ изменений</span>
320 <button id="close-analysis-panel" style="
321 background: rgba(255, 255, 255, 0.2);
322 border: none;
323 color: white;
324 width: 28px;
325 height: 28px;
326 border-radius: 6px;
327 cursor: pointer;
328 font-size: 18px;
329 display: flex;
330 align-items: center;
331 justify-content: center;
332 transition: background 0.2s;
333 ">×</button>
334 `;
335
336 // Контент панели
337 const content = document.createElement('div');
338 content.style.cssText = `
339 padding: 20px;
340 max-height: calc(85vh - 60px);
341 overflow-y: auto;
342 `;
343
344 if (analysis.error) {
345 content.innerHTML = `
346 <div style="color: #d32f2f; padding: 16px; background: #ffebee; border-radius: 8px; text-align: center;">
347 <strong>Внимание:</strong> ${analysis.error}
348 </div>
349 `;
350 } else {
351 // Общий вывод
352 content.innerHTML = `
353 <div style="background: #f5f7fa; padding: 16px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #005bff;">
354 <div style="font-weight: 600; color: #1a1a1a; margin-bottom: 8px; font-size: 14px;">📊 Общий вывод</div>
355 <div style="color: #4a4a4a; font-size: 13px; line-height: 1.6;">${analysis.summary}</div>
356 </div>
357 `;
358
359 // Продукты
360 analysis.products.forEach((product) => {
361 const priorityColors = {
362 'критический': { bg: '#ffebee', border: '#d32f2f', text: '#d32f2f', icon: '🔴' },
363 'высокий': { bg: '#fff3e0', border: '#f57c00', text: '#f57c00', icon: '🟡' },
364 'средний': { bg: '#e3f2fd', border: '#1976d2', text: '#1976d2', icon: '🔵' }
365 };
366
367 const colors = priorityColors[product.priority] || priorityColors['средний'];
368
369 const productCard = document.createElement('div');
370 productCard.style.cssText = `
371 background: white;
372 border: 2px solid ${colors.border};
373 border-radius: 8px;
374 padding: 16px;
375 margin-bottom: 16px;
376 transition: box-shadow 0.2s;
377 `;
378 productCard.onmouseenter = () => {
379 productCard.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
380 };
381 productCard.onmouseleave = () => {
382 productCard.style.boxShadow = 'none';
383 };
384
385 productCard.innerHTML = `
386 <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px;">
387 <div style="font-weight: 600; color: #1a1a1a; font-size: 13px; flex: 1; line-height: 1.4;">
388 ${product.name.length > 70 ? product.name.substring(0, 70) + '...' : product.name}
389 </div>
390 <div style="
391 background: ${colors.bg};
392 color: ${colors.text};
393 padding: 4px 10px;
394 border-radius: 12px;
395 font-size: 11px;
396 font-weight: 600;
397 white-space: nowrap;
398 margin-left: 8px;
399 border: 1px solid ${colors.border};
400 ">
401 ${colors.icon} ${product.priority}
402 </div>
403 </div>
404
405 <div style="background: ${colors.bg}; padding: 12px; border-radius: 6px; margin-bottom: 12px; border-left: 3px solid ${colors.border};">
406 <div style="font-weight: 600; color: ${colors.text}; font-size: 12px; margin-bottom: 4px;">📈 Что произошло:</div>
407 <div style="color: #2c2c2c; font-size: 12px; line-height: 1.5;">${product.change}</div>
408 </div>
409
410 <div style="margin-bottom: 12px;">
411 <div style="font-weight: 600; color: #d32f2f; font-size: 12px; margin-bottom: 6px;">🔍 Причины:</div>
412 <ul style="margin: 0; padding-left: 20px; color: #4a4a4a; font-size: 12px; line-height: 1.6;">
413 ${product.reasons.map(r => `<li>${r}</li>`).join('')}
414 </ul>
415 </div>
416
417 <div>
418 <div style="font-weight: 600; color: #388e3c; font-size: 12px; margin-bottom: 6px;">✅ Действия:</div>
419 <ul style="margin: 0; padding-left: 20px; color: #4a4a4a; font-size: 12px; line-height: 1.6;">
420 ${product.actions.map(a => `<li>${a}</li>`).join('')}
421 </ul>
422 </div>
423 `;
424
425 content.appendChild(productCard);
426 });
427 }
428
429 panel.appendChild(header);
430 panel.appendChild(content);
431 document.body.appendChild(panel);
432
433 // Обработчик закрытия панели
434 document.getElementById('close-analysis-panel').addEventListener('click', () => {
435 panel.remove();
436 });
437
438 console.log('Панель с результатами отображена');
439 }
440
441 // Функция для создания кнопки AI-анализа
442 function createAnalysisButton() {
443 console.log('Создаем кнопку AI-анализа...');
444
445 // Проверяем, не создана ли уже кнопка
446 if (document.getElementById('ai-analysis-button')) {
447 console.log('Кнопка уже существует');
448 return;
449 }
450
451 // Ищем место для размещения кнопки
452 const targetContainer = document.querySelector('.styles_rightColumn_1dd5d') ||
453 document.querySelector('.styles_dropdown_Fhu9J') ||
454 document.querySelector('div[class*="rightColumn"]');
455
456 if (!targetContainer) {
457 console.log('Контейнер для кнопки не найден, создаем фиксированную кнопку');
458 createFloatingButton();
459 return;
460 }
461
462 // Создаем кнопку
463 const button = document.createElement('button');
464 button.id = 'ai-analysis-button';
465 button.type = 'button';
466 button.style.cssText = `
467 background: linear-gradient(135deg, #005bff 0%, #0041b8 100%);
468 color: white;
469 border: none;
470 padding: 10px 20px;
471 border-radius: 8px;
472 font-size: 14px;
473 font-weight: 600;
474 cursor: pointer;
475 display: flex;
476 align-items: center;
477 gap: 8px;
478 transition: all 0.3s;
479 box-shadow: 0 2px 8px rgba(0, 91, 255, 0.3);
480 margin-left: 12px;
481 `;
482 button.innerHTML = '🤖 AI Анализ изменений';
483
484 button.addEventListener('mouseenter', () => {
485 button.style.transform = 'translateY(-2px)';
486 button.style.boxShadow = '0 4px 12px rgba(0, 91, 255, 0.4)';
487 });
488
489 button.addEventListener('mouseleave', () => {
490 button.style.transform = 'translateY(0)';
491 button.style.boxShadow = '0 2px 8px rgba(0, 91, 255, 0.3)';
492 });
493
494 button.addEventListener('click', async () => {
495 console.log('Кнопка AI-анализа нажата');
496
497 // Показываем индикатор загрузки
498 button.disabled = true;
499 button.innerHTML = '⏳ Анализируем изменения...';
500 button.style.background = '#9e9e9e';
501
502 try {
503 // Извлекаем данные
504 const allProducts = extractProductData();
505
506 if (allProducts.length === 0) {
507 alert('Не удалось найти данные о продуктах. Убедитесь, что вы находитесь на странице аналитики с таблицей продуктов.');
508 return;
509 }
510
511 // Фильтруем продукты со значительными изменениями
512 const significantProducts = filterSignificantChanges(allProducts);
513
514 if (significantProducts.length === 0) {
515 alert('Не найдено продуктов со значительными изменениями. Все показатели стабильны.');
516 return;
517 }
518
519 console.log(`Анализируем ${significantProducts.length} продуктов со значительными изменениями`);
520
521 // Анализируем с помощью AI
522 const analysis = await analyzeProductsWithAI(significantProducts);
523
524 // Отображаем результаты
525 displayAnalysisResults(analysis);
526
527 } catch (error) {
528 console.error('Ошибка при анализе:', error);
529 alert('Произошла ошибка при анализе: ' + error.message);
530 } finally {
531 // Восстанавливаем кнопку
532 button.disabled = false;
533 button.innerHTML = '🤖 AI Анализ изменений';
534 button.style.background = 'linear-gradient(135deg, #005bff 0%, #0041b8 100%)';
535 }
536 });
537
538 // Вставляем кнопку
539 targetContainer.appendChild(button);
540 console.log('Кнопка AI-анализа создана');
541 }
542
543 // Функция для создания плавающей кнопки
544 function createFloatingButton() {
545 const button = document.createElement('button');
546 button.id = 'ai-analysis-button';
547 button.type = 'button';
548 button.style.cssText = `
549 position: fixed;
550 top: 100px;
551 right: 20px;
552 background: linear-gradient(135deg, #005bff 0%, #0041b8 100%);
553 color: white;
554 border: none;
555 padding: 12px 24px;
556 border-radius: 8px;
557 font-size: 14px;
558 font-weight: 600;
559 cursor: pointer;
560 z-index: 9999;
561 box-shadow: 0 4px 16px rgba(0, 91, 255, 0.4);
562 transition: all 0.3s;
563 `;
564 button.innerHTML = '🤖 AI Анализ';
565
566 button.addEventListener('mouseenter', () => {
567 button.style.transform = 'scale(1.05)';
568 });
569
570 button.addEventListener('mouseleave', () => {
571 button.style.transform = 'scale(1)';
572 });
573
574 button.addEventListener('click', async () => {
575 button.disabled = true;
576 button.innerHTML = '⏳ Анализируем...';
577 button.style.background = '#9e9e9e';
578
579 try {
580 const allProducts = extractProductData();
581 if (allProducts.length === 0) {
582 alert('Не удалось найти данные о продуктах.');
583 return;
584 }
585
586 const significantProducts = filterSignificantChanges(allProducts);
587 if (significantProducts.length === 0) {
588 alert('Не найдено продуктов со значительными изменениями.');
589 return;
590 }
591
592 const analysis = await analyzeProductsWithAI(significantProducts);
593 displayAnalysisResults(analysis);
594
595 } catch (error) {
596 console.error('Ошибка:', error);
597 alert('Ошибка при анализе: ' + error.message);
598 } finally {
599 button.disabled = false;
600 button.innerHTML = '🤖 AI Анализ';
601 button.style.background = 'linear-gradient(135deg, #005bff 0%, #0041b8 100%)';
602 }
603 });
604
605 document.body.appendChild(button);
606 }
607
608 // Функция инициализации
609 function init() {
610 console.log('Инициализация AI Аналитики продуктов Ozon...');
611
612 // Проверяем, что мы на странице аналитики
613 if (window.location.href.includes('seller.ozon.ru/app/analytics')) {
614 console.log('Страница аналитики обнаружена');
615
616 // Ждем загрузки таблицы
617 const checkTable = setInterval(() => {
618 const table = document.querySelector('table.ct590-a');
619 if (table) {
620 console.log('Таблица найдена, создаем кнопку');
621 clearInterval(checkTable);
622 setTimeout(createAnalysisButton, 1000);
623 }
624 }, 1000);
625
626 // Останавливаем проверку через 30 секунд
627 setTimeout(() => clearInterval(checkTable), 30000);
628 }
629 }
630
631 // Запускаем инициализацию
632 if (document.readyState === 'loading') {
633 document.addEventListener('DOMContentLoaded', init);
634 } else {
635 init();
636 }
637
638 // Наблюдаем за изменениями в DOM для поддержки SPA навигации
639 const observer = new MutationObserver(() => {
640 if (window.location.href.includes('seller.ozon.ru/app/analytics') &&
641 !document.getElementById('ai-analysis-button')) {
642 const table = document.querySelector('table.ct590-a');
643 if (table) {
644 createAnalysisButton();
645 }
646 }
647 });
648
649 observer.observe(document.body, {
650 childList: true,
651 subtree: true
652 });
653
654})();