// ==UserScript==
// @name         멀티 검색 엔진 웹 크롤러
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  여러 검색 엔진의 결과를 크롤링하여 데이터를 수집하고 UI에 표시합니다.
// @author       Your Name
// @match        https://www.google.com/*
// @match        https://yandex.com/*
// @match        https://www.bing.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @connect      *
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 전역 변수
    let collectedData = [];
    let visitedUrls = new Set();
    let currentPage = 0;
    let isCrawling = false;
    let selectedEngine = 'Google'; // 기본 검색 엔진
    let totalPages = 0;
    let processedPages = 0;
    let searchQuery = '';
    let stopCrawlingFlag = false;
    let blacklistDomains = [];
    let whitelistDomains = [];

    // 검색 엔진별 설정
    const searchEngines = {
        'Google': {
            searchBoxSelector: 'input[name="q"]',
            searchButtonSelector: 'input[name="btnK"], button[type="submit"]',
            resultsSelector: 'div.g',
            siteNameSelector: 'h3',
            siteUrlSelector: 'a',
            siteDescriptionSelector: '.VwiC3b',
            nextPageSelector: '#pnnext',
        },
        'Yandex': {
            searchBoxSelector: 'input.input__control[name="text"]',
            searchButtonSelector: 'button.Button2_type_submit',
            resultsSelector: 'li.serp-item',
            siteNameSelector: 'a.link.organic__url',
            siteUrlSelector: 'a.link.organic__url',
            siteDescriptionSelector: 'div.text-container__text',
            nextPageSelector: 'a.link.pager__item_kind_next',
        },
        'Bing': {
            searchBoxSelector: 'input[name="q"]',
            searchButtonSelector: 'input[type="submit"], button[type="submit"]',
            resultsSelector: 'li.b_algo',
            siteNameSelector: 'h2',
            siteUrlSelector: 'h2 a',
            siteDescriptionSelector: '.b_caption p',
            nextPageSelector: 'a.sb_pagN',
        }
        // 추가 검색 엔진 설정 가능
    };

    // UI 생성 함수
    function createUI() {
        // 스타일 추가
        GM_addStyle(`
            #crawler-container {
                position: fixed;
                top: 10px;
                left: 10px;
                width: 600px;
                height: 700px;
                background-color: #f9f9f9;
                border: 1px solid #ccc;
                z-index: 10000;
                padding: 10px;
                overflow-y: auto;
                box-shadow: 0 0 10px rgba(0,0,0,0.1);
                font-family: Arial, sans-serif;
            }
            #crawler-container button.tab {
                flex: 1;
                padding: 10px;
                border: none;
                background-color: #f9f9f9;
                cursor: pointer;
                transition: background-color 0.3s;
            }
            #crawler-container button.tab.active {
                background-color: #ddd;
            }
            #crawler-container button.tab:hover {
                background-color: #ccc;
            }
            #crawler-container textarea#log-area {
                width: 100%;
                height: 500px;
                resize: none;
                padding: 5px;
                box-sizing: border-box;
            }
            #crawler-container table {
                width: 100%;
                border-collapse: collapse;
                margin-bottom: 10px;
            }
            #crawler-container table, #crawler-container th, #crawler-container td {
                border: 1px solid #ccc;
            }
            #crawler-container th, #crawler-container td {
                padding: 5px;
                text-align: left;
            }
            #crawler-container th {
                background-color: #f2f2f2;
            }
            #crawler-container div.modal-bg {
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background-color: rgba(0,0,0,0.5);
                display: flex;
                justify-content: center;
                align-items: center;
                z-index: 10001;
            }
            #crawler-container div.modal {
                width: 80%;
                height: 80%;
                background-color: #fff;
                border: 1px solid #ccc;
                padding: 20px;
                overflow-y: auto;
                position: relative;
            }
            #crawler-container div.modal button.close-btn {
                position: absolute;
                top: 10px;
                right: 10px;
                cursor: pointer;
                background: none;
                border: none;
                font-size: 16px;
            }
            #progress-percentage {
                position: fixed;
                top: 10px;
                left: 10px;
                width: 150px;
                height: 25px;
                background-color: #e0e0e0;
                border-radius: 5px;
                overflow: hidden;
                box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
                z-index: 10002;
            }
            #progress-percentage div {
                height: 100%;
                width: 0%;
                background-color: #76c7c0;
                transition: width 0.3s;
            }
            #progress-percentage div.complete {
                background-color: #4caf50;
            }
        `);

        // UI 컨테이너 생성
        const container = document.createElement('div');
        container.id = 'crawler-container';

        // 탭 메뉴
        const tabMenu = document.createElement('div');
        tabMenu.style.display = 'flex';
        tabMenu.style.marginBottom = '10px';

        const tabs = ['📜 로그', '📊 데이터', '⚙️ 설정'];
        tabs.forEach((tabName, index) => {
            const tab = document.createElement('button');
            tab.textContent = tabName;
            tab.classList.add('tab');
            if (index === 0) tab.classList.add('active');
            tab.dataset.index = index;
            tab.title = `${tabName} 탭 전환`;
            tab.addEventListener('click', () => switchTab(index));
            tabMenu.appendChild(tab);
        });

        container.appendChild(tabMenu);

        // 탭 콘텐츠
        const tabContent = document.createElement('div');
        tabContent.id = 'tab-content';
        container.appendChild(tabContent);

        // 크롤링 컨트롤 패널
        const controlPanel = document.createElement('div');
        controlPanel.style.marginBottom = '10px';
        controlPanel.style.borderTop = '1px solid #ccc';
        controlPanel.style.paddingTop = '10px';

        // 검색어 입력
        const searchInputLabel = document.createElement('label');
        searchInputLabel.textContent = '🔍 검색어: ';
        searchInputLabel.style.display = 'block';
        searchInputLabel.style.marginBottom = '5px';
        const searchInput = document.createElement('input');
        searchInput.type = 'text';
        searchInput.id = 'search-query';
        searchInput.style.width = '100%';
        searchInput.placeholder = '검색어를 입력하세요';
        searchInput.title = '검색어를 입력하세요';
        searchInputLabel.appendChild(searchInput);
        controlPanel.appendChild(searchInputLabel);

        // 시작 버튼
        const startButton = document.createElement('button');
        startButton.textContent = '🚀 크롤링 시작';
        startButton.style.padding = '10px';
        startButton.style.marginRight = '10px';
        startButton.style.cursor = 'pointer';
        startButton.title = '크롤링을 시작합니다.';
        startButton.addEventListener('click', () => {
            const query = document.getElementById('search-query').value.trim();
            if (query) {
                searchQuery = query;
                startCrawling(query);
            } else {
                alert('검색어를 입력해주세요.');
            }
        });
        controlPanel.appendChild(startButton);

        // 중지 버튼
        const stopButton = document.createElement('button');
        stopButton.textContent = '⏹️ 크롤링 중지';
        stopButton.style.padding = '10px';
        stopButton.style.cursor = 'pointer';
        stopButton.title = '크롤링을 중지합니다.';
        stopButton.addEventListener('click', () => {
            if (isCrawling) {
                stopCrawlingFlag = true;
                log('크롤링 중지 요청됨.', 'warn');
            }
        });
        controlPanel.appendChild(stopButton);

        // 진행률 표시
        const progressContainer = document.createElement('div');
        progressContainer.id = 'progress-bar-container';
        progressContainer.style.marginTop = '10px';
        progressContainer.title = '크롤링 진행률';
        const progressBar = document.createElement('div');
        progressBar.id = 'progress-bar';
        progressBar.style.width = '0%';
        progressBar.style.height = '20px';
        progressBar.style.backgroundColor = '#76c7c0';
        progressContainer.appendChild(progressBar);
        controlPanel.appendChild(progressContainer);

        container.appendChild(controlPanel);

        document.body.appendChild(container);

        // 추가: 진행률 퍼센트 표시
        const progressPercentage = document.createElement('div');
        progressPercentage.id = 'progress-percentage';
        const progressInner = document.createElement('div');
        progressPercentage.appendChild(progressInner);
        document.body.appendChild(progressPercentage);

        // 초기 탭
        renderLogs();
    }

    // 탭 전환 함수
    function switchTab(index) {
        const tabs = document.querySelectorAll('#crawler-container button.tab');
        tabs.forEach((tab, i) => {
            if (i === index) {
                tab.classList.add('active');
            } else {
                tab.classList.remove('active');
            }
        });

        switch(index) {
            case 0:
                renderLogs();
                break;
            case 1:
                renderData();
                break;
            case 2:
                renderSettings();
                break;
        }
    }

    // 로그 탭 렌더링
    function renderLogs() {
        const content = document.getElementById('tab-content');
        content.innerHTML = '';
        const logArea = document.createElement('textarea');
        logArea.id = 'log-area';
        logArea.readOnly = true;
        logArea.title = '진행 로그';
        content.appendChild(logArea);
    }

    // 데이터 탭 렌더링
    function renderData() {
        const content = document.getElementById('tab-content');
        content.innerHTML = '';

        // 검색 필드
        const searchField = document.createElement('input');
        searchField.type = 'text';
        searchField.id = 'data-search';
        searchField.placeholder = '데이터를 검색하세요';
        searchField.style.width = '100%';
        searchField.style.marginBottom = '10px';
        searchField.title = '데이터 검색 필드';
        searchField.addEventListener('input', filterTable);
        content.appendChild(searchField);

        // 데이터 테이블
        const table = document.createElement('table');
        table.id = 'data-table';

        // 헤더
        const header = document.createElement('tr');
        ['사이트 이름', '주소', '설명', '분류', '텍스트 내용', 'HTML 내용'].forEach(text => {
            const th = document.createElement('th');
            th.textContent = text;
            header.appendChild(th);
        });
        table.appendChild(header);

        // 데이터
        collectedData.forEach(item => {
            const row = document.createElement('tr');

            // 사이트 이름
            const nameTd = document.createElement('td');
            nameTd.textContent = item.name;
            row.appendChild(nameTd);

            // 주소
            const urlTd = document.createElement('td');
            const urlLink = document.createElement('a');
            urlLink.href = item.url;
            urlLink.textContent = item.url;
            urlLink.target = '_blank';
            urlTd.appendChild(urlLink);
            row.appendChild(urlTd);

            // 설명
            const descTd = document.createElement('td');
            descTd.textContent = item.description;
            row.appendChild(descTd);

            // 분류
            const catTd = document.createElement('td');
            catTd.textContent = item.category;
            row.appendChild(catTd);

            // 텍스트 내용
            const textTd = document.createElement('td');
            textTd.textContent = item.description;
            row.appendChild(textTd);

            // HTML 내용 (모달 링크)
            const htmlTd = document.createElement('td');
            const htmlLink = document.createElement('a');
            htmlLink.href = '#';
            htmlLink.textContent = '보기';
            htmlLink.style.color = 'blue';
            htmlLink.style.textDecoration = 'underline';
            htmlLink.title = 'HTML 내용을 확인하려면 클릭하세요';
            htmlLink.addEventListener('click', (e) => {
                e.preventDefault();
                showHTMLModal(item.htmlContent);
            });
            htmlTd.appendChild(htmlLink);
            row.appendChild(htmlTd);

            table.appendChild(row);
        });

        content.appendChild(table);

        // 엑셀 다운로드 버튼
        const exportExcelButton = document.createElement('button');
        exportExcelButton.textContent = '📥 엑셀 다운로드';
        exportExcelButton.style.padding = '10px';
        exportExcelButton.style.marginRight = '10px';
        exportExcelButton.style.cursor = 'pointer';
        exportExcelButton.title = '데이터를 엑셀 파일로 다운로드';
        exportExcelButton.addEventListener('click', exportToExcel);
        content.appendChild(exportExcelButton);

        // JSON 다운로드 버튼
        const exportJSONButton = document.createElement('button');
        exportJSONButton.textContent = '📄 JSON 다운로드';
        exportJSONButton.style.padding = '10px';
        exportJSONButton.style.cursor = 'pointer';
        exportJSONButton.title = '데이터를 JSON 파일로 다운로드';
        exportJSONButton.addEventListener('click', exportToJSON);
        content.appendChild(exportJSONButton);
    }

    // 설정 탭 렌더링
    function renderSettings() {
        const content = document.getElementById('tab-content');
        content.innerHTML = '';

        const settingsForm = document.createElement('form');

        // 검색 엔진 선택
        const engineLabel = document.createElement('label');
        engineLabel.textContent = '🔍 검색 엔진 선택: ';
        engineLabel.style.display = 'block';
        engineLabel.style.marginBottom = '5px';
        const engineSelect = document.createElement('select');
        engineSelect.id = 'search-engine';
        engineSelect.title = '크롤링에 사용할 검색 엔진을 선택합니다.';
        for (let engine in searchEngines) {
            const option = document.createElement('option');
            option.value = engine;
            option.textContent = engine;
            if (engine === selectedEngine) {
                option.selected = true;
            }
            engineSelect.appendChild(option);
        }
        engineLabel.appendChild(engineSelect);
        settingsForm.appendChild(engineLabel);
        settingsForm.appendChild(document.createElement('br'));

        // 블랙리스트 도메인
        const blacklistLabel = document.createElement('label');
        blacklistLabel.textContent = '🚫 블랙리스트 도메인 (comma separated): ';
        blacklistLabel.style.display = 'block';
        blacklistLabel.style.marginBottom = '5px';
        const blacklistInput = document.createElement('input');
        blacklistInput.type = 'text';
        blacklistInput.id = 'blacklist-domains';
        blacklistInput.style.width = '100%';
        blacklistInput.placeholder = '예: ads.example.com, spam.example.com';
        blacklistInput.title = '크롤링에서 제외할 도메인들을 쉼표로 구분하여 입력합니다.';
        blacklistLabel.appendChild(blacklistInput);
        settingsForm.appendChild(blacklistLabel);
        settingsForm.appendChild(document.createElement('br'));

        // 화이트리스트 도메인
        const whitelistLabel = document.createElement('label');
        whitelistLabel.textContent = '✅ 화이트리스트 도메인 (comma separated): ';
        whitelistLabel.style.display = 'block';
        whitelistLabel.style.marginBottom = '5px';
        const whitelistInput = document.createElement('input');
        whitelistInput.type = 'text';
        whitelistInput.id = 'whitelist-domains';
        whitelistInput.style.width = '100%';
        whitelistInput.placeholder = '예: edu.example.com, blog.example.com';
        whitelistInput.title = '크롤링에서 허용할 도메인들을 쉼표로 구분하여 입력합니다.';
        whitelistLabel.appendChild(whitelistInput);
        settingsForm.appendChild(whitelistLabel);
        settingsForm.appendChild(document.createElement('br'));

        // 속도 조절 (딜레이 설정)
        const delayLabel = document.createElement('label');
        delayLabel.textContent = '⏱️ 크롤링 딜레이 (밀리초): ';
        delayLabel.style.display = 'block';
        delayLabel.style.marginBottom = '5px';
        const delayInput = document.createElement('input');
        delayInput.type = 'number';
        delayInput.id = 'crawl-delay';
        delayInput.style.width = '100%';
        delayInput.placeholder = '예: 1000';
        delayInput.title = '크롤링 시 각 요청 사이의 대기 시간을 밀리초 단위로 입력하세요.';
        delayLabel.appendChild(delayInput);
        settingsForm.appendChild(delayLabel);
        settingsForm.appendChild(document.createElement('br'));

        // 저장 버튼
        const saveButton = document.createElement('button');
        saveButton.type = 'button';
        saveButton.textContent = '💾 설정 저장 (Ctrl+S)';
        saveButton.style.padding = '10px';
        saveButton.style.marginTop = '10px';
        saveButton.style.cursor = 'pointer';
        saveButton.title = '설정을 저장합니다.';
        saveButton.addEventListener('click', saveSettings);
        settingsForm.appendChild(saveButton);

        content.appendChild(settingsForm);

        // 설정 불러오기
        loadSettings();
    }

    // 설정 저장 함수
    function saveSettings() {
        selectedEngine = document.getElementById('search-engine').value;
        blacklistDomains = document.getElementById('blacklist-domains').value.split(',').map(s => s.trim()).filter(s => s);
        whitelistDomains = document.getElementById('whitelist-domains').value.split(',').map(s => s.trim()).filter(s => s);
        const crawlDelay = parseInt(document.getElementById('crawl-delay').value) || 1000;
        localStorage.setItem('selectedEngine', selectedEngine);
        localStorage.setItem('blacklistDomains', JSON.stringify(blacklistDomains));
        localStorage.setItem('whitelistDomains', JSON.stringify(whitelistDomains));
        localStorage.setItem('crawlDelay', crawlDelay);
        log(`설정이 저장되었습니다. 선택된 검색 엔진: ${selectedEngine}`);
    }

    // 설정 불러오기 함수
    function loadSettings() {
        selectedEngine = localStorage.getItem('selectedEngine') || 'Google';
        blacklistDomains = JSON.parse(localStorage.getItem('blacklistDomains')) || [];
        whitelistDomains = JSON.parse(localStorage.getItem('whitelistDomains')) || [];
        const crawlDelay = parseInt(localStorage.getItem('crawlDelay')) || 1000;

        // 설정이 저장된 경우 UI에 반영
        document.getElementById('search-engine')?.value = selectedEngine;
        document.getElementById('blacklist-domains')?.value = blacklistDomains.join(', ');
        document.getElementById('whitelist-domains')?.value = whitelistDomains.join(', ');
        document.getElementById('crawl-delay')?.value = crawlDelay;
    }

    // 로그 함수
    function log(message, type = 'info') {
        const logArea = document.getElementById('log-area');
        if (logArea) {
            const timestamp = new Date().toLocaleTimeString();
            logArea.value += `[${timestamp}] [${type.toUpperCase()}] ${message}\n`;
            logArea.scrollTop = logArea.scrollHeight;
        }
        switch(type) {
            case 'info':
                console.info(message);
                break;
            case 'warn':
                console.warn(message);
                break;
            case 'error':
                console.error(message);
                break;
            default:
                console.log(message);
        }
    }

    // 크롤링 시작 함수
    async function startCrawling(query) {
        if (isCrawling) {
            log('이미 크롤링이 진행 중입니다.', 'warn');
            return;
        }
        isCrawling = true;
        stopCrawlingFlag = false;
        collectedData = [];
        visitedUrls = new Set();
        currentPage = 1;
        processedPages = 0;
        log(`크롤링 시작: "${query}"`);

        // 설정 불러오기
        loadSettings();
        const crawlDelay = parseInt(localStorage.getItem('crawlDelay')) || 1000;

        // Reset progress
        totalPages = estimateTotalPages();
        updateProgress();

        // 검색어 입력 및 검색 실행
        const engineConfig = searchEngines[selectedEngine];

        const searchInput = document.querySelector(engineConfig.searchBoxSelector);
        const searchButton = document.querySelector(engineConfig.searchButtonSelector);

        if (searchInput) {
            searchInput.value = query;
            log('검색어 입력 완료.', 'info');
            if (searchButton) {
                searchButton.click();
                log('검색 실행.', 'info');
                await waitForResults(engineConfig);
            } else {
                log('검색 버튼을 찾을 수 없습니다.', 'error');
                isCrawling = false;
            }
        } else {
            log('검색창을 찾을 수 없습니다.', 'error');
            isCrawling = false;
        }
    }

    // 검색 결과 대기 및 처리
    function waitForResults(engineConfig) {
        return new Promise((resolve, reject) => {
            const resultsSelector = engineConfig.resultsSelector;
            const interval = setInterval(() => {
                const results = document.querySelectorAll(resultsSelector);
                if (results.length > 0) {
                    clearInterval(interval);
                    log(`검색 결과 ${results.length}개 발견. 페이지 ${currentPage} 처리 시작.`, 'info');
                    processResults(results, engineConfig).then(() => resolve()).catch(err => reject(err));
                }
            }, 1000);
        });
    }

    // 검색 결과 처리
    async function processResults(results, engineConfig) {
        for (let result of results) {
            if (stopCrawlingFlag) {
                log('크롤링이 중지되었습니다.', 'warn');
                isCrawling = false;
                updateProgress(true);
                return;
            }

            // 사이트 이름, URL, 설명 추출
            let siteName = '';
            let siteUrl = '';
            let siteDescription = '';
            let htmlContent = '';

            try {
                siteName = result.querySelector(engineConfig.siteNameSelector)?.innerText || 'N/A';
                siteUrl = result.querySelector(engineConfig.siteUrlSelector)?.href || 'N/A';
                siteDescription = result.querySelector(engineConfig.siteDescriptionSelector)?.innerText || 'N/A';
                htmlContent = result.innerHTML || 'N/A';
            } catch (error) {
                log(`데이터 추출 실패: ${error}`, 'error');
                continue;
            }

            // 중복 검사
            if (visitedUrls.has(siteUrl)) {
                log(`중복된 URL 발견: ${siteUrl}`, 'warn');
                continue;
            }

            // 블랙리스트/화이트리스트 검사
            const domain = getDomain(siteUrl);
            if (whitelistDomains.length > 0 && !whitelistDomains.includes(domain)) {
                log(`화이트리스트에 없는 도메인: ${domain}`, 'warn');
                continue;
            }
            if (blacklistDomains.includes(domain)) {
                log(`블랙리스트에 있는 도메인: ${domain}`, 'warn');
                continue;
            }

            // 광고 사이트 필터링 (간단한 예시)
            if (isAdSite(siteUrl)) {
                log(`광고 사이트로 필터링된 도메인: ${domain}`, 'warn');
                continue;
            }

            visitedUrls.add(siteUrl);

            // 콘텐츠 품질 평가
            const qualityScore = evaluateContentQuality(siteDescription);

            // 자동 키워드 분류
            const category = classifyKeywords(siteDescription);

            collectedData.push({
                name: siteName,
                url: siteUrl,
                description: siteDescription,
                category: category,
                quality: qualityScore,
                htmlContent: htmlContent
            });

            log(`수집 완료: ${siteName} (${siteUrl})`, 'info');

            // 데이터 탭이 활성화되어 있다면 실시간 업데이트
            const activeTab = document.querySelector('#crawler-container button.tab.active');
            if (activeTab && activeTab.textContent.includes('데이터')) {
                renderData();
            }

            // 진행률 업데이트
            processedPages += 1;
            updateProgress();

            // 속도 조절
            const crawlDelay = parseInt(localStorage.getItem('crawlDelay')) || 1000;
            await delay(crawlDelay);
        }

        // 다음 페이지으로 이동
        goToNextPage(engineConfig);
    }

    // 다음 페이지으로 이동 함수
    function goToNextPage(engineConfig) {
        if (stopCrawlingFlag) {
            log('크롤링이 중지되었습니다.', 'warn');
            isCrawling = false;
            updateProgress(true);
            return;
        }

        const nextPageButton = document.querySelector(engineConfig.nextPageSelector);

        if (nextPageButton && !nextPageButton.classList.contains('disabled') && isCrawling) {
            currentPage += 1;
            log(`다음 페이지으로 이동: 페이지 ${currentPage}`, 'info');
            nextPageButton.click();
            waitForResults(engineConfig);
        } else {
            log('크롤링 완료.', 'info');
            isCrawling = false;
            updateProgress(true);
            renderData();
            exportToExcel();
        }
    }

    // 도메인 추출 함수
    function getDomain(url) {
        try {
            return (new URL(url)).hostname.replace('www.', '');
        } catch {
            return '';
        }
    }

    // 광고 사이트 간단 필터링 함수 (예시)
    function isAdSite(domain) {
        const adKeywords = ['ads', 'advertisement', 'promo'];
        return adKeywords.some(keyword => domain.includes(keyword));
    }

    // 콘텐츠 품질 평가 함수 (단어 수 기준)
    function evaluateContentQuality(text) {
        const wordCount = text.split(/\s+/).length;
        return wordCount > 100 ? '높음' : '낮음';
    }

    // 키워드 기반 자동 분류 함수 (예시)
    function classifyKeywords(text) {
        const keywords = {
            '교육': ['학교', '교육', '학습', '대학교', '교육기관'],
            '테크': ['기술', '소프트웨어', '하드웨어', 'IT', '컴퓨터'],
            '엔터테인먼트': ['게임', '영화', '음악', '엔터테인먼트', '드라마']
        };
        for (let category in keywords) {
            for (let keyword of keywords[category]) {
                if (text.includes(keyword)) {
                    return category;
                }
            }
        }
        return '기타';
    }

    // 딜레이 함수
    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    // 엑셀으로 내보내기 함수
    function exportToExcel() {
        if (collectedData.length === 0) {
            log('내보낼 데이터가 없습니다.', 'warn');
            return;
        }
        const wb = XLSX.utils.book_new();
        const wsData = collectedData.map(item => ({
            '사이트 이름': item.name,
            '주소': item.url,
            '설명': item.description,
            '분류': item.category,
            '품질': item.quality
        }));
        const ws = XLSX.utils.json_to_sheet(wsData);
        XLSX.utils.book_append_sheet(wb, ws, "Data");
        XLSX.writeFile(wb, "크롤링_결과.xlsx");
        log('데이터가 엑셀 파일로 저장되었습니다.', 'info');
    }

    // JSON으로 내보내기 함수
    function exportToJSON() {
        if (collectedData.length === 0) {
            log('내보낼 데이터가 없습니다.', 'warn');
            return;
        }
        const dataStr = JSON.stringify(collectedData, null, 2);
        const blob = new Blob([dataStr], { type: "application/json" });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = '크롤링_결과.json';
        a.click();
        URL.revokeObjectURL(url);
        log('데이터가 JSON 파일로 저장되었습니다.', 'info');
    }

    // HTML 내용 모달 창 생성 함수
    function showHTMLModal(htmlContent) {
        // 모달 백그라운드
        const modalBg = document.createElement('div');
        modalBg.classList.add('modal-bg');

        // 모달 창
        const modal = document.createElement('div');
        modal.classList.add('modal');

        // 닫기 버튼
        const closeBtn = document.createElement('button');
        closeBtn.textContent = '✖️ 닫기';
        closeBtn.classList.add('close-btn');
        closeBtn.addEventListener('click', () => {
            document.body.removeChild(modalBg);
        });
        modal.appendChild(closeBtn);

        // HTML 내용 표시
        const pre = document.createElement('pre');
        pre.textContent = htmlContent;
        modal.appendChild(pre);

        modalBg.appendChild(modal);
        document.body.appendChild(modalBg);
    }

    // 중복된 테이블 필터 함수
    function filterTable() {
        const filter = document.getElementById('data-search').value.toLowerCase();
        const rows = document.querySelectorAll('#data-table tr');
        rows.forEach((row, index) => {
            if (index === 0) return; // 헤더는 제외
            const cells = row.querySelectorAll('td');
            let match = false;
            cells.forEach(cell => {
                if (cell.textContent.toLowerCase().includes(filter)) {
                    match = true;
                }
            });
            row.style.display = match ? '' : 'none';
        });
    }

    // 진행률 업데이트 함수
    function updateProgress(isComplete = false) {
        const progressBar = document.getElementById('progress-bar');
        const progressPercentageDiv = document.querySelector('#progress-percentage div');
        if (!progressBar || !progressPercentageDiv) return;
        if (isComplete) {
            progressBar.style.width = '100%';
            progressBar.style.backgroundColor = '#4caf50';
            progressPercentageDiv.style.width = '100%';
            progressPercentageDiv.classList.add('complete');
            progressPercentageDiv.title = '크롤링 완료';
        } else {
            if (totalPages === 0) return;
            const percent = ((processedPages / totalPages) * 100).toFixed(2);
            progressBar.style.width = `${percent}%`;
            progressPercentageDiv.style.width = `${percent}%`;
            progressPercentageDiv.title = `진행률: ${percent}%`;
        }
    }

    // 총 페이지 수 추정 함수 (예시, 실제로는 검색 결과 수에 따라 달라질 수 있음)
    function estimateTotalPages() {
        // 예를 들어, 최대 10페이지로 제한
        return 10;
    }

    // 키보드 단축키 지원
    function setupKeyboardShortcuts() {
        document.addEventListener('keydown', function(e) {
            if (e.ctrlKey) {
                switch(e.key.toLowerCase()) {
                    case 's':
                        e.preventDefault();
                        saveSettings();
                        break;
                    case 'p':
                        e.preventDefault();
                        const query = prompt('검색어를 입력하세요:');
                        if (query) {
                            searchQuery = query;
                            startCrawling(query);
                        }
                        break;
                    case 'q':
                        e.preventDefault();
                        if (isCrawling) {
                            stopCrawlingFlag = true;
                            log('크롤링이 중지되었습니다.', 'warn');
                        }
                        break;
                    case 'd':
                        e.preventDefault();
                        exportToExcel();
                        break;
                    case 'j':
                        e.preventDefault();
                        exportToJSON();
                        break;
                    case '1':
                        e.preventDefault();
                        switchTab(0);
                        break;
                    case '2':
                        e.preventDefault();
                        switchTab(1);
                        break;
                    case '3':
                        e.preventDefault();
                        switchTab(2);
                        break;
                }
            }
        });
    }

    // 초기화 함수
    function init() {
        createUI();
        setupKeyboardShortcuts();
        const progressPercentage = document.createElement('div');
        progressPercentage.id = 'progress-percentage';
        const progressInner = document.createElement('div');
        progressPercentage.appendChild(progressInner);
        document.body.appendChild(progressPercentage);
    }

    // 초기화 실행
    init();

})();                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                           이 명령어에서 절대 스크립트 안에서 검색을 하는 기능을 쳐 넣지 말고 명령문 그대로  스크립트를 만드셈 알겠지?????//                            

