Greasy Fork is available in English.
Быстро прокручивает страницу категории Ozon и копирует результаты в буфер обмена.
// ==UserScript==
// @name Ozon Scraper
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Быстро прокручивает страницу категории Ozon и копирует результаты в буфер обмена.
// @author torch
// @match https://www.ozon.ru/*
// @grant GM_setClipboard
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// Создание контейнера для панели управления
const panel = document.createElement('div');
panel.id = 'ozon-scraper-panel';
panel.style.cssText = `
position: fixed;
bottom: 24px;
right: 24px;
z-index: 999999;
background: rgba(20, 20, 20, 0.85);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 16px;
padding: 16px;
color: #f3f4f6;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
width: 280px;
box-sizing: border-box;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
`;
panel.innerHTML = `
<div style="font-weight: 600; margin-bottom: 12px; font-size: 14px; display: flex; justify-content: space-between; align-items: center; letter-spacing: -0.01em;">
<span>Ozon Scraper (Fast)</span>
<span id="scraper-status" style="font-size: 10px; background: #ef4444; color: #fff; padding: 3px 8px; border-radius: 20px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;">Остановлен</span>
</div>
<div style="font-size: 12px; margin-bottom: 16px; color: #9ca3af; display: flex; justify-content: space-between;">
<span>Найдено товаров:</span>
<span id="product-count" style="font-weight: 700; color: #10b981; font-size: 13px;">0</span>
</div>
<div style="display: flex; flex-direction: column; gap: 8px;">
<button id="btn-toggle-scroll" style="background: #2563eb; color: white; border: none; padding: 10px; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 13px; transition: background 0.2s;">▶ Быстрая прокрутка</button>
<button id="btn-copy-data" style="background: #10b981; color: #042f1a; border: none; padding: 10px; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 13px; transition: background 0.2s;">📋 Скопировать данные</button>
<button id="btn-reset" style="background: transparent; color: #9ca3af; border: 1px solid #4b5563; padding: 8px; border-radius: 8px; cursor: pointer; font-size: 11px; transition: all 0.2s;">Сбросить счетчик</button>
</div>
`;
document.body.appendChild(panel);
// Переменные состояния
let isScrolling = false;
let scrollTimer = null;
let lastCollectedCount = 0;
let noNewItemsStreak = 0;
const seenSkus = new Set();
const collectedProducts = [];
// Элементы интерфейса
const statusBadge = document.getElementById('scraper-status');
const countSpan = document.getElementById('product-count');
const toggleBtn = document.getElementById('btn-toggle-scroll');
const copyBtn = document.getElementById('btn-copy-data');
const resetBtn = document.getElementById('btn-reset');
// Функция парсинга карточек товаров
function scanVisibleProducts() {
const productLinks = document.querySelectorAll('a[href*="/product/"]');
productLinks.forEach(link => {
const href = link.getAttribute('href');
if (!href) return;
const skuMatch = href.match(/-(\d+)\/?(?:\?|$)/);
const sku = skuMatch ? skuMatch[1] : href.split('?')[0];
if (seenSkus.has(sku)) return;
let cardContainer = link.closest('[data-index]') || link.closest('.tile-root') || link.parentElement;
while (cardContainer && cardContainer !== document.body && !cardContainer.querySelector('img')) {
cardContainer = cardContainer.parentElement;
}
if (!cardContainer) return;
let name = "";
const nameEl = cardContainer.querySelector('span[class*="tsBody500Medium"], a[href*="/product/"] span, span[class*="tsBody"]');
if (nameEl) name = nameEl.innerText.trim();
if (!name || name.length < 5) {
name = link.innerText.trim();
}
if (!name || name.length < 4 || name.includes("₽") || name.includes("рейтинг")) return;
let price = "N/A";
const priceElements = Array.from(cardContainer.querySelectorAll('*')).filter(el => {
return el.children.length === 0 && el.innerText && (el.innerText.includes('₽') || el.innerText.includes(' ₽'));
});
if (priceElements.length > 0) {
price = priceElements[0].innerText.trim().replace(/\s+/g, ' ');
}
const cleanUrl = 'https://www.ozon.ru' + href.split('?')[0];
seenSkus.add(sku);
collectedProducts.push({ name, price, url: cleanUrl, sku });
});
countSpan.innerText = collectedProducts.length;
}
// Функция быстрой циклической прокрутки
function scrollPageStep() {
window.scrollBy(0, window.innerHeight * 0.85);
scanVisibleProducts();
if (collectedProducts.length === lastCollectedCount) {
noNewItemsStreak++;
} else {
noNewItemsStreak = 0;
lastCollectedCount = collectedProducts.length;
}
// Порог увеличен до 35 попыток (около 12 секунд ожидания при медленном интернете),
// чтобы дать Ozon время на загрузку данных на высокой скорости прокрутки.
if (noNewItemsStreak >= 35) {
pauseScrolling();
statusBadge.innerText = 'Завершено';
statusBadge.style.background = '#10b981';
statusBadge.style.color = '#fff';
alert(`Сбор завершен. Уникальных товаров собрано: ${collectedProducts.length}`);
}
}
function startScrolling() {
isScrolling = true;
toggleBtn.innerText = '⏸ Приостановить';
toggleBtn.style.background = '#ef4444';
statusBadge.innerText = 'Быстрый скролл';
statusBadge.style.background = '#2563eb';
// Быстрый интервал — 350 мс
scrollTimer = setInterval(scrollPageStep, 350);
}
function pauseScrolling() {
isScrolling = false;
toggleBtn.innerText = '▶ Быстрая прокрутка';
toggleBtn.style.background = '#2563eb';
statusBadge.innerText = 'Остановлен';
statusBadge.style.background = '#ef4444';
if (scrollTimer) {
clearInterval(scrollTimer);
scrollTimer = null;
}
}
// Обработчики кнопок
toggleBtn.addEventListener('click', () => {
if (isScrolling) {
pauseScrolling();
} else {
startScrolling();
}
});
copyBtn.addEventListener('click', () => {
if (collectedProducts.length === 0) {
alert("Нет данных для копирования. Пожалуйста, запустите прокрутку.");
return;
}
let tsvOutput = "Название товара\tЦена\tСсылка на товар\tSKU\n";
collectedProducts.forEach(p => {
tsvOutput += `${p.name}\t${p.price}\t${p.url}\t${p.sku}\n`;
});
if (typeof GM_setClipboard !== 'undefined') {
GM_setClipboard(tsvOutput);
alert(`Данные (${collectedProducts.length} шт.) успешно скопированы! Откройте Excel или Google Таблицы и вставьте их через Ctrl+V.`);
} else {
navigator.clipboard.writeText(tsvOutput).then(() => {
alert(`Данные (${collectedProducts.length} шт.) успешно скопированы!`);
}).catch(err => {
const fallbackArea = document.createElement('textarea');
fallbackArea.value = tsvOutput;
document.body.appendChild(fallbackArea);
fallbackArea.select();
document.execCommand('copy');
document.body.removeChild(fallbackArea);
alert(`Данные (${collectedProducts.length} шт.) скопированы (резервный метод).`);
});
}
});
resetBtn.addEventListener('click', () => {
if (confirm("Вы действительно хотите очистить результаты?")) {
seenSkus.clear();
collectedProducts.length = 0;
lastCollectedCount = 0;
noNewItemsStreak = 0;
countSpan.innerText = '0';
if (!isScrolling) {
statusBadge.innerText = 'Остановлен';
statusBadge.style.background = '#ef4444';
}
}
});
scanVisibleProducts();
})();