Ozon Scraper

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

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

Advertisement:

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

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