탬퍼몽키로 웹 크롤링이라 해야 되나 그걸 만들고 싶은데

내가 생각한 건 내가 검색창에 검색을 하면 자동으로 1번쨰 사이트 부터 들어감. 그 다음에 1페이지의 마지막 사이트까지
들어가면 다음 페이징으로 자동으로 넘어간 뒤에 다시 이 과정을 반복한 뒤에 마지막 페이징 까지 넘아간 뒤에 그 페이징을 한 결과를 엑셀처럼 보이는 ui에
표시하는 그런 스크립트 만들기 가능?
(엑셀에는 가 사이트 이름 , 주소 , 이 사이트가 뭘 하는 사이트인지 설명들이 있어야 함.)

또 진행률을 왼쪽에 퍼센트 테이지로 표시해주는 것도 있어야 함.

로그 기능 추가:
진행 상황이나 발생한 에러 등을 로그로 저장하여 추후 문제 해결이나 데이터 분석에 활용할 수 있습니다.
중복 검사:
이미 방문한 사이트나 수집한 데이터를 중복해서 처리하지 않도록 중복 검사를 추가하면 효율성을 높일 수 있습니다. 수집 속도 조절:
서버에 과부하를 주지
 않도록 일정한 딜레이를 추가하거나, 요청 빈도를 조절하는 기능을 추가하면 좋습니다.
