Генератор SEO-описаний для товаров на Wildberries с анализом ключевых слов
Size
49.0 KB
Version
2.3.1
Created
Jan 13, 2026
Updated
about 1 month ago
1// ==UserScript==
2// @name Wildberries Description Generator
3// @description Генератор SEO-описаний для товаров на Wildberries с анализом ключевых слов
4// @version 2.3.1
5// @match https://*.seller.wildberries.ru/*
6// @icon https://seller.wildberries.ru/favicon.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Wildberries Description Generator: Расширение запущено');
12
13 // Добавляем стили для модального окна
14 TM_addStyle(`
15 .wb-desc-modal {
16 position: fixed;
17 top: 0;
18 left: 0;
19 width: 100%;
20 height: 100%;
21 background: rgba(0, 0, 0, 0.5);
22 display: flex;
23 align-items: center;
24 justify-content: center;
25 z-index: 10000;
26 }
27
28 .wb-desc-modal-content {
29 background: white;
30 border-radius: 12px;
31 padding: 24px;
32 max-width: 600px;
33 width: 90%;
34 max-height: 80vh;
35 overflow-y: auto;
36 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
37 }
38
39 .wb-desc-modal-header {
40 font-size: 20px;
41 font-weight: 600;
42 margin-bottom: 20px;
43 color: #001a34;
44 }
45
46 .wb-desc-input-group {
47 margin-bottom: 16px;
48 }
49
50 .wb-desc-label {
51 display: block;
52 margin-bottom: 8px;
53 font-weight: 500;
54 color: #001a34;
55 }
56
57 .wb-desc-textarea {
58 width: 100%;
59 min-height: 100px;
60 padding: 12px;
61 border: 1px solid #d1d5db;
62 border-radius: 8px;
63 font-size: 14px;
64 font-family: inherit;
65 resize: vertical;
66 }
67
68 .wb-desc-textarea:focus {
69 outline: none;
70 border-color: #9333ea;
71 }
72
73 .wb-desc-result {
74 background: #f3f4f6;
75 padding: 16px;
76 border-radius: 8px;
77 margin-bottom: 16px;
78 max-height: 300px;
79 overflow-y: auto;
80 white-space: pre-wrap;
81 word-wrap: break-word;
82 }
83
84 .wb-desc-char-count {
85 text-align: right;
86 font-size: 12px;
87 color: #6b7280;
88 margin-top: 4px;
89 }
90
91 .wb-desc-char-count.warning {
92 color: #f59e0b;
93 }
94
95 .wb-desc-char-count.error {
96 color: #ef4444;
97 }
98
99 .wb-desc-buttons {
100 display: flex;
101 gap: 12px;
102 justify-content: flex-end;
103 margin-top: 20px;
104 }
105
106 .wb-desc-btn {
107 padding: 10px 20px;
108 border: none;
109 border-radius: 8px;
110 font-size: 14px;
111 font-weight: 500;
112 cursor: pointer;
113 transition: all 0.2s;
114 }
115
116 .wb-desc-btn-primary {
117 background: #9333ea;
118 color: white;
119 }
120
121 .wb-desc-btn-primary:hover {
122 background: #7e22ce;
123 }
124
125 .wb-desc-btn-primary:disabled {
126 background: #9ca3af;
127 cursor: not-allowed;
128 }
129
130 .wb-desc-btn-secondary {
131 background: #e5e7eb;
132 color: #374151;
133 }
134
135 .wb-desc-btn-secondary:hover {
136 background: #d1d5db;
137 }
138
139 .wb-desc-btn-success {
140 background: #10b981;
141 color: white;
142 }
143
144 .wb-desc-btn-success:hover {
145 background: #059669;
146 }
147
148 .wb-desc-generator-btn {
149 margin-left: 12px;
150 padding: 10px 20px;
151 background: #9333ea;
152 color: white;
153 border: none;
154 border-radius: 8px;
155 font-size: 14px;
156 font-weight: 500;
157 cursor: pointer;
158 transition: all 0.2s;
159 }
160
161 .wb-desc-generator-btn:hover {
162 background: #7e22ce;
163 }
164
165 .wb-desc-status {
166 margin-top: 12px;
167 padding: 8px 12px;
168 border-radius: 6px;
169 font-size: 13px;
170 }
171
172 .wb-desc-status.info {
173 background: #dbeafe;
174 color: #1e40af;
175 }
176
177 .wb-desc-status.success {
178 background: #d1fae5;
179 color: #065f46;
180 }
181
182 .wb-desc-status.error {
183 background: #fee2e2;
184 color: #991b1b;
185 }
186 `);
187
188 // Функция для создания кнопки генератора
189 function createGeneratorButton() {
190 const descriptionHeader = document.querySelector('.Description-header__zK-9sKs8RX');
191
192 if (!descriptionHeader) {
193 console.log('Wildberries Description Generator: Заголовок описания не найден');
194 return;
195 }
196
197 // Проверяем, не добавлена ли уже кнопка
198 if (document.querySelector('.wb-desc-generator-btn')) {
199 console.log('Wildberries Description Generator: Кнопка уже добавлена');
200 return;
201 }
202
203 const button = document.createElement('button');
204 button.className = 'wb-desc-generator-btn';
205 button.textContent = 'Генератор описаний';
206 button.type = 'button';
207 button.addEventListener('click', function() {
208 // Автоматически открываем ингредиенты
209 const expandButton = document.querySelector('div.Characteristics__expand__570w3PkC7D button');
210 if (expandButton) expandButton.click();
211
212 // Открываем модальное окно
213 setTimeout(openModal, 500);
214});
215
216 descriptionHeader.appendChild(button);
217 console.log('Wildberries Description Generator: Кнопка добавлена');
218 }
219
220 // Функция для открытия модального окна
221 function openModal() {
222
223 console.log('Wildberries Description Generator: Открытие модального окна');
224
225 const modal = document.createElement('div');
226 modal.className = 'wb-desc-modal';
227 modal.innerHTML = `
228 <div class="wb-desc-modal-content">
229 <div class="wb-desc-modal-header">Генератор описаний для Wildberries</div>
230
231 <div class="wb-desc-input-group">
232 <label class="wb-desc-label">Введите ключевые слова (каждое с новой строки):</label>
233 <textarea class="wb-desc-textarea" id="wb-keywords-input" placeholder="Например: витамины иммунитет здоровье"></textarea>
234 </div>
235
236 <div class="wb-desc-input-group">
237 <label class="wb-desc-label">Минус-слова (каждое с новой строки):</label>
238 <textarea class="wb-desc-textarea" style="min-height: 80px;" id="wb-minus-words-input" placeholder="Например: mixit nivea магнит"></textarea>
239 </div>
240
241 <div id="wb-desc-result-container" style="display: none;">
242 <div class="wb-desc-label">Сгенерированное описание:</div>
243 <div class="wb-desc-result" id="wb-desc-result"></div>
244 <div class="wb-desc-char-count" id="wb-char-count"></div>
245 </div>
246
247 <div id="wb-desc-status-container"></div>
248
249 <div class="wb-desc-buttons">
250 <button class="wb-desc-btn wb-desc-btn-secondary" id="wb-close-btn">Закрыть</button>
251 <button class="wb-desc-btn wb-desc-btn-primary" id="wb-generate-btn">Сгенерировать</button>
252 <button class="wb-desc-btn wb-desc-btn-primary" id="wb-regenerate-btn" style="display: none;">Перегенерировать</button>
253 <button class="wb-desc-btn wb-desc-btn-success" id="wb-insert-btn" style="display: none;">Вставить в описание</button>
254 </div>
255 </div>
256 `;
257
258 document.body.appendChild(modal);
259
260 // Обработчики событий
261 modal.addEventListener('click', (e) => {
262 if (e.target === modal) {
263 modal.remove();
264 }
265 });
266
267 document.getElementById('wb-close-btn').addEventListener('click', () => {
268 modal.remove();
269 });
270
271 document.getElementById('wb-generate-btn').addEventListener('click', () => {
272 generateDescription(modal);
273 });
274
275 document.getElementById('wb-regenerate-btn').addEventListener('click', () => {
276 generateDescription(modal);
277 });
278
279 document.getElementById('wb-insert-btn').addEventListener('click', () => {
280 insertDescription(modal);
281 });
282 }
283
284 // Функция для сбора данных с аналитики
285 async function collectAnalyticsData(keywords, minusWords) {
286 console.log('Wildberries Description Generator: Начало сбора данных с аналитики');
287
288 // Сохраняем данные для доступа из другой вкладки
289 await GM.setValue('wb_keywords_to_process', JSON.stringify(keywords));
290 await GM.setValue('wb_minus_words', JSON.stringify(minusWords));
291 await GM.setValue('wb_analytics_data', JSON.stringify([]));
292 await GM.setValue('wb_collection_status', 'pending');
293
294 // Открываем страницу аналитики в новой вкладке
295 const analyticsUrl = 'https://seller.wildberries.ru/search-analytics/popular-search-queries';
296 await GM.openInTab(analyticsUrl, false);
297
298 console.log('Wildberries Description Generator: Открыта страница аналитики, ожидание сбора данных...');
299
300 // Ждем завершения сбора данных (максимум 5 минут)
301 const maxWaitTime = 300000; // 5 минут
302 const checkInterval = 2000; // проверяем каждые 2 секунды
303 let waitedTime = 0;
304
305 while (waitedTime < maxWaitTime) {
306 await new Promise(resolve => setTimeout(resolve, checkInterval));
307 waitedTime += checkInterval;
308
309 const status = await GM.getValue('wb_collection_status', 'pending');
310
311 if (status === 'completed') {
312 const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
313 const analyticsData = JSON.parse(analyticsDataStr);
314 console.log('Wildberries Description Generator: Данные успешно собраны');
315 return analyticsData;
316 } else if (status === 'error') {
317 console.error('Wildberries Description Generator: Ошибка при сборе данных');
318 return [];
319 }
320 }
321
322 console.error('Wildberries Description Generator: Превышено время ожидания сбора данных');
323 return [];
324 }
325
326 // Функция для автоматического сбора данных на странице аналитики
327 async function autoCollectOnAnalyticsPage() {
328 // Проверяем, что мы на странице аналитики
329 if (!window.location.href.includes('seller.wildberries.ru/search-analytics/popular-search-queries')) {
330 return;
331 }
332
333 console.log('Wildberries Description Generator: Обнаружена страница аналитики');
334
335 // Проверяем, есть ли задача на сбор данных
336 const status = await GM.getValue('wb_collection_status', 'none');
337 if (status !== 'pending') {
338 return;
339 }
340
341 console.log('Wildberries Description Generator: Начинаем автоматический сбор данных');
342
343 try {
344 const keywordsStr = await GM.getValue('wb_keywords_to_process', '[]');
345 const minusWordsStr = await GM.getValue('wb_minus_words', '[]');
346 const keywords = JSON.parse(keywordsStr);
347 const minusWords = JSON.parse(minusWordsStr);
348
349 const analyticsData = [];
350
351 // Ждем загрузки страницы
352 await new Promise(resolve => setTimeout(resolve, 3000));
353
354 for (const keyword of keywords) {
355 console.log(`Wildberries Description Generator: Обработка ключевого слова: ${keyword}`);
356
357 try {
358 // Находим поле поиска
359 const searchInput = document.querySelector('input[name="searchString"]');
360 if (!searchInput) {
361 console.error('Wildberries Description Generator: Поле поиска не найдено');
362 continue;
363 }
364
365 // Очищаем и вводим ключевое слово
366 searchInput.value = '';
367 searchInput.focus();
368 searchInput.value = keyword;
369 searchInput.dispatchEvent(new Event('input', { bubbles: true }));
370 searchInput.dispatchEvent(new Event('change', { bubbles: true }));
371
372 // Ждем загрузки результатов
373 await new Promise(resolve => setTimeout(resolve, 5000));
374
375 // Собираем данные из таблицы
376 const rows = document.querySelectorAll('table tbody tr');
377 const keywordData = {
378 keyword: keyword,
379 queries: []
380 };
381
382 console.log(`Wildberries Description Generator: Найдено строк в таблице: ${rows.length}`);
383
384 rows.forEach(row => {
385 const cells = row.querySelectorAll('td');
386 if (cells.length >= 2) {
387 const query = cells[0]?.textContent?.trim();
388 const popularity = cells[1]?.textContent?.trim();
389
390 if (query && popularity) {
391 // Фильтруем по минус-словам
392 const queryLower = query.toLowerCase();
393 const hasMinusWord = minusWords.some(minusWord =>
394 queryLower.includes(minusWord.toLowerCase())
395 );
396
397 if (!hasMinusWord) {
398 keywordData.queries.push({
399 query,
400 popularity
401 });
402 } else {
403 console.log(`Wildberries Description Generator: Исключен запрос "${query}" (содержит минус-слово)`);
404 }
405 }
406 }
407 });
408
409 analyticsData.push(keywordData);
410 console.log(`Wildberries Description Generator: Собрано ${keywordData.queries.length} запросов для "${keyword}"`);
411
412 } catch (error) {
413 console.error(`Wildberries Description Generator: Ошибка при обработке ключевого слова "${keyword}":`, error);
414 }
415 }
416
417 // Сохраняем собранные данные
418 await GM.setValue('wb_analytics_data', JSON.stringify(analyticsData));
419 await GM.setValue('wb_collection_status', 'completed');
420
421 console.log('Wildberries Description Generator: Сбор данных завершен, можно закрыть вкладку');
422
423 // Закрываем вкладку через 2 секунды
424 setTimeout(() => {
425 window.close();
426 }, 2000);
427
428 } catch (error) {
429 console.error('Wildberries Description Generator: Ошибка при автоматическом сборе данных:', error);
430 await GM.setValue('wb_collection_status', 'error');
431 }
432 }
433
434 // Функция для получения информации о товаре со страницы
435 function getProductInfo() {
436 console.log('Wildberries Description Generator: Сбор информации о товаре');
437
438 const productInfo = {
439 title: '',
440 currentDescription: '',
441 composition: '',
442 attributes: []
443 };
444
445 // 1. Получаем текущее описание
446 const descriptionTextarea = document.querySelector('textarea[data-testid="card-form-main-field-description"]');
447 if (descriptionTextarea) {
448 productInfo.currentDescription = descriptionTextarea.value || '';
449 console.log('Wildberries Description Generator: Текущее описание найдено');
450 }
451
452 // 2. Получаем название товара - ИСПРАВЛЕННЫЙ ПОИСК
453 // Способ 1: По id "editable-title" (contenteditable)
454 const editableTitle = document.querySelector('#editable-title');
455 if (editableTitle) {
456 // Для contenteditable элементов берем textContent или innerText
457 productInfo.title = editableTitle.textContent || editableTitle.innerText || '';
458 console.log('Wildberries Description Generator: Название найдено по #editable-title');
459 }
460
461 // Способ 2: Поиск по label "Наименование"
462 if (!productInfo.title) {
463 // Находим label с текстом "Наименование"
464 const labels = document.querySelectorAll('label, span, div');
465 const nameLabel = Array.from(labels).find(el =>
466 el.textContent && el.textContent.trim() === 'Наименование'
467 );
468
469 if (nameLabel) {
470 console.log('Wildberries Description Generator: Найден label "Наименование"');
471 // Ищем поле ввода рядом с label
472 const parent = nameLabel.closest('div, .field-wrapper, .form-group');
473 if (parent) {
474 // Ищем input или contenteditable
475 const input = parent.querySelector('input, textarea, [contenteditable="true"]');
476 if (input) {
477 if (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') {
478 productInfo.title = input.value || '';
479 } else {
480 productInfo.title = input.textContent || input.innerText || '';
481 }
482 console.log('Wildberries Description Generator: Название найдено рядом с label');
483 }
484 }
485 }
486 }
487
488 // Способ 3: Общий поиск input
489 if (!productInfo.title) {
490 const nameInput = document.querySelector('input[name*="name"], input[placeholder*="Название"], input[placeholder*="наименование"]');
491 if (nameInput) {
492 productInfo.title = nameInput.value || '';
493 console.log('Wildberries Description Generator: Название найдено через общий поиск');
494 }
495 }
496
497 // 3. Получаем состав товара
498 const compositionBlock = document.querySelector('div#Состав');
499 if (compositionBlock) {
500 console.log('Wildberries Description Generator: Найден блок "Состав"');
501
502 let ingredients = [];
503
504 // Способ 1: По data-testid
505 const byTestId = compositionBlock.querySelectorAll('[data-testid^="undefined-select-item-"]');
506 if (byTestId.length > 0) {
507 ingredients = Array.from(byTestId).map(el => el.textContent.trim()).filter(Boolean);
508 console.log(`Wildberries Description Generator: Извлечено ${ingredients.length} ингредиентов по data-testid`);
509 }
510 // Способ 2: По классу
511 else {
512 const byClass = compositionBlock.querySelectorAll('.Selected-item__text__6P8EDRPmWD');
513 if (byClass.length > 0) {
514 ingredients = Array.from(byClass).map(el => el.textContent.trim()).filter(Boolean);
515 console.log(`Wildberries Description Generator: Извлечено ${ingredients.length} ингредиентов по классу`);
516 }
517 }
518
519 // Способ 3: Поиск в чипах
520 if (ingredients.length === 0) {
521 const chips = compositionBlock.querySelectorAll('.Selected-item__fIyMG5Li-v, .New-multi-select-input__selected-item__KOh9hWF-7q');
522 chips.forEach(chip => {
523 const text = chip.textContent.trim();
524 const cleanText = text.replace(/remove$/, '').trim();
525 if (cleanText) {
526 ingredients.push(cleanText);
527 }
528 });
529 console.log(`Wildberries Description Generator: Извлечено ${ingredients.length} ингредиентов по чипам`);
530 }
531
532 // Объединяем ингредиенты
533 if (ingredients.length > 0) {
534 productInfo.composition = ingredients.join(', ');
535 console.log('Wildberries Description Generator: Состав товара извлечен');
536 } else {
537 console.log('Wildberries Description Generator: Ингредиенты не найдены в блоке "Состав"');
538 }
539 } else {
540 console.log('Wildberries Description Generator: Блок "Состав" не найден');
541
542 // Старый способ
543 const compositionTextarea = document.querySelector('textarea[placeholder*="Состав"], textarea[name*="composition"]');
544 if (compositionTextarea && compositionTextarea.value) {
545 productInfo.composition = compositionTextarea.value;
546 console.log('Wildberries Description Generator: Состав найден через textarea');
547 }
548 }
549
550 // 4. Дополнительные атрибуты (опционально)
551 try {
552 const attributes = {};
553
554 // Цвет
555 const colorElement = document.querySelector('div#Цвет');
556 if (colorElement) {
557 const colorText = colorElement.querySelector('[data-testid^="undefined-select-item-"]')?.textContent;
558 if (colorText) attributes.color = colorText.trim();
559 }
560
561 // Бренд
562 const brandInput = document.querySelector('input[placeholder*="Бренд"], input[name*="brand"]');
563 if (brandInput && brandInput.value) {
564 attributes.brand = brandInput.value;
565 }
566
567 if (Object.keys(attributes).length > 0) {
568 productInfo.attributes = Object.entries(attributes).map(([key, value]) => `${key}: ${value}`);
569 }
570 } catch (e) {
571 console.log('Wildberries Description Generator: Ошибка при сборе атрибутов:', e);
572 }
573
574 console.log('Wildberries Description Generator: Информация о товаре собрана', {
575 titleLength: productInfo.title.length,
576 descriptionLength: productInfo.currentDescription.length,
577 compositionLength: productInfo.composition.length,
578 attributesCount: productInfo.attributes.length
579 });
580
581 return productInfo;
582 }
583
584 // Функция для анализа использованных ключей и расчета популярности
585 async function analyzeUsedKeywords(description) {
586 console.log('Wildberries Description Generator: Анализ использованных ключей');
587
588 const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
589 const analyticsData = JSON.parse(analyticsDataStr);
590
591 const descriptionLower = description.toLowerCase();
592 const usedQueries = [];
593 let totalPopularity = 0;
594
595 // Проходим по всем собранным запросам
596 analyticsData.forEach(keywordData => {
597 keywordData.queries.forEach(queryData => {
598 const query = queryData.query.toLowerCase();
599
600 // Проверяем, используется ли запрос в описании
601 if (descriptionLower.includes(query)) {
602 // Парсим популярность (убираем пробелы)
603 const popularityStr = queryData.popularity.replace(/\s/g, '');
604 const popularity = parseInt(popularityStr) || 0;
605
606 usedQueries.push({
607 query: queryData.query,
608 popularity: popularity
609 });
610
611 totalPopularity += popularity;
612 }
613 });
614 });
615
616 console.log(`Wildberries Description Generator: Использовано ${usedQueries.length} запросов`);
617 console.log(`Wildberries Description Generator: Общая популярность: ${totalPopularity}`);
618
619 return {
620 usedQueries,
621 totalPopularity,
622 totalQueriesAvailable: analyticsData.reduce((sum, kd) => sum + kd.queries.length, 0)
623 };
624 }
625
626 // Функция для форматирования числа с разделителями
627 function formatNumber(num) {
628 return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
629 }
630
631 // Функция для генерации описания
632 async function generateDescription(modal) {
633 const keywordsInput = document.getElementById('wb-keywords-input');
634 const minusWordsInput = document.getElementById('wb-minus-words-input');
635 const generateBtn = document.getElementById('wb-generate-btn');
636 const regenerateBtn = document.getElementById('wb-regenerate-btn');
637 const insertBtn = document.getElementById('wb-insert-btn');
638 const resultContainer = document.getElementById('wb-desc-result-container');
639 const resultDiv = document.getElementById('wb-desc-result');
640 const charCountDiv = document.getElementById('wb-char-count');
641 const statusContainer = document.getElementById('wb-desc-status-container');
642
643 const keywordsText = keywordsInput.value.trim();
644 const minusWordsText = minusWordsInput.value.trim();
645
646 if (!keywordsText) {
647 showStatus(statusContainer, 'Пожалуйста, введите ключевые слова', 'error');
648 return;
649 }
650
651 const keywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
652 const minusWords = minusWordsText.split('\n').map(k => k.trim().toLowerCase()).filter(k => k);
653
654 if (keywords.length === 0) {
655 showStatus(statusContainer, 'Пожалуйста, введите хотя бы одно ключевое слово', 'error');
656 return;
657 }
658
659 // Показываем загрузку
660 generateBtn.disabled = true;
661 regenerateBtn.style.display = 'none';
662 insertBtn.style.display = 'none';
663 resultContainer.style.display = 'none';
664
665 showStatus(statusContainer, 'Сбор данных с аналитики...', 'info');
666
667 try {
668 // Собираем данные с аналитики
669 const analyticsData = await collectAnalyticsData(keywords, minusWords);
670
671 showStatus(statusContainer, 'Генерация описания с помощью AI...', 'info');
672
673 // Получаем информацию о товаре
674 const productInfo = getProductInfo();
675
676 // Формируем промпт для AI (СОХРАНЕН БЕЗ ИЗМЕНЕНИЙ)
677 const prompt = `Создай SEO-оптимизированное описание товара для маркетплейса Wildberries.
678
679ВАЖНО: Это описание для внутренней SEO-оптимизации, его не увидят покупатели. Максимальная длина - 5000 символов.
680
681Информация о товаре:
682${productInfo.title ? `Название: ${productInfo.title}` : ''}
683${productInfo.currentDescription ? `Текущее описание: ${productInfo.currentDescription.substring(0, 500)}` : ''}
684${productInfo.composition ? `\nСостав (ингредиенты через запятую):\n${productInfo.composition}\n` : ''}
685
686Ключевые слова для оптимизации:
687${keywords.join(', ')}
688
689${minusWords.length > 0 ? `Минус-слова (НЕ использовать): ${minusWords.join(', ')}` : ''}
690
691Данные из аналитики Wildberries:
692${JSON.stringify(analyticsData, null, 2)}
693
694СТРОГИЕ ТРЕБОВАНИЯ:
695
6961. ФОРМАТ ОТВЕТА: Выдай только готовый текст без вступлений, заголовков и пояснений. Начинай сразу с описания товара.
697
6982. ИСПОЛЬЗОВАНИЕ СОСТАВА:
699 ОБЯЗАТЕЛЬНО используй ингредиенты из блока "Состав" из ${productInfo.composition}!
700 - Выбирай только значимые ингредиенты, которые интересны покупателям
701 - Интегрируй их в предложения с ключевыми запросами
702 - Например: "Магний цитрат с витамином B6", "Экстракт женьшеня для энергии", "Цинк для иммунитета", "Ниацинамид", "Пантенол"
703 - НЕ используй вспомогательные компоненты (тальк, желатин, стеараты и т.д.)
704
7053. СТРУКТУРА ПРЕДЛОЖЕНИЙ - КРИТИЧЕСКИ ВАЖНО:
706 КАЖДОЕ предложение ОБЯЗАТЕЛЬНО должно содержать:
707 [Ключевой запрос из аналитики] + [ингредиент из состава] + [что делает] + [какой эффект]
708 или
709 [Ключевой запрос из аналитики] + [что делает] + [какой эффект]
710 или
711 [ингредиент из состава] + [что делает] + [какой эффект]
712
713 Примеры правильных предложений:
714 - "Магний цитрат с витамином B6 – органическая форма магния, обладающая высокой биодоступностью."
715 - "Магний с витамином В6 является важнейшим элементом организма взрослых в борьбе со повседневным стрессом."
716 - "Коллаген для суставов с гиалуроновой кислотой способствует восстановлению хрящевой ткани и улучшает подвижность."
717
718 ВАЖНО: Используй КАЖДЫЙ релевантный запрос из списка аналитики! Не пропускай запросы!
719
7204. ПРИОРИТЕТ ПРОСТЫХ ЗАПРОСОВ:
721 ОБЯЗАТЕЛЬНО используй простые запросы, которые точно совпадают с ключевыми словами!
722 - Если ключевое слово "магний" - ОБЯЗАТЕЛЬНО используй запрос "магний"
723 - Если ключевое слово "коллаген" - ОБЯЗАТЕЛЬНО используй запрос "коллаген"
724 - Если ключевое слово "для суставов" - ОБЯЗАТЕЛЬНО используй запрос "для суставов"
725 Эти простые запросы обычно имеют самую высокую популярность и КРИТИЧЕСКИ важны для SEO!
726
7275. СОРТИРОВКА ПО ПОПУЛЯРНОСТИ: Располагай предложения в порядке убывания популярности из аналитики. Начинай с запросов, которые имеют наибольшую популярность.
728
7296. УНИКАЛЬНОСТЬ ФРАЗ: Каждая ключевая фраза используется ТОЛЬКО ОДИН РАЗ:
730 ✓ "сыворотка для лица" - 1 раз
731 ✓ "увлажняющая сыворотка для лица" - 1 раз
732 ✗ НЕ повторяй одинаковые фразы
733
7347. ИСКЛЮЧЕНИЕ КОНКУРЕНТОВ И НЕРЕЛЕВАНТНЫХ ЗАПРОСОВ:
735 Автоматически НЕ используй запросы, которые:
736 ✗ Содержат названия брендов конкурентов (mixit, nivea, garnier, loreal, эвалар, зубарева, солгар, now foods, доппельгерц и т.д.)
737 ✗ Содержат фамилии (зубарева, малышева, агапкин и т.д.)
738 ✗ Содержат имена собственные или названия компаний
739 ✗ Не относятся к товару (например, "магнит" вместо "магний", "аптека" как место покупки)
740 ✗ Содержат названия магазинов (магнит, пятерочка, wildberries, ozon, аптека и т.д.)
741 ✗ Содержат слова "купить", "цена", "отзывы", "инструкция", "заказать", "доставка" (это поисковые намерения, а не характеристики)
742 ✗ Являются опечатками или нерелевантными вариациями основного ключа
743 ✗ Содержат латинские буквы или английские слова (кроме общепринятых обозначений типа B6, D3)
744
745 ПРАВИЛО БРЕНДА: Если в запросе есть слово, которое выглядит как название бренда (начинается с заглавной буквы, редко употребляется, похоже на фамилию или название компании) - НЕ используй этот запрос!
746
747 Используй только запросы, которые описывают характеристики, свойства и применение товара на РУССКОМ языке.
748
7498. МАКСИМАЛЬНОЕ ИСПОЛЬЗОВАНИЕ ЗАПРОСОВ:
750 КРИТИЧЕСКИ ВАЖНО: Используй КАЖДЫЙ релевантный запрос из данных аналитики!
751 - Проходи по ВСЕМ запросам из списка аналитики по порядку популярности
752 - Для КАЖДОГО запроса создавай отдельное предложение
753 - НЕ пропускай запросы! Твоя цель - использовать максимум запросов
754 - Если запрос релевантен и не содержит брендов - ОБЯЗАТЕЛЬНО используй его
755 - Продолжай писать предложения, пока не закончатся релевантные запросы или не достигнешь 4000 символов
756
7579. ОБЪЕМ ТЕКСТА:
758 Создай текст на 3800-4000 символов, используя МАКСИМАЛЬНОЕ количество запросов из аналитики.
759 Чем больше запросов используешь - тем лучше для SEO!
760
76110. ЯЗЫК:
762 КРИТИЧЕСКИ ВАЖНО: Пиши ТОЛЬКО на русском языке!
763
764 ✗ СТРОГО ЗАПРЕЩЕНО использовать английские слова:
765 - essential → незаменимый, важный, ключевой
766 - crucial → критически важный, ключевой, важнейший
767 - hundreds → сотни
768 - magnesium → магний
769 - protein → белок
770 - amino acids → аминокислоты
771
772 ✗ ЗАПРЕЩЕНО использовать латинские буквы (кроме обозначений витаминов: B6, D3, C, E, A, K)
773 ✓ РАЗРЕШЕНЫ только обозначения витаминов латиницей: B1, B2, B3, B6, B12, D, D3, C, E, A, K, PP
774 ✓ Все остальное - ТОЛЬКО русскими буквами
775
776 ПРАВИЛО: Если не знаешь русский перевод слова - используй другое русское слово с похожим значением!
777 Примеры замены:
778 - "essential amino acids" → "незаменимые аминокислоты" или "важнейшие аминокислоты"
779 - "crucial role" → "ключевая роль" или "важнейшая роль"
780 - "optimal ratio" → "оптимальное соотношение" или "идеальная пропорция"
781
782 Если в запросе из аналитики есть английские слова - НЕ используй этот запрос!
783 Используй только запросы на чистом русском языке.
784
78511. ЕСТЕСТВЕННОСТЬ: Текст должен быть читабельным и естественным, несмотря на высокую плотность ключевых слов.
786
787КРИТИЧЕСКИ ВАЖНО:
788- Используй КАЖДЫЙ релевантный запрос из аналитики (не пропускай!)
789- В КАЖДОМ предложении должен быть запрос из аналитики + ингредиент из состава
790- Цель: максимальная видимость = максимум использованных запросов
791- Пиши ТОЛЬКО на русском языке, исключая бренды и фамилии
792
793ИНСТРУКЦИЯ ПО ИСПОЛЬЗОВАНИЮ ЗАПРОСОВ:
794Проходи по списку запросов из аналитики ПОСЛЕДОВАТЕЛЬНО и для КАЖДОГО запроса:
7951. Проверь, содержит ли запрос бренды/фамилии/английские слова - если ДА, пропусти
7962. Если запрос чистый - ОБЯЗАТЕЛЬНО создай для него предложение
7973. Продолжай, пока не используешь ВСЕ релевантные запросы или не достигнешь 4000 символов
798
799Твоя цель - использовать МИНИМУМ 60-70% от всех доступных запросов!
800
801Вот инструкция для описания:
802
803ПОШАГОВАЯ ИНСТРУКЦИЯ ДЛЯ СОЗДАНИЯ ОПИСАНИЯ:
804
805ШАГ 1: АНАЛИЗ ИНГРЕДИЕНТОВ
806- Посмотри на состав товара: ${productInfo.composition}
807- Выдели 7-10 самых важных ингредиентов
808- Для каждого ингредиента определи: что это, для чего, какая польза
809
810ШАГ 2: СОЗДАНИЕ БАЗОВЫХ ПРЕДЛОЖЕНИЙ ПРО ИНГРЕДИЕНТЫ
811Создай по 2-3 предложения на КАЖДЫЙ важный ингредиент:
812Пример для "гиалуроновая кислота":
8131. "Гиалуроновая кислота в составе притягивает и удерживает влагу."
8142. "Молекулы гиалуроновой кислоты заполняют межклеточное пространство."
8153. "Для обезвоженной кожи гиалуроновая кислота является спасением."
816
817СДЕЛАЙ ЭТО ДЛЯ ВСЕХ ИНГРЕДИЕНТОВ ИЗ СОСТАВА!
818
819ШАГ 3: ИНТЕГРАЦИЯ С КЛЮЧЕВЫМИ ЗАПРОСАМИ
820- Возьми список запросов из аналитики
821- Для КАЖДОГО запроса найди, с каким ингредиентом его можно связать
822- Создай предложение: [Запрос] + [ингредиент] + [действие] + [эффект]
823Пример: "Увлажняющий крем с гиалуроновой кислотой интенсивно насыщает кожу влагой."
824
825ШАГ 4: ДОПОЛНИТЕЛЬНОЕ ИСПОЛЬЗОВАНИЕ ЗАПРОСОВ
826- Если остались неиспользованные запросы - создай для них предложения
827- Можно использовать запросы без привязки к конкретному ингредиенту
828- Главное - использовать максимальное количество запросов
829
830ШАГ 5: КОМПОНОВКА
831- Начни с самых популярных запросов
832- Чередуй: предложения с ингредиентами → предложения с запросами
833- Добейся объема 3800-4000 символов
834
835ВАЖНЕЙШЕЕ ПРАВИЛО: Если название ключевые запросы не соответствуют названию товара, но не надо их использовать! Например, если товар Лосьон для пяток, то не надо использовать Массажер для пяток или пилочка для пяток.
836Если товар Крем от прыщей, то не надо использовать Патчи от прыщей.
837
838
839Сгенерируй описание:`;
840
841 console.log('Wildberries Description Generator: Отправка запроса к AI');
842
843 // Генерируем описание с помощью AI
844 const description = await RM.aiCall(prompt);
845
846 console.log('Wildberries Description Generator: Описание сгенерировано');
847
848 // Анализируем использованные ключи
849 const analysis = await analyzeUsedKeywords(description);
850
851 // Проверяем длину
852 const charCount = description.length;
853
854 // Сохраняем описание
855 await GM.setValue('wb_generated_description', description);
856
857 // Показываем результат
858 resultDiv.textContent = description;
859 resultContainer.style.display = 'block';
860
861 // Обновляем счетчик символов с информацией о ключах
862 const popularityInfo = `${charCount} / 5000 символов | Использовано ${analysis.usedQueries.length} из ${analysis.totalQueriesAvailable} запросов | Общая популярность: ${formatNumber(analysis.totalPopularity)}`;
863 charCountDiv.textContent = popularityInfo;
864
865 // Показываем кнопки
866 generateBtn.style.display = 'none';
867 regenerateBtn.style.display = 'inline-block';
868 insertBtn.style.display = 'inline-block';
869
870 showStatus(statusContainer, `Описание успешно сгенерировано! (${charCount} символов)`, 'success');
871
872 } catch (error) {
873 console.error('Wildberries Description Generator: Ошибка при генерации описания:', error);
874 showStatus(statusContainer, 'Ошибка при генерации описания. Попробуйте еще раз.', 'error');
875 } finally {
876 generateBtn.disabled = false;
877 }
878 }
879
880 // Функция для показа статуса
881 function showStatus(container, message, type) {
882 container.innerHTML = `<div class="wb-desc-status ${type}">${message}</div>`;
883 }
884
885 // Функция для вставки описания
886 async function insertDescription(modal) {
887 console.log('Wildberries Description Generator: Вставка описания');
888
889 try {
890 const description = await GM.getValue('wb_generated_description', '');
891
892 if (!description) {
893 alert('Описание не найдено. Пожалуйста, сгенерируйте описание сначала.');
894 return;
895 }
896
897 // Находим поле описания
898 const descriptionTextarea = document.querySelector('textarea[data-testid="card-form-main-field-description"]');
899
900 if (!descriptionTextarea) {
901 alert('Не удалось найти поле описания. Убедитесь, что вы находитесь на странице редактирования товара.');
902 return;
903 }
904
905 // Вставляем описание
906 descriptionTextarea.value = description;
907 descriptionTextarea.dispatchEvent(new Event('input', { bubbles: true }));
908 descriptionTextarea.dispatchEvent(new Event('change', { bubbles: true }));
909
910 console.log('Wildberries Description Generator: Описание успешно вставлено');
911
912 // Закрываем модальное окно
913 modal.remove();
914
915 alert('Описание успешно вставлено!');
916
917 } catch (error) {
918 console.error('Wildberries Description Generator: Ошибка при вставке описания:', error);
919 alert('Ошибка при вставке описания. Попробуйте еще раз.');
920 }
921 }
922
923 // Функция для инициализации расширения
924 function init() {
925 console.log('Wildberries Description Generator: Инициализация');
926
927 // Проверяем, что мы на странице редактирования товара
928 if (window.location.href.includes('seller.wildberries.ru/new-goods/card')) {
929
930 // Ждем загрузки страницы и добавляем кнопку
931 const observer = new MutationObserver((mutations, obs) => {
932 const descriptionHeader = document.querySelector('.Description-header__zK-9sKs8RX');
933 if (descriptionHeader) {
934 createGeneratorButton();
935 obs.disconnect();
936 }
937 });
938
939 observer.observe(document.body, {
940 childList: true,
941 subtree: true
942 });
943
944 // Также пробуем добавить кнопку сразу
945 setTimeout(createGeneratorButton, 2000);
946 }
947
948 // Проверяем, что мы на странице аналитики и запускаем автосбор
949 if (window.location.href.includes('seller.wildberries.ru/search-analytics/popular-search-queries')) {
950 setTimeout(autoCollectOnAnalyticsPage, 2000);
951 }
952 }
953
954 // Запускаем инициализацию
955 if (document.readyState === 'loading') {
956 document.addEventListener('DOMContentLoaded', init);
957 } else {
958 init();
959 }
960
961})();