419 lines
14 KiB
HTML
419 lines
14 KiB
HTML
<!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> |