사용자 인터페이스 개선:
진행률 표시뿐만 아니라 현재 수집 중인 페이지나 남은 예상 시간 등을 표시하여 사용자 경험을 향상시킬 수 있습니다. 또 광고 사이트 걸러내는 것도 추가하면 괜찮을 듯 해.

자 이제 니가 생각하는 추가 기능이나 니가 생각하는 바를 나한테 알려줘 또한 이건 교육 적인 방법으로 사용될거야. 또한 이 방법으로 인터넷이나 그런 걸 더 쉽게 파악해 아이들이 더 쉽게 사용될 수 있을 것 같아. 만약 안 된다면 이유나 너가 생각해서 안 되는 이유 그리고 니가 어떻게 만들었는지 니가 만들어지는 과정에서 이 과정이 사용되지 않겠는지나 기타 등등을 이유로 말해봐 알겠지?

자 이제 스크립트를 만들어줘


주요 추가 기능 설명
탭 인터페이스
탭 메뉴: 📜 로그, 📊 데이터, ⚙️ 설정의 세 가지 탭으로 구성되어 있습니다.
탭 이벤트: 탭을 클릭하거나 Ctrl+1, Ctrl+2, Ctrl+3 단축키를 사용하여 각 탭으로 전환할 수 있습니다.
스타일: 활성화된 탭은 색상이 다르며, 마우스 호버 시 색상이 변경됩니다.
HTML 내용 수집 및 텍스트 추출
각 검색 결과의 전체 HTML 내용과 주요 텍스트 내용을 비동기로 수집합니다.
수집된 내용을 데이터 테이블의 🗨️ 텍스트 내용과 📄 HTML 내용 열에 표시합니다.
📄 HTML 내용 셀을 클릭하면 모달 창으로 전체 HTML 내용을 확인할 수 있습니다.
툴팁 추가
버튼과 입력 필드에 title 속성을 추가하여 툴팁을 제공합니다. 이를 통해 사용자가 각 요소의 기능을 직관적으로 이해할 수 있습니다.
키보드 단축키 지원
기본 단축키:
Ctrl+S: 설정 저장
Ctrl+P: 크롤링 시작
Ctrl+Q: 크롤링 중지
Ctrl+D: CSV 다운로드
Ctrl+J: JSON 다운로드
Ctrl+1: 로그 탭 활성화
Ctrl+2: 데이터 탭 활성화
Ctrl+3: 설정 탭 활성화
검색 기능 강화
데이터 테이블 상단의 검색 입력 필드에 키워드를 입력하면 실시간으로 데이터를 필터링할 수 있습니다.
filterTable 함수가 입력된 키워드에 따라 관련 없는 행을 숨기거나 표시합니다.
실시간 데이터 업데이트 표시
데이터를 수집할 때마다 테이블과 차트가 실시간으로 업데이트됩니다.
높은 품질의 콘텐츠는 테이블에 더 높은 점수로 표시됩니다.
콘텐츠 품질 평가
evaluateContentQuality 함수는 페이지의 텍스트 단어 수를 기준으로 간단한 품질 점수를 계산합니다.
점수는 🗨️ 텍스트 내용 열 아래에 표시되며, info 유형의 로그로 출력됩니다.
자동 키워드 분류
classifyKeywords 함수는 설명 내용에 포함된 특정 키워드를 기반으로 콘텐츠를 자동으로 카테고리 분류합니다.
카테고리는 🗂️ 분류 열에 표시됩니다.
사용자 정의 블랙리스트/화이트리스트
설정 탭에서 🚫 블랙리스트 도메인과 ✅ 화이트리스트 도메인을 관리할 수 있습니다.
화이트리스트가 설정된 경우, 화이트리스트 도메인에 속한 사이트만 크롤링이 허용됩니다.
블랙리스트 도메인은 추가로 제한되며, 해당 도메인에서 수집된 데이터는 필터링됩니다.