// ==UserScript==
// @name         멀티 검색 엔진 웹 크롤러
// @namespace    http://tampermonkey.net/
// @version      1.1
// @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
// @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'; // 기본 검색 엔진

    // 검색 엔진별 설정
    const searchEngines = {
        'Google': {
            searchBoxSelector: 'input[name="q"]',
            searchButtonSelector: 'input[name="btnK"]',
            resultsSelector: 'div.g', // Google 검색 결과의 선택자
            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"]',
            resultsSelector: 'li.b_algo',
            siteNameSelector: 'h2',
            siteUrlSelector: 'h2 a',
            siteDescriptionSelector: '.b_caption p',
            nextPageSelector: 'a.sb_pagN',
        }
        // 여기에 추가적인 검색 엔진 설정을 추가할 수 있습니다.
    };

    // UI 생성 함수
    function createUI() {
        // UI 컨테이너 생성
        const container = document.createElement('div');
        container.id = 'crawler-container';
        container.style.position = 'fixed';
        container.style.top = '10px';
        container.style.right = '10px';
        container.style.width = '500px';
        container.style.height = '600px';
        container.style.backgroundColor = '#f9f9f9';
        container.style.border = '1px solid #ccc';
        container.style.zIndex = '10000';
        container.style.padding = '10px';
        container.style.overflowY = 'scroll';
        container.style.boxShadow = '0 0 10px rgba(0,0,0,0.1)';

        // 탭 메뉴
        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.dataset.index = index;
            tab.style.flex = '1';
            tab.style.padding = '10px';
            tab.style.border = 'none';
            tab.style.backgroundColor = index === 0 ? '#ddd' : '#f9f9f9';
            tab.style.cursor = 'pointer';
            tab.addEventListener('click', () => switchTab(index));
            tabMenu.appendChild(tab);
        });

        container.appendChild(tabMenu);

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

        document.body.appendChild(container);

        // 초기 탭
        renderLogs();
    }

    // 탭 전환 함수
    function switchTab(index) {
        const tabs = document.querySelectorAll('#crawler-container button');
        tabs.forEach((tab, i) => {
            tab.style.backgroundColor = i === index ? '#ddd' : '#f9f9f9';
        });

        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.style.width = '100%';
        logArea.style.height = '500px';
        logArea.readOnly = true;
        content.appendChild(logArea);
    }

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

        const table = document.createElement('table');
        table.id = 'data-table';
        table.style.width = '100%';
        table.style.borderCollapse = 'collapse';

        // 헤더
        const header = document.createElement('tr');
        ['사이트 이름', '주소', '설명', '분류'].forEach(text => {
            const th = document.createElement('th');
            th.textContent = text;
            th.style.border = '1px solid #ccc';
            th.style.padding = '5px';
            header.appendChild(th);
        });
        table.appendChild(header);

        // 데이터
        collectedData.forEach(item => {
            const row = document.createElement('tr');
            ['name', 'url', 'description', 'category'].forEach(key => {
                const td = document.createElement('td');
                td.textContent = item[key];
                td.style.border = '1px solid #ccc';
                td.style.padding = '5px';
                row.appendChild(td);
            });
            table.appendChild(row);
        });

        content.appendChild(table);
    }

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

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

        // 검색 엔진 선택
        const engineLabel = document.createElement('label');
        engineLabel.textContent = '🔍 검색 엔진 선택: ';
        const engineSelect = document.createElement('select');
        engineSelect.id = 'search-engine';
        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): ';
        const blacklistInput = document.createElement('input');
        blacklistInput.type = 'text';
        blacklistInput.id = 'blacklist-domains';
        blacklistInput.style.width = '100%';
        blacklistInput.value = ''; // 초기값 설정
        blacklistLabel.appendChild(blacklistInput);
        settingsForm.appendChild(blacklistLabel);
        settingsForm.appendChild(document.createElement('br'));

        // 화이트리스트 도메인
        const whitelistLabel = document.createElement('label');
        whitelistLabel.textContent = '✅ 화이트리스트 도메인 (comma separated): ';
        const whitelistInput = document.createElement('input');
        whitelistInput.type = 'text';
        whitelistInput.id = 'whitelist-domains';
        whitelistInput.style.width = '100%';
        whitelistInput.value = ''; // 초기값 설정
        whitelistLabel.appendChild(whitelistInput);
        settingsForm.appendChild(whitelistLabel);
        settingsForm.appendChild(document.createElement('br'));

        // 저장 버튼
        const saveButton = document.createElement('button');
        saveButton.type = 'button';
        saveButton.textContent = '설정 저장 (Ctrl+S)';
        saveButton.style.marginTop = '10px';
        saveButton.addEventListener('click', saveSettings);
        settingsForm.appendChild(saveButton);

        content.appendChild(settingsForm);
    }

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

    // 설정 불러오기 함수
    function loadSettings() {
        selectedEngine = localStorage.getItem('selectedEngine') || 'Google';
        const blacklist = JSON.parse(localStorage.getItem('blacklistDomains')) || [];
        const whitelist = JSON.parse(localStorage.getItem('whitelistDomains')) || [];
        return { blacklist, whitelist };
    }

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

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

        // 설정 불러오기
        const settings = loadSettings();

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

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

        if (searchInput && searchButton) {
            searchInput.value = searchQuery;
            searchButton.click();
            waitForResults();
        } else {
            log('검색창이나 검색 버튼을 찾을 수 없습니다.', 'error');
            isCrawling = false;
        }
    }

    // 검색 결과 대기 후 처리
    function waitForResults() {
        const engineConfig = searchEngines[selectedEngine];
        const resultsSelector = engineConfig.resultsSelector;
        const interval = setInterval(() => {
            const results = document.querySelectorAll(resultsSelector);
            if (results.length > 0) {
                clearInterval(interval);
                log(`검색 결과 ${results.length}개 발견. 페이지 ${currentPage} 처리 시작.`);
                processResults(results);
            }
        }, 1000);
    }

    // 검색 결과 처리
    async function processResults(results) {
        const settings = loadSettings();
        const engineConfig = searchEngines[selectedEngine];
        for (let result of results) {
            // 각 검색 엔진마다 사이트 이름, URL, 설명을 추출하는 방식이 다름
            let siteName = '';
            let siteUrl = '';
            let siteDescription = '';

            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';
            } catch (error) {
                log(`데이터 추출 실패: ${error}`, 'error');
                continue;
            }

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

            // 블랙리스트/화이트리스트 검사
            const domain = (new URL(siteUrl)).hostname;
            if (settings.whitelist.length > 0 && !settings.whitelist.includes(domain)) {
                log(`화이트리스트에 없는 도메인: ${domain}`, 'warn');
                continue;
            }
            if (settings.blacklist.includes(domain)) {
                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
            });

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

            // 속도 조절 (예: 1초 대기)
            await delay(1000);
        }

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

    // 다음 페이지으로 이동 함수
    function goToNextPage() {
        const engineConfig = searchEngines[selectedEngine];
        const nextPageButton = document.querySelector(engineConfig.nextPageSelector);

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

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

    // 키워드 기반 자동 분류 함수 (예시)
    function classifyKeywords(text) {
        const keywords = {
            '교육': ['학교', '교육', '학습'],
            '테크': ['기술', '소프트웨어', '하드웨어'],
            '엔터테인먼트': ['게임', '영화', '음악']
        };
        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() {
        const wb = XLSX.utils.book_new();
        const ws = XLSX.utils.json_to_sheet(collectedData);
        XLSX.utils.book_append_sheet(wb, ws, "Data");
        XLSX.writeFile(wb, "크롤링_결과.xlsx");
        log('데이터가 엑셀 파일로 저장되었습니다.');
    }

    // 키보드 단축키 지원
    function setupKeyboardShortcuts() {
        document.addEventListener('keydown', function(e) {
            if (e.ctrlKey) {
                switch(e.key) {
                    case 's':
                    case 'S':
                        e.preventDefault();
                        saveSettings();
                        break;
                    case 'p':
                    case 'P':
                        e.preventDefault();
                        const query = prompt('검색어를 입력하세요:');
                        if (query) {
                            startCrawling(query);
                        }
                        break;
                    case 'q':
                    case 'Q':
                        e.preventDefault();
                        isCrawling = false;
                        log('크롤링이 중지되었습니다.', 'warn');
                        break;
                    case 'd':
                    case 'D':
                        e.preventDefault();
                        exportToExcel();
                        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();
    }

    // 초기화 실행
    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 함수는 설명 내용에 포함된 특정 키워드를 기반으로 콘텐츠를 자동으로 카테고리 분류합니다.
카테고리는 🗂️ 분류 열에 표시됩니다.
사용자 정의 블랙리스트/화이트리스트
설정 탭에서 🚫 블랙리스트 도메인과 ✅ 화이트리스트 도메인을 관리할 수 있습니다.
화이트리스트가 설정된 경우, 화이트리스트 도메인에 속한 사이트만 크롤링이 허용됩니다.
블랙리스트 도메인은 추가로 제한되며, 해당 도메인에서 수집된 데이터는 필터링됩니다.