Ozon Scraper

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

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.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

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