new commit
This commit is contained in:
0
scan-sphera-main/public/favicon.ico
Normal file
0
scan-sphera-main/public/favicon.ico
Normal file
0
scan-sphera-main/public/images/cache/.gitkeep
vendored
Normal file
0
scan-sphera-main/public/images/cache/.gitkeep
vendored
Normal file
BIN
scan-sphera-main/public/images/logo.png
Normal file
BIN
scan-sphera-main/public/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 387 KiB |
1
scan-sphera-main/public/images/logo.svg
Normal file
1
scan-sphera-main/public/images/logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" fill="#444"/><text x="50%" y="50%" font-family="Arial" font-size="14" fill="#fff" text-anchor="middle" dominant-baseline="middle">Logo</text></svg>
|
After Width: | Height: | Size: 244 B |
1
scan-sphera-main/public/images/no-image.svg
Normal file
1
scan-sphera-main/public/images/no-image.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" fill="#ddd"/><text x="50%" y="50%" font-family="Arial" font-size="14" fill="#555" text-anchor="middle" dominant-baseline="middle">No Image</text></svg>
|
After Width: | Height: | Size: 248 B |
231
scan-sphera-main/public/test-anychart.html
Normal file
231
scan-sphera-main/public/test-anychart.html
Normal file
@ -0,0 +1,231 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test AnyChart Box and Whisker</title>
|
||||
<script src="https://cdn.anychart.com/releases/v8/js/anychart-base.min.js"></script>
|
||||
<script src="https://cdn.anychart.com/releases/v8/js/anychart-cartesian.min.js"></script>
|
||||
<script src="https://cdn.anychart.com/releases/v8/js/anychart-ui.min.js"></script>
|
||||
<script src="https://cdn.anychart.com/releases/v8/js/anychart-exports.min.js"></script>
|
||||
<link href="https://cdn.anychart.com/releases/v8/css/anychart-ui.min.css" type="text/css" rel="stylesheet">
|
||||
<link href="https://cdn.anychart.com/releases/v8/fonts/css/anychart-font.min.css" type="text/css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
.header {
|
||||
background: #1e293b;
|
||||
color: #22c55e;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
#chart-container {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
color: #1e293b;
|
||||
font-size: 14px;
|
||||
}
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.legend-box {
|
||||
width: 16px;
|
||||
height: 12px;
|
||||
background: rgba(34, 197, 94, 0.3);
|
||||
border: 1px solid #22c55e;
|
||||
}
|
||||
.legend-line {
|
||||
width: 16px;
|
||||
height: 3px;
|
||||
background: #16a34a;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>ПОЗИЦИИ • СРАВНЕНИЕ ТОВАРОВ</h1>
|
||||
<div style="font-size: 14px; opacity: 0.8; margin-top: 8px;">
|
||||
🔵 Ваш товар: 173269559 | 🔴 Конкурент: 388410028
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chart-container"></div>
|
||||
|
||||
<div class="legend">
|
||||
<div class="legend-item">
|
||||
<div class="legend-box"></div>
|
||||
<span>Диапазон позиций</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-line"></div>
|
||||
<span>Медиана</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
anychart.onDocumentReady(function () {
|
||||
// Тестовые данные позиций товаров по городам
|
||||
const testData = [
|
||||
{
|
||||
x: 'Москва',
|
||||
low: 1,
|
||||
q1: 1,
|
||||
median: 1.5,
|
||||
q3: 2,
|
||||
high: 2,
|
||||
outliers: []
|
||||
},
|
||||
{
|
||||
x: 'СПб',
|
||||
low: 3,
|
||||
q1: 3,
|
||||
median: 4.5,
|
||||
q3: 6,
|
||||
high: 6,
|
||||
outliers: []
|
||||
},
|
||||
{
|
||||
x: 'Казань',
|
||||
low: 1,
|
||||
q1: 1,
|
||||
median: 1,
|
||||
q3: 1,
|
||||
high: 1,
|
||||
outliers: []
|
||||
},
|
||||
{
|
||||
x: 'Екатеринбург',
|
||||
low: 1,
|
||||
q1: 1,
|
||||
median: 1,
|
||||
q3: 1,
|
||||
high: 1,
|
||||
outliers: []
|
||||
},
|
||||
{
|
||||
x: 'Новосибирск',
|
||||
low: 1,
|
||||
q1: 1,
|
||||
median: 2.5,
|
||||
q3: 4,
|
||||
high: 4,
|
||||
outliers: []
|
||||
},
|
||||
{
|
||||
x: 'Краснодар',
|
||||
low: 1,
|
||||
q1: 1,
|
||||
median: 1,
|
||||
q3: 1,
|
||||
high: 1,
|
||||
outliers: []
|
||||
},
|
||||
{
|
||||
x: 'Хабаровск',
|
||||
low: 1,
|
||||
q1: 1,
|
||||
median: 1,
|
||||
q3: 1,
|
||||
high: 1,
|
||||
outliers: []
|
||||
}
|
||||
];
|
||||
|
||||
// Создаем Box chart
|
||||
const chart = anychart.box();
|
||||
|
||||
// Настраиваем заголовок
|
||||
chart.title('Сравнение позиций товаров по городам');
|
||||
chart.title().fontColor('#22c55e');
|
||||
chart.title().fontSize(18);
|
||||
chart.title().fontWeight('bold');
|
||||
|
||||
// Настраиваем оси
|
||||
chart.yAxis().title('Позиция в поиске');
|
||||
chart.yAxis().labels().format('{%value}');
|
||||
|
||||
chart.xAxis().title('Города');
|
||||
chart.xAxis().staggerMode(true);
|
||||
|
||||
// Инвертируем ось Y через шкалу (1 позиция вверху)
|
||||
try {
|
||||
var yScale = chart.yScale();
|
||||
if (yScale && typeof yScale.inverted === 'function') {
|
||||
yScale.inverted(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Не удалось инвертировать ось Y:', error);
|
||||
}
|
||||
|
||||
// Создаем серию
|
||||
const series = chart.box(testData);
|
||||
|
||||
// Настраиваем внешний вид
|
||||
series.whiskerWidth('60%');
|
||||
series.fill('#22c55e', 0.3);
|
||||
series.stroke('#22c55e', 2);
|
||||
series.whiskerStroke('#22c55e', 2);
|
||||
series.medianStroke('#16a34a', 3);
|
||||
|
||||
// Настраиваем подсказки
|
||||
series.tooltip().format(
|
||||
'Город: {%x}' +
|
||||
'\nМин позиция: {%low}' +
|
||||
'\nМакс позиция: {%high}' +
|
||||
'\nМедиана: {%median}'
|
||||
);
|
||||
|
||||
// Настраиваем сетку
|
||||
try {
|
||||
chart.grid(0).stroke('#e5e7eb', 1, '2 2');
|
||||
chart.grid(1).layout('vertical').stroke('#e5e7eb', 1, '2 2');
|
||||
} catch (error) {
|
||||
console.log('Не удалось настроить сетку:', error);
|
||||
// Альтернативный способ настройки сетки для Box диаграмм
|
||||
try {
|
||||
chart.yGrid(true);
|
||||
chart.xGrid(true);
|
||||
} catch (gridError) {
|
||||
console.log('Альтернативная настройка сетки также недоступна:', gridError);
|
||||
}
|
||||
}
|
||||
|
||||
// Настраиваем фон
|
||||
chart.background().fill('transparent');
|
||||
|
||||
// Устанавливаем контейнер и отрисовываем
|
||||
chart.container('chart-container');
|
||||
chart.draw();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
216
scan-sphera-main/public/test-column-chart.html
Normal file
216
scan-sphera-main/public/test-column-chart.html
Normal file
@ -0,0 +1,216 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Тест Box and Whisker диаграммы AnyChart</title>
|
||||
<script src="https://cdn.anychart.com/releases/v8/js/anychart-base.min.js"></script>
|
||||
<script src="https://cdn.anychart.com/releases/v8/js/anychart-ui.min.js"></script>
|
||||
<script src="https://cdn.anychart.com/releases/v8/js/anychart-exports.min.js"></script>
|
||||
<script src="https://cdn.anychart.com/releases/v8/js/anychart-data-adapter.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.anychart.com/releases/v8/css/anychart-ui.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.anychart.com/releases/v8/fonts/css/anychart-font.min.css">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: linear-gradient(to right, #9da8f9, #fce7ff);
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
#chart-container {
|
||||
height: 500px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.legend-box {
|
||||
width: 16px;
|
||||
height: 12px;
|
||||
border: 1px solid;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Тест Box and Whisker диаграммы - Сравнение позиций товаров</h1>
|
||||
<p>Тестируем Box and Whisker диаграмму с исправлением для одинаковых позиций</p>
|
||||
|
||||
<div id="chart-container"></div>
|
||||
|
||||
<div class="legend">
|
||||
<div class="legend-item">
|
||||
<div class="legend-box" style="background-color: rgba(34, 197, 94, 0.3); border-color: #22c55e;"></div>
|
||||
<span>Диапазон позиций</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-box" style="background-color: #16a34a; border-color: #16a34a; height: 3px;"></div>
|
||||
<span>Медиана</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
anychart.onDocumentReady(function () {
|
||||
// Тестовые данные позиций
|
||||
const testData = [
|
||||
{ city: 'Москва', myPosition: 5, competitorPosition: 3 },
|
||||
{ city: 'СПб', myPosition: 2, competitorPosition: 7 },
|
||||
{ city: 'Екатеринбург', myPosition: 1, competitorPosition: 1 }, // Одинаковые позиции
|
||||
{ city: 'Новосибирск', myPosition: 8, competitorPosition: 4 },
|
||||
{ city: 'Казань', myPosition: 3, competitorPosition: 12 }
|
||||
];
|
||||
|
||||
// Преобразуем данные для Box and Whisker диаграммы с исправлением одинаковых значений
|
||||
function prepareBoxData(data) {
|
||||
return data.map((item) => {
|
||||
// Собираем все позиции (исключаем нулевые и отрицательные)
|
||||
const positions = [item.myPosition, item.competitorPosition].filter(p => p > 0);
|
||||
|
||||
if (positions.length === 0) {
|
||||
// Если нет валидных позиций, создаем базовый элемент
|
||||
return {
|
||||
x: item.city,
|
||||
low: 1,
|
||||
q1: 1,
|
||||
median: 1,
|
||||
q3: 1,
|
||||
high: 1,
|
||||
outliers: []
|
||||
};
|
||||
}
|
||||
|
||||
if (positions.length === 1) {
|
||||
// Если только одна позиция, показываем её как точку
|
||||
const pos = positions[0];
|
||||
return {
|
||||
x: item.city,
|
||||
low: pos,
|
||||
q1: pos,
|
||||
median: pos,
|
||||
q3: pos,
|
||||
high: pos,
|
||||
outliers: []
|
||||
};
|
||||
}
|
||||
|
||||
// Если две позиции
|
||||
let [pos1, pos2] = positions;
|
||||
|
||||
// Если позиции одинаковые, добавляем небольшое смещение для визуализации
|
||||
if (pos1 === pos2) {
|
||||
pos2 = pos1 + 0.1; // Небольшое смещение
|
||||
}
|
||||
|
||||
const min = Math.min(pos1, pos2);
|
||||
const max = Math.max(pos1, pos2);
|
||||
const median = (pos1 + pos2) / 2;
|
||||
|
||||
return {
|
||||
x: item.city,
|
||||
low: min,
|
||||
q1: min,
|
||||
median: median,
|
||||
q3: max,
|
||||
high: max,
|
||||
outliers: []
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Подготавливаем данные
|
||||
const boxData = prepareBoxData(testData);
|
||||
console.log('Данные для диаграммы:', boxData);
|
||||
|
||||
// Создаем Box and Whisker диаграмму
|
||||
const chart = anychart.box();
|
||||
|
||||
// Настраиваем заголовок
|
||||
chart.title('Сравнение позиций товаров по городам');
|
||||
chart.title().fontColor('#22c55e');
|
||||
chart.title().fontSize(18);
|
||||
|
||||
// Настраиваем оси
|
||||
chart.yAxis().title('Позиция');
|
||||
chart.yAxis().labels().format('{%value}');
|
||||
|
||||
chart.xAxis().title('Города');
|
||||
chart.xAxis().staggerMode(true);
|
||||
|
||||
// Инвертируем ось Y (позиция 1 сверху)
|
||||
try {
|
||||
chart.yScale().inverted(true);
|
||||
} catch (error) {
|
||||
console.log('Не удалось инвертировать ось Y:', error);
|
||||
}
|
||||
|
||||
// Создаем серию
|
||||
const series = chart.box(boxData);
|
||||
|
||||
// Настраиваем внешний вид
|
||||
series.whiskerWidth('60%');
|
||||
series.fill('#22c55e', 0.3);
|
||||
series.stroke('#22c55e', 2);
|
||||
series.whiskerStroke('#22c55e', 2);
|
||||
series.medianStroke('#16a34a', 3);
|
||||
|
||||
// Настраиваем подсказки
|
||||
series.tooltip().format(
|
||||
'Город: {%x}' +
|
||||
'\nМин позиция: {%low}' +
|
||||
'\nМакс позиция: {%high}' +
|
||||
'\nМедиана: {%median}'
|
||||
);
|
||||
|
||||
// Настраиваем сетку
|
||||
try {
|
||||
chart.grid(0).stroke('#e5e7eb', 1, '2 2');
|
||||
chart.grid(1).layout('vertical').stroke('#e5e7eb', 1, '2 2');
|
||||
} catch (error) {
|
||||
console.log('Не удалось настроить сетку:', error);
|
||||
try {
|
||||
chart.yGrid(true);
|
||||
chart.xGrid(true);
|
||||
} catch (gridError) {
|
||||
console.log('Альтернативная настройка сетки также недоступна:', gridError);
|
||||
}
|
||||
}
|
||||
|
||||
// Настраиваем фон
|
||||
chart.background().fill('transparent');
|
||||
|
||||
// Устанавливаем контейнер и отрисовываем
|
||||
chart.container('chart-container');
|
||||
chart.draw();
|
||||
|
||||
console.log('Диаграмма успешно создана!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка при создании диаграммы:', error);
|
||||
document.getElementById('chart-container').innerHTML =
|
||||
'<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #ef4444;">' +
|
||||
'<div><h3>Ошибка загрузки диаграммы</h3><p>' + error.message + '</p></div>' +
|
||||
'</div>';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
419
scan-sphera-main/public/test-parser.html
Normal file
419
scan-sphera-main/public/test-parser.html
Normal file
@ -0,0 +1,419 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Тест Парсера Wildberries - ОБНОВЛЕНО</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #2d3748;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
background: #48bb78;
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.update-info {
|
||||
background: #e6fffa;
|
||||
border: 2px solid #48bb78;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.update-info h3 {
|
||||
color: #2d3748;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.update-info ul {
|
||||
color: #4a5568;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
background: #f7fafc;
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #2d3748;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.preset-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.preset-btn {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.preset-btn:hover {
|
||||
background: #5a67d8;
|
||||
}
|
||||
|
||||
.test-btn {
|
||||
background: #48bb78;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 30px;
|
||||
border-radius: 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.test-btn:hover:not(:disabled) {
|
||||
background: #38a169;
|
||||
}
|
||||
|
||||
.test-btn:disabled {
|
||||
background: #a0aec0;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: none;
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
color: #4a5568;
|
||||
}
|
||||
|
||||
.loading.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid #e2e8f0;
|
||||
border-radius: 50%;
|
||||
border-top-color: #667eea;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.results {
|
||||
display: none;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.results.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
background: white;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.product-details h3 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #2d3748;
|
||||
}
|
||||
|
||||
.positions-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.positions-table th,
|
||||
.positions-table td {
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.positions-table th {
|
||||
background: #4a5568;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.position-found {
|
||||
color: #48bb78;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.position-not-found {
|
||||
color: #f56565;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #fed7d7;
|
||||
border: 2px solid #f56565;
|
||||
color: #c53030;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🎯 Тест Парсера Wildberries</h1>
|
||||
<span class="status-badge">✅ РАБОТАЕТ</span>
|
||||
</div>
|
||||
|
||||
<div class="update-info">
|
||||
<h3>🚀 Парсер обновлен и работает!</h3>
|
||||
<ul>
|
||||
<li><strong>Решена проблема блокировки:</strong> Добавлены продвинутые методы обхода защиты Wildberries</li>
|
||||
<li><strong>Улучшенная маскировка:</strong> Парсер теперь имитирует реальное поведение пользователя</li>
|
||||
<li><strong>API поиск:</strong> Добавлен резервный поиск через внутренние API</li>
|
||||
<li><strong>Стабильная работа:</strong> Тестирование показало успешное нахождение товаров</li>
|
||||
<li><strong>Корректная обработка:</strong> Правильно обрабатывает случаи "товар не найден"</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>🧪 Протестировать парсер</h2>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="query">Поисковый запрос:</label>
|
||||
<input type="text" id="query" value="лабубу" placeholder="Введите поисковый запрос">
|
||||
<div class="preset-buttons">
|
||||
<button class="preset-btn" onclick="setPreset('лабубу', '447020075')">Лабубу (работает)</button>
|
||||
<button class="preset-btn" onclick="setPreset('плюшевая игрушка', '197761909')">Плюшевая игрушка (работает)</button>
|
||||
<button class="preset-btn" onclick="setPreset('игрушка', '123456789')">Тест "не найдено"</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="article">Артикул товара:</label>
|
||||
<input type="text" id="article" value="447020075" placeholder="Введите артикул">
|
||||
</div>
|
||||
|
||||
<button class="test-btn" onclick="testParser()" id="testBtn">
|
||||
🔍 Запустить тест парсера
|
||||
</button>
|
||||
|
||||
<div class="loading" id="loading">
|
||||
<div class="spinner"></div>
|
||||
Парсинг в процессе... Это может занять до 1 минуты
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="results" id="results">
|
||||
<div class="result-header">
|
||||
<span class="status-icon" id="statusIcon"></span>
|
||||
<h2 id="resultTitle"></h2>
|
||||
</div>
|
||||
|
||||
<div id="resultContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function setPreset(query, article) {
|
||||
document.getElementById('query').value = query;
|
||||
document.getElementById('article').value = article;
|
||||
}
|
||||
|
||||
async function testParser() {
|
||||
const query = document.getElementById('query').value.trim();
|
||||
const article = document.getElementById('article').value.trim();
|
||||
|
||||
if (!query || !article) {
|
||||
alert('Пожалуйста, заполните все поля');
|
||||
return;
|
||||
}
|
||||
|
||||
const testBtn = document.getElementById('testBtn');
|
||||
const loading = document.getElementById('loading');
|
||||
const results = document.getElementById('results');
|
||||
|
||||
// Показываем индикатор загрузки
|
||||
testBtn.disabled = true;
|
||||
loading.classList.add('active');
|
||||
results.classList.remove('active');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/test-parser', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: query,
|
||||
myArticleId: article
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showResults(true, data.data, data.message);
|
||||
} else {
|
||||
showResults(false, null, data.error || 'Произошла ошибка');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
showResults(false, null, 'Ошибка соединения: ' + error.message);
|
||||
} finally {
|
||||
testBtn.disabled = false;
|
||||
loading.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
function showResults(success, data, message) {
|
||||
const results = document.getElementById('results');
|
||||
const statusIcon = document.getElementById('statusIcon');
|
||||
const resultTitle = document.getElementById('resultTitle');
|
||||
const resultContent = document.getElementById('resultContent');
|
||||
|
||||
if (success && data) {
|
||||
statusIcon.textContent = '✅';
|
||||
resultTitle.textContent = 'Парсинг выполнен успешно!';
|
||||
|
||||
const product = data.product;
|
||||
const positions = data.positions;
|
||||
|
||||
const foundPositions = positions.filter(p => p.position !== null);
|
||||
const isFound = foundPositions.length > 0;
|
||||
|
||||
resultContent.innerHTML = `
|
||||
<div class="product-card">
|
||||
<div class="product-info">
|
||||
<img src="${product.imageUrl}" alt="${product.name}" class="product-image" onerror="this.src='/images/no-image.svg'">
|
||||
<div class="product-details">
|
||||
<h3>${product.name}</h3>
|
||||
<p><strong>Бренд:</strong> ${product.brand}</p>
|
||||
<p><strong>Цена:</strong> ${product.price} ₽</p>
|
||||
<p><strong>Артикул:</strong> ${product.article}</p>
|
||||
<p><strong>Статус:</strong> ${isFound ? '<span class="position-found">Найден в поиске</span>' : '<span class="position-not-found">Не найден в поиске</span>'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Позиции по городам:</h3>
|
||||
<table class="positions-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Город</th>
|
||||
<th>Код</th>
|
||||
<th>Позиция</th>
|
||||
<th>Статус</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${positions.map(pos => `
|
||||
<tr>
|
||||
<td>${pos.city}</td>
|
||||
<td>${pos.cityCode}</td>
|
||||
<td>${pos.position || '—'}</td>
|
||||
<td>${pos.position ? '<span class="position-found">Найден</span>' : '<span class="position-not-found">Не найден</span>'}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p style="margin-top: 20px; color: #4a5568; font-style: italic;">${message}</p>
|
||||
`;
|
||||
} else {
|
||||
statusIcon.textContent = '❌';
|
||||
resultTitle.textContent = 'Ошибка парсинга';
|
||||
resultContent.innerHTML = `<div class="error">${message}</div>`;
|
||||
}
|
||||
|
||||
results.classList.add('active');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
101
scan-sphera-main/public/test.html
Normal file
101
scan-sphera-main/public/test.html
Normal file
@ -0,0 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Тест парсера WB</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.container { max-width: 800px; margin: 0 auto; }
|
||||
input, button { padding: 10px; margin: 5px; }
|
||||
input[type="text"] { width: 300px; }
|
||||
#result { margin-top: 20px; padding: 15px; background: #f5f5f5; border-radius: 5px; }
|
||||
.loading { color: #666; }
|
||||
.error { color: red; }
|
||||
.success { color: green; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Тест парсера Wildberries</h1>
|
||||
<div>
|
||||
<label>Поисковый запрос:</label><br>
|
||||
<input type="text" id="query" value="игрушка" placeholder="Введите поисковый запрос">
|
||||
</div>
|
||||
<div>
|
||||
<label>Артикул товара:</label><br>
|
||||
<input type="text" id="articleId" value="439726135" placeholder="Введите артикул">
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="testParser()">Запустить тест</button>
|
||||
</div>
|
||||
<div id="result"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function testParser() {
|
||||
const resultDiv = document.getElementById('result');
|
||||
const query = document.getElementById('query').value;
|
||||
const articleId = document.getElementById('articleId').value;
|
||||
|
||||
if (!query || !articleId) {
|
||||
resultDiv.innerHTML = '<div class="error">Пожалуйста, заполните все поля</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
resultDiv.innerHTML = '<div class="loading">Запуск парсинга... Это может занять до 60 секунд.</div>';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/parser', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: query,
|
||||
myArticleId: articleId
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
resultDiv.innerHTML = `
|
||||
<div class="success">
|
||||
<h3>Результаты парсинга:</h3>
|
||||
<h4>Информация о товаре:</h4>
|
||||
<p><strong>Название:</strong> ${data.products.my.name}</p>
|
||||
<p><strong>Артикул:</strong> ${data.products.my.articleId}</p>
|
||||
<p><strong>Цена:</strong> ${data.products.my.price} ₽</p>
|
||||
<p><strong>Бренд:</strong> ${data.products.my.brand}</p>
|
||||
|
||||
<h4>Позиции в городах:</h4>
|
||||
<table border="1" cellpadding="5">
|
||||
<tr>
|
||||
<th>Город</th>
|
||||
<th>Страница</th>
|
||||
<th>Позиция</th>
|
||||
</tr>
|
||||
${data.positions.map(pos => `
|
||||
<tr>
|
||||
<td>${pos.city}</td>
|
||||
<td>${pos.myPage}</td>
|
||||
<td>${pos.myPosition}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</table>
|
||||
|
||||
<h4>Полный JSON ответ:</h4>
|
||||
<pre style="background: white; padding: 10px; overflow: auto;">${JSON.stringify(data, null, 2)}</pre>
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
resultDiv.innerHTML = `<div class="error">Ошибка: ${error.message}</div>`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user