// ==UserScript==
// @name         Advanced Web Crawler
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  자동 웹 크롤러 with UI, logging, and various enhanced features
// @author       Your Name
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_download
// @connect      *
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// ==/UserScript==

(function() {
    'use strict';

    // ====== 기본 설정 ======
    const defaultSettings = {
        delay: 3000, // 딜레이 (밀리초)
        blacklist: [], // 블랙리스트 도메인
        whitelist: [], // 화이트리스트 도메인
        searchEngines: ['google'], // 기본 검색 엔진
    };

    const settings = GM_getValue('settings', defaultSettings);

    // ====== 크롤링 상태 관리 ======
    let crawling = false;
    let crawledUrls = new Set(GM_getValue('crawledUrls', []));
    let totalUrls = 0;

    // ====== UI 생성 ======
    const createUI = () => {
        // 스타일 추가
        const style = `
            /* UI 스타일링 */
            #crawler-ui {
                position: fixed;
                top: 10px;
                right: 10px;
                width: 500px;
                height: 700px;
                background: white;
                border: 2px solid #333;
                border-radius: 8px;
                z-index: 10000;
                font-family: Arial, sans-serif;
                box-shadow: 0 4px 8px rgba(0,0,0,0.2);
                resize: both;
                overflow: hidden;
            }
            #crawler-header {
                background: #333;
                color: white;
                padding: 10px;
                cursor: move;
                user-select: none;
            }
            #crawler-tabs {
                display: flex;
                border-bottom: 1px solid #ccc;
            }
            .crawler-tab {
                flex: 1;
                padding: 10px;
                text-align: center;
                cursor: pointer;
                background: #f9f9f9;
            }
            .crawler-tab.active {
                background: #e0e0e0;
                font-weight: bold;
            }
            #crawler-content {
                padding: 10px;
                height: calc(100% - 50px);
                overflow: auto;
            }
            .progress-bar {
                width: 100%;
                background: #f3f3f3;
                border: 1px solid #ccc;
                height: 20px;
                position: relative;
                margin-bottom: 10px;
            }
            .progress {
                background: #4caf50;
                height: 100%;
                width: 0%;
            }
            table {
                width: 100%;
                border-collapse: collapse;
                margin-top: 10px;
            }
            th, td {
                border: 1px solid #ddd;
                padding: 8px;
                text-align: left;
            }
            th {
                background-color: #f2f2f2;
            }
            tr:hover {
                background-color: #f1f1f1;
            }
            /* 버튼 스타일 */
            .btn {
                padding: 8px 12px;
                margin: 5px 2px;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
            }
            .btn-start { background-color: #4caf50; color: white; }
            .btn-stop { background-color: #f44336; color: white; }
            .btn-reset { background-color: #ff9800; color: white; }
            .btn-download { background-color: #2196f3; color: white; }
            /* 모달 스타일 */
            .modal {
                display: none;
                position: fixed;
                z-index: 10001;
                left: 0;
                top: 0;
                width: 100%;
                height: 100%;
                overflow: auto;
                background-color: rgba(0,0,0,0.4);
            }
            .modal-content {
                background-color: #fff;
                margin: 5% auto;
                padding: 20px;
                border: 1px solid #888;
                width: 80%;
                height: 80%;
                overflow: auto;
                border-radius: 8px;
                position: relative;
            }
            .close {
                color: #aaa;
                position: absolute;
                top: 10px;
                right: 25px;
                font-size: 28px;
                font-weight: bold;
                cursor: pointer;
            }
            .close:hover,
            .close:focus {
                color: black;
                text-decoration: none;
                cursor: pointer;
            }
            /* 로그 스타일 */
            .log-info { color: #000; }
            .log-warning { color: #FF9800; }
            .log-error { color: #F44336; }
        `;
        $('head').append(`<style>${style}</style>`);

        // UI 구조
        const ui = `
            <div id="crawler-ui">
                <div id="crawler-header">🔍 Advanced Web Crawler</div>
                <div id="crawler-tabs">
                    <div class="crawler-tab active" data-tab="logs">📜 로그</div>
                    <div class="crawler-tab" data-tab="data">📊 데이터</div>
                    <div class="crawler-tab" data-tab="settings">⚙️ 설정</div>
                    <div class="crawler-tab" data-tab="saved">💾 저장된 사이트</div>
                </div>
                <div id="crawler-content">
                    <div id="logs" class="tab-content">
                        <div id="main-log" style="margin-bottom:10px; font-weight: bold;">✅ 종합 로그</div>
                        <textarea id="log-area" style="width: 100%; height: 60%;"></textarea>
                        <div id="error-log" style="margin-top:10px; font-weight: bold;">❌ 버그 로그</div>
                        <textarea id="bug-log" style="width: 100%; height: 30%; color: red;"></textarea>
                    </div>
                    <div id="data" class="tab-content" style="display:none;">
                        <div class="progress-bar">
                            <div class="progress" id="progress"></div>
                        </div>
                        <table id="data-table">
                            <thead>
                                <tr>
                                    <th>사이트 이름</th>
                                    <th>주소</th>
                                    <th>설명</th>
                                    <th>분류</th>
                                    <th>질 점수</th>
                                    <th>텍스트 내용 🗨️</th>
                                    <th>HTML 내용 📄</th>
                                </tr>
                            </thead>
                            <tbody></tbody>
                        </table>
                        <button class="btn btn-download" id="download-csv">CSV 다운로드</button>
                        <button class="btn btn-download" id="download-json">JSON 다운로드</button>
                    </div>
                    <div id="settings" class="tab-content" style="display:none;">
                        <h3>크롤링 설정</h3>
                        <label>딜레이 (밀리초):</label>
                        <input type="number" id="delay-input" value="${settings.delay}" title="크롤링 딜레이 설정"><br><br>
                        <label>블랙리스트 도메인 (콤마로 구분):</label>
                        <input type="text" id="blacklist-input" value="${settings.blacklist.join(',')}" title="블랙리스트 도메인"><br><br>
                        <label>화이트리스트 도메인 (콤마로 구분):</label>
                        <input type="text" id="whitelist-input" value="${settings.whitelist.join(',')}" title="화이트리스트 도메인"><br><br>
                        <label>검색 엔진 선택:</label><br>
                        <select id="search-engine-select" multiple size="3">
                            <option value="google" ${settings.searchEngines.includes('google') ? 'selected' : ''}>Google</option>
                            <option value="bing" ${settings.searchEngines.includes('bing') ? 'selected' : ''}>Bing</option>
                            <option value="duckduckgo" ${settings.searchEngines.includes('duckduckgo') ? 'selected' : ''}>DuckDuckGo</option>
                            <!-- 추가 검색 엔진 옵션 -->
                        </select><br><br>
                        <button class="btn btn-start" id="start-crawling">시작</button>
                        <button class="btn btn-stop" id="stop-crawling">중지</button>
                        <button class="btn btn-reset" id="reset-crawling">초기화</button>
                        <button class="btn btn-download" id="save-settings">설정 저장 (Ctrl+S)</button>
                    </div>
                    <div id="saved" class="tab-content" style="display:none;">
                        <h3>저장된 사이트 관리</h3>
                        <table id="saved-table">
                            <thead>
                                <tr>
                                    <th>사이트 이름</th>
                                    <th>주소</th>
                                    <th>삭제</th>
                                </tr>
                            </thead>
                            <tbody></tbody>
                        </table>
                        <button class="btn btn-download" id="clear-saved">모두 삭제</button>
                    </div>
                </div>
            </div>
            <!-- 모달 -->
            <div id="html-modal" class="modal">
                <div class="modal-content">
                    <span class="close">&times;</span>
                    <pre id="modal-content"></pre>
                </div>
            </div>
        `;
        $('body').append(ui);

        // 드래그 가능한 헤더 설정
        makeDraggable(document.getElementById("crawler-ui"), document.getElementById("crawler-header"));

        // 탭 이벤트
        $('.crawler-tab').on('click', function() {
            $('.crawler-tab').removeClass('active');
            $(this).addClass('active');
            const tab = $(this).data('tab');
            $('.tab-content').hide();
            $(`#${tab}`).show();
        });

        // 모달 이벤트
        $('.close').on('click', function() {
            $('#html-modal').hide();
        });
        $(window).on('click', function(event) {
            if (event.target.id === 'html-modal') {
                $('#html-modal').hide();
            }
        });

        // 설정 저장 이벤트
        $('#save-settings').on('click', saveSettings);

        // 크롤링 제어 버튼 이벤트
        $('#start-crawling').on('click', startCrawling);
        $('#stop-crawling').on('click', stopCrawling);
        $('#reset-crawling').on('click', resetCrawling);

        // 다운로드 버튼 이벤트
        $('#download-csv').on('click', downloadCSV);
        $('#download-json').on('click', downloadJSON);

        // 저장된 사이트 관리 이벤트
        $('#clear-saved').on('click', clearSavedSites);
        populateSavedSites();

        // 키보드 단축키 설정
        setupKeyboardShortcuts();
    };

    // ====== 드래그 가능 UI ======
    function makeDraggable(elmnt, handle) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        handle.onmousedown = dragMouseDown;

        function dragMouseDown(e) {
            e = e || window.event;
            e.preventDefault();
            // Get the mouse cursor position at startup:
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }
        function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();
            // Calculate the new cursor position:
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            // Set the element's new position:
            elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
            elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
        }
        function closeDragElement() {
            // Stop moving when mouse button is released:
            document.onmouseup = null;
            document.onmousemove = null;
        }
    }

    // ====== 설정 저장 ======
    const saveSettings = () => {
        const delay = parseInt($('#delay-input').val()) || 3000;
        const blacklist = $('#blacklist-input').val().split(',').map(s => s.trim()).filter(s => s);
        const whitelist = $('#whitelist-input').val().split(',').map(s => s.trim()).filter(s => s);
        const searchEngines = $('#search-engine-select').val() || [];

        settings.delay = delay;
        settings.blacklist = blacklist;
        settings.whitelist = whitelist;
        settings.searchEngines = searchEngines;

        GM_setValue('settings', settings);
        log('설정이 저장되었습니다.', 'info');
        refreshSearchEngineOptions();
    };

    // ====== 설정 로드 ======
    const loadSettings = () => {
        const saved = GM_getValue('settings');
        if (saved) {
            settings.delay = saved.delay;
            settings.blacklist = saved.blacklist;
            settings.whitelist = saved.whitelist;
            settings.searchEngines = saved.searchEngines;
        }
    };

    // ====== 로그 기능 ======
    const log = (message, type = 'info') => {
        const timestamp = new Date().toLocaleTimeString();
        const formattedMessage = `${timestamp} [${type.toUpperCase()}] ${message}`;
        if (type === 'error') {
            $('#bug-log').val(formattedMessage + '\n' + $('#bug-log').val());
        } else {
            $('#log-area').val(formattedMessage + '\n' + $('#log-area').val());
        }
        $('#main-log').text('✅ 종합 로그');
    };

    // ====== 데이터 추가 ======
    const addData = (data) => {
        const row = `
            <tr>
                <td>${data.name}</td>
                <td><a href="${data.url}" target="_blank">${data.url}</a></td>
                <td>${data.description}</td>
                <td>${data.category}</td>
                <td>${data.score}</td>
                <td><button class="view-text" data-text="${escapeHtml(data.text)}">보기</button></td>
                <td><button class="view-html" data-html="${escapeHtml(data.html)}">보기</button></td>
            </tr>
        `;
        $('#data-table tbody').append(row);
        // 저장된 데이터 업데이트
        let savedData = GM_getValue('savedData', []);
        savedData.push(data);
        GM_setValue('savedData', savedData);
    };

    // ====== HTML 및 텍스트 보기 ======
    $(document).on('click', '.view-html, .view-text', function() {
        const content = $(this).data($(this).hasClass('view-html') ? 'html' : 'text');
        $('#modal-content').text(unescapeHtml(content));
        $('#html-modal').show();
    });

    // ====== Escape/Unescape HTML ======
    const escapeHtml = (unsafe) => {
        return unsafe
             .replace(/&/g, "&amp;")
             .replace(/</g, "&lt;")
             .replace(/>/g, "&gt;")
             .replace(/"/g, "&quot;")
             .replace(/'/g, "&#039;");
    };

    const unescapeHtml = (safe) => {
        return safe
             .replace(/&lt;/g, "<")
             .replace(/&gt;/g, ">")
             .replace(/&amp;/g, "&")
             .replace(/&quot;/g, "\"")
             .replace(/&#039;/g, "'");
    };

    // ====== 다운로드 기능 ======
    const downloadCSV = () => {
        const ws = XLSX.utils.table_to_sheet(document.getElementById('data-table'));
        const wb = XLSX.utils.book_new();
        XLSX.utils.book_append_sheet(wb, ws, "데이터");
        XLSX.writeFile(wb, "data.csv");
        log('CSV 파일이 다운로드되었습니다.', 'info');
    };

    const downloadJSON = () => {
        const data = GM_getValue('savedData', []);
        const blob = new Blob([JSON.stringify(data, null, 2)], {type : 'application/json'});
        GM_download({
            url: URL.createObjectURL(blob),
            name: 'data.json',
            saveAs: true
        });
        log('JSON 파일이 다운로드되었습니다.', 'info');
    };

    // ====== 키보드 단축키 ======
    const setupKeyboardShortcuts = () => {
        $(document).on('keydown', function(e) {
            if (e.ctrlKey) {
                switch (e.key) {
                    case 's':
                        e.preventDefault();
                        saveSettings();
                        break;
                    case 'p':
                        e.preventDefault();
                        if (!crawling) startCrawling();
                        else stopCrawling();
                        break;
                    case 'q':
                        e.preventDefault();
                        stopCrawling();
                        break;
                    case 'd':
                        e.preventDefault();
                        downloadCSV();
                        break;
                    case 'j':
                        e.preventDefault();
                        downloadJSON();
                        break;
                    case '1':
                        e.preventDefault();
                        $('.crawler-tab[data-tab="logs"]').click();
                        break;
                    case '2':
                        e.preventDefault();
                        $('.crawler-tab[data-tab="data"]').click();
                        break;
                    case '3':
                        e.preventDefault();
                        $('.crawler-tab[data-tab="settings"]').click();
                        break;
                    case '4':
                        e.preventDefault();
                        $('.crawler-tab[data-tab="saved"]').click();
                        break;
                }
            }
        });
    };

    // ====== 크롤링 기능 ======
    const startCrawling = () => {
        if (crawling) {
            log('이미 크롤링이 진행 중입니다.', 'warning');
            return;
        }
        if (settings.searchEngines.length === 0) {
            log('적어도 하나의 검색 엔진을 선택해야 합니다.', 'error');
            return;
        }
        crawling = true;
        log('크롤링을 시작합니다.', 'info');
        performSearch('예시 검색어'); // 사용자 정의 검색어로 변경 가능
    };

    const stopCrawling = () => {
        if (!crawling) {
            log('현재 진행 중인 크롤링이 없습니다.', 'warning');
            return;
        }
        crawling = false;
        log('크롤링이 중지되었습니다.', 'info');
    };

    const resetCrawling = () => {
        crawledUrls.clear();
        GM_setValue('crawledUrls', []);
        $('#data-table tbody').empty();
        $('#progress').css('width', '0%');
        log('크롤링 상태가 초기화되었습니다.', 'info');
    };

    const performSearch = async (query) => {
        for (const engine of settings.searchEngines) {
            if (!crawling) break;
            let searchUrl = '';
            switch(engine) {
                case 'google':
                    searchUrl = `https://www.google.com/search?q=${encodeURIComponent(query)}`;
                    break;
                case 'bing':
                    searchUrl = `https://www.bing.com/search?q=${encodeURIComponent(query)}`;
                    break;
                case 'duckduckgo':
                    searchUrl = `https://duckduckgo.com/?q=${encodeURIComponent(query)}`;
                    break;
                // 추가 검색 엔진 대응 가능
                default:
                    log(`지원되지 않는 검색 엔진: ${engine}`, 'warning');
                    continue;
            }
            try {
                const response = await fetch(searchUrl, {mode: 'cors'});
                const text = await response.text();
                const parser = new DOMParser();
                const doc = parser.parseFromString(text, 'text/html');
                const results = extractLinks(doc, engine);
                log(`${engine}에서 ${results.length}개의 URL을 찾았습니다.`, 'info');
                totalUrls += results.length;
                await processResults(results);
            } catch (error) {
                log(`검색 엔진 ${engine} 사용 중 오류 발생: ${error.message}`, 'error');
            }
        }
        log('모든 크롤링 작업이 완료되었습니다.', 'info');
        crawling = false;
    };

    // ====== 링크 추출 ======
    const extractLinks = (doc, engine) => {
        let links = [];
        switch(engine) {
            case 'google':
                $('a', doc).each(function() {
                    const href = $(this).attr('href');
                    if (href && href.startsWith('/url?q=')) {
                        const url = new URL(href.split('/url?q=')[1].split('&')[0]);
                        links.push(url.href);
                    }
                });
                break;
            case 'bing':
                $('a', doc).each(function() {
                    const href = $(this).attr('href');
                    if (href && href.startsWith('http')) {
                        links.push(href);
                    }
                });
                break;
            case 'duckduckgo':
                $('a.result__a', doc).each(function() {
                    const href = $(this).attr('href');
                    if (href && href.startsWith('http')) {
                        links.push(href);
                    }
                });
                break;
            // 추가 검색 엔진 대응 가능
        }
        // 필터링
        links = links.filter(url => !isBlacklisted(url) && isWhitelisted(url));
        return links;
    };

    // ====== 페이지 분석 ======
    const analyzePage = () => {
        if (!crawling) return;
        log('페이지 분석을 시작합니다.', 'info');
        const results = [];
        $('a').each(function() {
            const url = $(this).attr('href');
            if (url && url.startsWith('http') && !isBlacklisted(url) && isWhitelisted(url)) {
                results.push(url);
            }
        });
        log(`찾은 URL 개수: ${results.length}`, 'info');
        processResults(results);
    };

    const isBlacklisted = (url) => {
        return settings.blacklist.some(domain => url.includes(domain));
    };

    const isWhitelisted = (url) => {
        if (settings.whitelist.length === 0) return true;
        return settings.whitelist.some(domain => url.includes(domain));
    };

    // ====== 결과 처리 ======
    const processResults = async (urls) => {
        for (let i = 0; i < urls.length; i++) {
            if (!crawling) break;
            const url = urls[i];
            if (crawledUrls.has(url)) {
                log(`이미 크롤링한 URL: ${url}`, 'warning');
                continue;
            }
            crawledUrls.add(url);
            GM_setValue('crawledUrls', Array.from(crawledUrls));
            try {
                log(`크롤링 중: ${url}`, 'info');
                const response = await fetch(url, {mode: 'cors'});
                const html = await response.text();
                const parser = new DOMParser();
                const doc = parser.parseFromString(html, 'text/html');
                const name = doc.querySelector('title') ? doc.querySelector('title').innerText : 'No Title';
                const description = doc.querySelector('meta[name="description"]') ? doc.querySelector('meta[name="description"]').getAttribute('content') : 'No Description';
                const textContent = doc.body ? doc.body.innerText.substring(0, 200) + '...' : 'No Text Content';
                const category = classifyKeywords(description);
                const score = evaluateContentQuality(description);
                addData({ name, url, description, category, score, html, text: textContent });
                updateProgress((crawledUrls.size / totalUrls) * 100);
                await delay(settings.delay);
            } catch (error) {
                log(`에러 발생: ${url} - ${error.message}`, 'error');
                // 재시도 로직 추가 가능
            }
        }
    };

    // ====== 진행률 업데이트 ======
    const updateProgress = (percentage) => {
        $('#progress').css('width', `${percentage}%`);
    };

    // ====== 딜레이 ======
    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

    // ====== 콘텐츠 품질 평가 ======
    const evaluateContentQuality = (text) => {
        const wordCount = text.split(/\s+/).length;
        return wordCount;
    };

    // ====== 키워드 분류 ======
    const classifyKeywords = (text) => {
        const categories = {
            '교육': ['학생', '학교', '교육', '학습'],
            '기술': ['기술', '소프트웨어', '프로그래밍', '개발'],
            '비즈니스': ['비즈니스', '회사', '시장', '경제'],
            // 추가 카테고리 및 키워드 추가 가능
        };
        for (const [category, keywords] of Object.entries(categories)) {
            for (const keyword of keywords) {
                if (text.includes(keyword)) {
                    return category;
                }
            }
        }
        return '기타';
    };

    // ====== 초기화 ======
    const init = () => {
        createUI();
        loadSettings();
    };

    // ====== 저장된 사이트 관리 ======
    const populateSavedSites = () => {
        const savedData = GM_getValue('savedData', []);
        $('#saved-table tbody').empty();
        savedData.forEach((data, index) => {
            const row = `
                <tr>
                    <td>${data.name}</td>
                    <td><a href="${data.url}" target="_blank">${data.url}</a></td>
                    <td><button class="btn btn-stop remove-saved" data-index="${index}">삭제</button></td>
                </tr>
            `;
            $('#saved-table tbody').append(row);
        });
    };

    $(document).on('click', '.remove-saved', function() {
        const index = $(this).data('index');
        let savedData = GM_getValue('savedData', []);
        savedData.splice(index, 1);
        GM_setValue('savedData', savedData);
        populateSavedSites();
        log('저장된 사이트가 삭제되었습니다.', 'info');
    });

    const clearSavedSites = () => {
        GM_setValue('savedData', []);
        populateSavedSites();
        log('모든 저장된 사이트가 삭제되었습니다.', 'info');
    };

    // ====== 저장된 데이터 다시 로드 ======
    const loadSavedData = () => {
        const savedData = GM_getValue('savedData', []);
        savedData.forEach(data => {
            addData(data);
        });
    };

    // ====== 종합 로그 및 에러 로그 나누기 ======
    // 이미 로그 함수에서 처리됨

    // ====== 검색 엔진 옵션 새로고침 ======
    const refreshSearchEngineOptions = () => {
        $('#search-engine-select option').each(function() {
            const value = $(this).val();
            if (settings.searchEngines.includes(value)) {
                $(this).prop('selected', true);
            } else {
                $(this).prop('selected', false);
            }
        });
    };

    // ====== 이벤트 핸들러 ======
    $(document).ready(function() {
        init();
        loadSavedData();
    });

})();                  크ㄹ롤링이  시작이 안 됨.... +  로그들 창 길게 해주셈... 그 

또 UI창 크기를 조절할 수 있는 기능을 추가하셈


각 검색 결과의 전체 HTML 내용과 주요 텍스트 내용을 비동기로 수집합니다.
수집된 내용을 데이터 테이블의 🗨️ 텍스트 내용과 📄 HTML 내용 열에 표시합니다.
📄 HTML 내용 셀을 클릭하면 모달 창으로 전체 HTML 내용을 확인할 수 있습니다. 이것도 추 가 
     
   설정 쪽  다양하면서도 세심하게 만드셈 그리고 다양한 기능들을 엄청 많이 추가하셈 굉장히 실용적이면서도 참신해야 함 

크롤링하는 사이트를 더더 확실하게 할 수 있는 방법들을 추가하셈 또 데이터쪽  굉장히 세심하면서도 다양한 기능들 많이 엄청 추가하셈. 굉장히 실용적이면서도 참신한 걸로  하지만 본질적인 거는 남겨놓고 ㅇㅇ 그리고 검색 엔진 다양하게 하셈 



그리고 맨위에 좋합 로그 놓고 버그나 밑에 그런 것들 로그들 다 따로따로 놓고  걍 모든 로그 나누고 맨 위에 좋합 로그 넣어두셈

그리고 종합로그나 버그 그런 것들 로그에서  버그나 그런 것들은 로그 메세지를 빨간 색 

또 크롤링 한 사이트 저장하고 또 그 저장한 사이트 들중에서 뺴고 넣고 이런 기능 추가 
 
HTML 내용 수집 및 텍스트 추출 기능 추ㄱ