Ozon Scraper

Быстро прокручивает страницу категории Ozon и копирует результаты в буфер обмена.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

Advertisement:

// ==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();
})();