// ==UserScript==
// @name         Advanced Web Crawler Enhanced Pro
// @namespace    http://tampermonkey.net/
// @version      6.0
// @description  고급 웹 크롤러 스크립트 with Enhanced Settings, Improved Accuracy, Resizable UI, and Additional Features
// @author       Your Name
// @match        https://www.google.com/search*
// @match        https://www.bing.com/search*
// @match        https://search.yahoo.com/search*
// @match        https://duckduckgo.com/*
// @match        https://www.baidu.com/s*
// @match        https://www.yandex.com/search/*
// @match        https://search.naver.com/search*
// @match        https://search.daum.net/search*
// @match        https://www.ask.com/*
// @match        https://www.ecosia.org/search*
// @match        https://www.startpage.com/*
// @match        https://searx.me/*
// @match        https://www.dogpile.com/*
// @match        https://www.bing.com/images*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @connect      *
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @require      https://cdn.jsdelivr.net/npm/chart.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// ==/UserScript==

(function() {
    'use strict';

    // 기본 설정
    const defaultSettings = {
        delay: 2000, // 초기 딜레이 2초
        delayRandom: 500, // 딜레이 랜덤 범위 ±0.5초
        maxPages: 3, // 기본 최대 크롤링 페이지 수
        userAgents: [
            'AdvancedWebCrawler/6.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)',
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15',
            'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)',
            'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15',
            'Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X) AppleWebKit/605.1.15',
            // 추가 사용자 에이전트
        ],
        userAgentIndex: 0,
        blacklist: [],
        whitelist: [],
        contentTypes: {
            include: ['text/html'],
            exclude: ['application/pdf', 'image/jpeg', 'image/png', 'video/mp4']
        },
        timeout: 8000, // 기본 타임아웃 8초
        includeKeywords: [],
        excludeKeywords: [],
        language: [],
        minWordCount: 100,
        maxWordCount: 10000,
        maxDepth: 2,
        proxy: '',
        regexInclude: [],
        regexExclude: [],
        dataFields: {
            name: true,
            url: true,
            description: true,
            html: true, // HTML 내용 수집 활성화
            category: true,
            quality: true,
            language: true,
            wordCount: true,
            metaTags: false,
            headings: false
        },
        schedule: {
            enabled: false,
            startTime: '00:00',
            interval: 3600000
        },
        searchEngines: {
            google: true,
            bing: true,
            yahoo: true,
            duckduckgo: true,
            baidu: true,
            yandex: true,
            naver: true,
            daum: true,
            ask: true,
            ecosia: true,
            startpage: true,
            searx: true,
            dogpile: true, // 추가된 검색 엔진
            bingimages: true // 추가된 검색 엔진
            // 추가 검색 엔진
        },
        retryAttempts: 3,
        retryDelay: 2000,
        respectRobots: true,
        maxConcurrentRequests: 3,
        dataVisualization: true,
        exportFormats: {
            csv: true,
            json: true,
            xml: true,
            xls: true
        },
        advancedLogging: true,
        enableCaptchas: false // CAPTCHA 처리 옵션 추가
    };

    // 현재 설정 로드
    const settings = {
        delay: parseInt(GM_getValue('delay', defaultSettings.delay), 10),
        delayRandom: parseInt(GM_getValue('delayRandom', defaultSettings.delayRandom), 10),
        maxPages: parseInt(GM_getValue('maxPages', defaultSettings.maxPages), 10),
        userAgents: GM_getValue('userAgents', defaultSettings.userAgents),
        userAgentIndex: parseInt(GM_getValue('userAgentIndex', defaultSettings.userAgentIndex), 10),
        blacklist: GM_getValue('blacklist', defaultSettings.blacklist),
        whitelist: GM_getValue('whitelist', defaultSettings.whitelist),
        contentTypes: GM_getValue('contentTypes', defaultSettings.contentTypes),
        timeout: parseInt(GM_getValue('timeout', defaultSettings.timeout), 10),
        includeKeywords: GM_getValue('includeKeywords', defaultSettings.includeKeywords),
        excludeKeywords: GM_getValue('excludeKeywords', defaultSettings.excludeKeywords),
        regexInclude: GM_getValue('regexInclude', defaultSettings.regexInclude),
        regexExclude: GM_getValue('regexExclude', defaultSettings.regexExclude),
        language: GM_getValue('language', defaultSettings.language),
        minWordCount: parseInt(GM_getValue('minWordCount', defaultSettings.minWordCount), 10),
        maxWordCount: parseInt(GM_getValue('maxWordCount', defaultSettings.maxWordCount), 10),
        maxDepth: parseInt(GM_getValue('maxDepth', defaultSettings.maxDepth), 10),
        proxy: GM_getValue('proxy', defaultSettings.proxy),
        dataFields: GM_getValue('dataFields', defaultSettings.dataFields),
        schedule: GM_getValue('schedule', defaultSettings.schedule),
        searchEngines: GM_getValue('searchEngines', defaultSettings.searchEngines),
        retryAttempts: parseInt(GM_getValue('retryAttempts', defaultSettings.retryAttempts), 10),
        retryDelay: parseInt(GM_getValue('retryDelay', defaultSettings.retryDelay), 10),
        respectRobots: GM_getValue('respectRobots', defaultSettings.respectRobots),
        maxConcurrentRequests: parseInt(GM_getValue('maxConcurrentRequests', defaultSettings.maxConcurrentRequests), 10),
        dataVisualization: GM_getValue('dataVisualization', defaultSettings.dataVisualization),
        exportFormats: GM_getValue('exportFormats', defaultSettings.exportFormats),
        advancedLogging: GM_getValue('advancedLogging', defaultSettings.advancedLogging),
        enableCaptchas: GM_getValue('enableCaptchas', defaultSettings.enableCaptchas),
        customKeywords: GM_getValue('customKeywords', {})
    };

    // 크롤링 상태 복원
    let visitedUrls = GM_getValue('visitedUrls', []);
    let logs = GM_getValue('logs', []);
    let data = GM_getValue('data', []);
    let isCrawling = GM_getValue('isCrawling', false);
    let currentPage = GM_getValue('currentPage', 1);
    let totalPages = GM_getValue('totalPages', settings.maxPages);
    let progress = GM_getValue('progress', 0);
    let currentDepth = GM_getValue('currentDepth', 0);
    let activeRequests = 0;
    let requestQueue = [];

    // UI 생성
    function createUI() {
        const style = `
            /* 스타일 추가 */
            #crawler-container {
                position: fixed;
                top: 20px;
                right: 20px;
                width: 800px; /* 크기 증가 */
                height: 800px; /* 크기 증가 */
                background: #fff;
                border: 1px solid #ccc;
                z-index: 10000;
                font-family: Arial, sans-serif;
                box-shadow: 0 4px 8px rgba(0,0,0,0.2);
                overflow: hidden;
                resize: both;
                overflow: auto;
                border-radius: 8px;
                min-width: 500px; /* 최소 너비 조정 */
                min-height: 600px; /* 최소 높이 조정 */
            }
            #crawler-tabs { display: flex; background: #f1f1f1; border-bottom: 1px solid #ccc; }
            .crawler-tab { flex: 1; padding: 10px; text-align: center; cursor: pointer; font-weight: bold; }
            .crawler-tab.active { background: #ddd; }
            #crawler-content { padding: 10px; overflow: auto; height: calc(100% - 50px); }
            /* 추가적인 스타일 */
            table { width: 100%; border-collapse: collapse; }
            th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
            th { background-color: #f2f2f2; cursor: pointer; }
            tr:hover { background-color: #f5f5f5; }
            .progress-bar { width: 100%; background-color: #f3f3f3; border: 1px solid #ccc; border-radius: 5px; overflow: hidden; margin-bottom: 10px; }
            .progress-bar-fill { height: 20px; background-color: #4caf50; width: 0%; transition: width 0.2s; }
            .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%; max-height: 80%; overflow-y: auto; border-radius: 8px; }
            .close { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
            .control-buttons { margin: 10px 0; }
            .control-buttons button { padding: 8px 12px; margin-right: 8px; cursor: pointer; border: none; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px; }
            .control-buttons button:disabled { background-color: #a5d6a7; cursor: not-allowed; }
            .status-text { margin-top: 10px; font-weight: bold; font-size: 14px; }
            /* 설정 섹션 스타일 */
            .settings-section { margin-bottom: 15px; }
            .settings-section label { display: block; margin-bottom: 5px; font-weight: bold; }
            .settings-section input[type="text"],
            .settings-section input[type="number"],
            .settings-section textarea,
            .settings-section select { width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }
            .settings-section .checkbox-group { display: flex; flex-wrap: wrap; }
            .settings-section .checkbox-group label { margin-right: 10px; font-size: 14px; }
            .settings-section .radio-group { display: flex; flex-wrap: wrap; }
            .settings-section .radio-group label { margin-right: 10px; font-size: 14px; }
            /* Tooltip 스타일 */
            .tooltip {
                position: relative;
                display: inline-block;
                cursor: help;
                font-size: 14px;
            }
            .tooltip .tooltiptext {
                visibility: hidden;
                width: 220px;
                background-color: #555;
                color: #fff;
                text-align: center;
                border-radius: 6px;
                padding: 5px;
                position: absolute;
                z-index: 1;
                bottom: 125%;
                left: 50%;
                margin-left: -110px;
                opacity: 0;
                transition: opacity 0.3s;
            }
            .tooltip:hover .tooltiptext {
                visibility: visible;
                opacity: 1;
            }
            /* 추가 설정 */
            #crawler-container::-webkit-resizer {
                background: #f1f1f1;
                border: none;
            }
            /* 데이터 시각화 섹션 */
            #data-visualization {
                margin-top: 20px;
            }
            #data-chart {
                width: 100%;
                height: 300px; /* 높이 조정 */
            }
            /* 모달 내 스타일 */
            #modal-html-content {
                white-space: pre-wrap;
                word-wrap: break-word;
                background-color: #f9f9f9;
                padding: 10px;
                border-radius: 4px;
            }
        `;
        $('head').append(`<style>${style}</style>`);

        const container = $(`
            <div id="crawler-container">
                <div id="crawler-tabs">
                    <div class="crawler-tab ${currentTab() === 'logs' ? 'active' : ''}" data-tab="logs" title="로그 보기">📜 로그</div>
                    <div class="crawler-tab ${currentTab() === 'data' ? 'active' : ''}" data-tab="data" title="수집된 데이터 보기">📊 데이터</div>
                    <div class="crawler-tab ${currentTab() === 'settings' ? 'active' : ''}" data-tab="settings" title="설정 변경">⚙️ 설정</div>
                </div>
                <div id="crawler-content">
                    <div id="tab-logs" style="display: ${currentTab() === 'logs' ? 'block' : 'none'};">
                        <div class="progress-bar">
                            <div class="progress-bar-fill" style="width: ${progress.toFixed(2)}%;"></div>
                        </div>
                        <div class="status-text">진행률: ${progress.toFixed(2)}% (${currentPage}/${totalPages} 페이지)</div>
                        <div class="control-buttons">
                            <button id="start-crawling" title="크롤링을 시작합니다." ${isCrawling ? 'disabled' : ''}>▶ 시작</button>
                            <button id="stop-crawling" title="크롤링을 중지합니다." ${isCrawling ? '' : 'disabled'}>■ 중지</button>
                            ${settings.exportFormats.csv ? `<button id="download-csv" title="수집된 데이터를 CSV 파일로 다운로드합니다.">📥 CSV</button>` : ''}
                            ${settings.exportFormats.json ? `<button id="download-json" title="수집된 데이터를 JSON 파일로 다운로드합니다.">📥 JSON</button>` : ''}
                            ${settings.exportFormats.xml ? `<button id="download-xml" title="수집된 데이터를 XML 파일로 다운로드합니다.">📥 XML</button>` : ''}
                            ${settings.exportFormats.xls ? `<button id="download-xls" title="수집된 데이터를 XLS 파일로 다운로드합니다.">📥 XLS</button>` : ''}
                        </div>
                        <div id="log-messages" style="height: 250px; overflow-y: auto; background: #f9f9f9; padding: 10px; border: 1px solid #ccc; border-radius: 4px;">로그 내용이 여기에 표시됩니다.</div>
                    </div>
                    <div id="tab-data" style="display: ${currentTab() === 'data' ? 'block' : 'none'};">
                        <input type="text" id="search-filter" placeholder="검색 필터..." title="데이터 필터링을 위한 키워드를 입력하세요." style="width: 100%; padding: 5px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px;">
                        <table>
                            <thead>
                                <tr>
                                    ${settings.dataFields.name ? '<th data-sort="name">사이트 이름</th>' : ''}
                                    ${settings.dataFields.url ? '<th data-sort="url">주소</th>' : ''}
                                    ${settings.dataFields.description ? '<th data-sort="description">설명</th>' : ''}
                                    ${settings.dataFields.html ? '<th>📄 HTML 내용</th>' : ''}
                                    ${settings.dataFields.category ? '<th data-sort="category">분류</th>' : ''}
                                    ${settings.dataFields.quality ? '<th data-sort="quality">품질점수</th>' : ''}
                                    ${settings.dataFields.language ? '<th data-sort="language">언어</th>' : ''}
                                    ${settings.dataFields.wordCount ? '<th data-sort="wordCount">단어 수</th>' : ''}
                                    ${settings.dataFields.metaTags ? '<th>메타 태그</th>' : ''}
                                    ${settings.dataFields.headings ? '<th>헤딩</th>' : ''}
                                </tr>
                            </thead>
                            <tbody id="data-table-body">
                                <!-- 데이터 행이 여기에 추가됩니다. -->
                            </tbody>
                        </table>
                        <!-- 데이터 시각화 섹션 -->
                        ${settings.dataVisualization ? `
                            <div id="data-visualization">
                                <h3>데이터 시각화</h3>
                                <canvas id="data-chart"></canvas>
                            </div>
                        ` : ''}
                    </div>
                    <div id="tab-settings" style="display: ${currentTab() === 'settings' ? 'block' : 'none'};">
                        <h3>설정</h3>
                        <!-- 크롤링 설정 섹션 -->
                        <div class="settings-section">
                            <label for="delay-input">크롤링 딜레이 (ms) <span class="tooltip">ℹ️<span class="tooltiptext">기본 딜레이과 딜레이 랜덤 값을 조합하여 요청 사이의 딜레이를 설정합니다.</span></span>:</label>
                            <input type="number" id="delay-input" value="${settings.delay}" title="크롤링 요청 사이의 딜레이를 밀리초 단위로 설정하세요." min="500" step="500">
                            <label for="delay-random-input">딜레이 랜덤 범위 (ms) <span class="tooltip">ℹ️<span class="tooltiptext">딜레이에 무작위 요소를 추가하여 보다 자연스러운 요청을 합니다.</span></span>:</label>
                            <input type="number" id="delay-random-input" value="${settings.delayRandom}" title="크롤링 요청 딜레이에 추가할 랜덤 범위를 밀리초 단위로 설정하세요." min="0" step="500">
                        </div>
                        <div class="settings-section">
                            <label for="max-pages-input">최대 크롤링 페이지 수 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링할 최대 페이지 수를 설정하세요.</span></span>:</label>
                            <input type="number" id="max-pages-input" value="${settings.maxPages}" title="크롤링할 최대 페이지 수를 설정하세요." min="1">
                        </div>
                        <div class="settings-section">
                            <label for="user-agent-select">사용자 에이전트(User-Agent) <span class="tooltip">ℹ️<span class="tooltiptext">크롤링에 사용할 사용자 에이전트를 선택하거나 추가할 수 있습니다.</span></span>:</label>
                            <select id="user-agent-select" multiple size="6" title="크롤링에 사용할 사용자 에이전트를 선택하세요.">
                                ${settings.userAgents.map((ua, idx) => `<option value="${idx}" ${idx === settings.userAgentIndex ? 'selected' : ''}>${ua}</option>`).join('')}
                            </select>
                            <button id="add-user-agent" title="새 사용자 에이전트를 추가합니다." style="margin-top: 5px; padding: 5px 10px; border: none; background-color: #2196F3; color: white; border-radius: 4px; font-size: 12px;">추가</button>
                            <button id="remove-user-agent" title="선택한 사용자 에이전트를 제거합니다." style="margin-top: 5px; padding: 5px 10px; border: none; background-color: #f44336; color: white; border-radius: 4px; font-size: 12px;">제거</button>
                        </div>
                        <!-- 콘텐츠 유형 섹션 -->
                        <div class="settings-section">
                            <label>콘텐츠 유형:</label>
                            <div>
                                <strong>포함할 유형:</strong>
                                <textarea id="content-include-input" rows="3" title="크롤링할 콘텐츠 유형을 입력하세요. 각 유형은 쉼표로 구분됩니다. 예: text/html, application/json">${settings.contentTypes.include.join(', ')}</textarea>
                            </div>
                            <div>
                                <strong>제외할 유형:</strong>
                                <textarea id="content-exclude-input" rows="3" title="크롤링에서 제외할 콘텐츠 유형을 입력하세요. 각 유형은 쉼표로 구분됩니다. 예: image/jpeg, video/mp4">${settings.contentTypes.exclude.join(', ')}</textarea>
                            </div>
                        </div>
                        <!-- 타임아웃 섹션 -->
                        <div class="settings-section">
                            <label for="timeout-input">크롤링 타임아웃 (ms) <span class="tooltip">ℹ️<span class="tooltiptext">요청이 완료되기까지 기다릴 최대 시간을 밀리초 단위로 설정합니다.</span></span>:</label>
                            <input type="number" id="timeout-input" value="${settings.timeout}" title="크롤링 요청의 타임아웃을 밀리초 단위로 설정하세요." min="1000" step="1000">
                        </div>
                        <!-- 키워드 섹션 -->
                        <div class="settings-section">
                            <label for="include-keywords-input">포함할 키워드 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링할 페이지에 반드시 포함되어야 하는 키워드를 입력하세요. 각 키워드는 줄바꿈으로 구분됩니다.</span></span>:</label>
                            <textarea id="include-keywords-input" rows="3" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="크롤링할 페이지에 반드시 포함되어야 하는 키워드를 입력하세요. 각 키워드는 줄바꿈으로 구분됩니다.">${settings.includeKeywords.join('\n')}</textarea>
                        </div>
                        <div class="settings-section">
                            <label for="exclude-keywords-input">제외할 키워드 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링할 페이지에 포함되면 안 되는 키워드를 입력하세요. 각 키워드는 줄바꿈으로 구분됩니다.</span></span>:</label>
                            <textarea id="exclude-keywords-input" rows="3" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="크롤링할 페이지에 포함되면 안 되는 키워드를 입력하세요. 각 키워드는 줄바꿈으로 구분됩니다.">${settings.excludeKeywords.join('\n')}</textarea>
                        </div>
                        <!-- 정규 표현식 키워드 섹션 -->
                        <div class="settings-section">
                            <label for="regex-include-input">포함할 키워드 (정규 표현식) <span class="tooltip">ℹ️<span class="tooltiptext">포함할 키워드의 정규 표현식을 입력하세요. 각 패턴은 줄바꿈으로 구분됩니다.</span></span>:</label>
                            <textarea id="regex-include-input" rows="3" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="포함할 키워드의 정규 표현식을 입력하세요. 각 패턴은 줄바꿈으로 구분됩니다.">${settings.regexInclude.join('\n')}</textarea>
                        </div>
                        <div class="settings-section">
                            <label for="regex-exclude-input">제외할 키워드 (정규 표현식) <span class="tooltip">ℹ️<span class="tooltiptext">제외할 키워드의 정규 표현식을 입력하세요. 각 패턴은 줄바꿈으로 구분됩니다.</span></span>:</label>
                            <textarea id="regex-exclude-input" rows="3" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="제외할 키워드의 정규 표현식을 입력하세요. 각 패턴은 줄바꿈으로 구분됩니다.">${settings.regexExclude.join('\n')}</textarea>
                        </div>
                        <!-- 언어 필터링 섹션 -->
                        <div class="settings-section">
                            <label for="language-select">언어 필터링 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링할 언어를 선택하세요. 선택하지 않으면 모든 언어가 포함됩니다.</span></span>:</label>
                            <select id="language-select" multiple size="10" title="크롤링할 언어를 선택하세요." style="padding: 5px; border: 1px solid #ccc; border-radius: 4px;">
                                <option value="en" ${settings.language.includes('en') ? 'selected' : ''}>영어 (en)</option>
                                <option value="ko" ${settings.language.includes('ko') ? 'selected' : ''}>한국어 (ko)</option>
                                <option value="zh" ${settings.language.includes('zh') ? 'selected' : ''}>중국어 (zh)</option>
                                <option value="ja" ${settings.language.includes('ja') ? 'selected' : ''}>일본어 (ja)</option>
                                <option value="ru" ${settings.language.includes('ru') ? 'selected' : ''}>러시아어 (ru)</option>
                                <option value="es" ${settings.language.includes('es') ? 'selected' : ''}>스페인어 (es)</option>
                                <option value="fr" ${settings.language.includes('fr') ? 'selected' : ''}>프랑스어 (fr)</option>
                                <option value="de" ${settings.language.includes('de') ? 'selected' : ''}>독일어 (de)</option>
                                <option value="it" ${settings.language.includes('it') ? 'selected' : ''}>이탈리아어 (it)</option>
                                <option value="pt" ${settings.language.includes('pt') ? 'selected' : ''}>포르투갈어 (pt)</option>
                                <option value="ar" ${settings.language.includes('ar') ? 'selected' : ''}>아랍어 (ar)</option>
                                <option value="hi" ${settings.language.includes('hi') ? 'selected' : ''}>힌디어 (hi)</option>
                                <option value="bn" ${settings.language.includes('bn') ? 'selected' : ''}>벵골어 (bn)</option>
                                <option value="pa" ${settings.language.includes('pa') ? 'selected' : ''}>펀자브어 (pa)</option>
                                <option value="nl" ${settings.language.includes('nl') ? 'selected' : ''}>네덜란드어 (nl)</option>
                                <option value="sv" ${settings.language.includes('sv') ? 'selected' : ''}>스웨덴어 (sv)</option>
                                <option value="tr" ${settings.language.includes('tr') ? 'selected' : ''}>터키어 (tr)</option>
                                <option value="vi" ${settings.language.includes('vi') ? 'selected' : ''}>베트남어 (vi)</option>
                                <option value="pl" ${settings.language.includes('pl') ? 'selected' : ''}>폴란드어 (pl)</option>
                                <option value="th" ${settings.language.includes('th') ? 'selected' : ''}>태국어 (th)</option>
                                <!-- 추가 언어 옵션 -->
                            </select>
                        </div>
                        <!-- 단어 수 섹션 -->
                        <div class="settings-section">
                            <label for="min-word-count-input">최소 단어 수 <span class="tooltip">ℹ️<span class="tooltiptext">수집할 페이지의 최소 단어 수를 설정하세요.</span></span>:</label>
                            <input type="number" id="min-word-count-input" value="${settings.minWordCount}" title="수집할 페이지의 최소 단어 수를 설정하세요." min="10" step="10">
                        </div>
                        <div class="settings-section">
                            <label for="max-word-count-input">최대 단어 수 <span class="tooltip">ℹ️<span class="tooltiptext">수집할 페이지의 최대 단어 수를 설정하세요.</span></span>:</label>
                            <input type="number" id="max-word-count-input" value="${settings.maxWordCount}" title="수집할 페이지의 최대 단어 수를 설정하세요." min="100" step="100">
                        </div>
                        <!-- 심층 크롤링 섹션 -->
                        <div class="settings-section">
                            <label for="max-depth-input">최대 크롤링 깊이 <span class="tooltip">ℹ️<span class="tooltiptext">링크를 추적할 최대 깊이를 설정하세요.</span></span>:</label>
                            <input type="number" id="max-depth-input" value="${settings.maxDepth}" title="링크를 추적할 최대 깊이를 설정하세요." min="1" step="1">
                        </div>
                        <!-- 프록시 섹션 -->
                        <div class="settings-section">
                            <label for="proxy-input">프록시 서버 <span class="tooltip">ℹ️<span class="tooltiptext">프록시 서버 주소를 입력하세요. 예: http://proxyserver:port</span></span>:</label>
                            <input type="text" id="proxy-input" value="${settings.proxy}" placeholder="http://proxyserver:port" title="프록시 서버 주소를 입력하세요. (옵션)" style="padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;">
                        </div>
                        <!-- 데이터 필드 섹션 -->
                        <div class="settings-section">
                            <label>수집할 데이터 필드 <span class="tooltip">ℹ️<span class="tooltiptext">수집할 데이터의 필드를 선택하세요.</span></span>:</label>
                            <div class="checkbox-group">
                                <label><input type="checkbox" id="field-name" ${settings.dataFields.name ? 'checked' : ''}> 사이트 이름</label>
                                <label><input type="checkbox" id="field-url" ${settings.dataFields.url ? 'checked' : ''}> 주소</label>
                                <label><input type="checkbox" id="field-description" ${settings.dataFields.description ? 'checked' : ''}> 설명</label>
                                <label><input type="checkbox" id="field-html" ${settings.dataFields.html ? 'checked' : ''}> HTML 내용</label>
                                <label><input type="checkbox" id="field-category" ${settings.dataFields.category ? 'checked' : ''}> 분류</label>
                                <label><input type="checkbox" id="field-quality" ${settings.dataFields.quality ? 'checked' : ''}> 품질점수</label>
                                <label><input type="checkbox" id="field-language" ${settings.dataFields.language ? 'checked' : ''}> 언어</label>
                                <label><input type="checkbox" id="field-wordCount" ${settings.dataFields.wordCount ? 'checked' : ''}> 단어 수</label>
                                <label><input type="checkbox" id="field-metaTags" ${settings.dataFields.metaTags ? 'checked' : ''}> 메타 태그</label>
                                <label><input type="checkbox" id="field-headings" ${settings.dataFields.headings ? 'checked' : ''}> 헤딩</label>
                            </div>
                        </div>
                        <!-- 재시도 섹션 -->
                        <div class="settings-section">
                            <label for="retry-attempts-input">재시도 횟수 <span class="tooltip">ℹ️<span class="tooltiptext">요청 실패 시 재시도할 횟수를 설정하세요.</span></span>:</label>
                            <input type="number" id="retry-attempts-input" value="${settings.retryAttempts}" title="요청 실패 시 재시도할 횟수를 설정하세요." min="0" step="1">
                        </div>
                        <div class="settings-section">
                            <label for="retry-delay-input">재시도 딜레이 (ms) <span class="tooltip">ℹ️<span class="tooltiptext">재시도 시 대기할 시간을 밀리초 단위로 설정하세요.</span></span>:</label>
                            <input type="number" id="retry-delay-input" value="${settings.retryDelay}" title="재시도 시 대기할 시간을 밀리초 단위로 설정하세요." min="1000" step="500">
                        </div>
                        <!-- 스케줄링 섹션 -->
                        <div class="settings-section">
                            <label>크롤링 스케줄링 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링 작업을 예약하려면 활성화하세요.</span></span>:</label>
                            <div class="checkbox-group">
                                <label><input type="checkbox" id="schedule-enabled" ${settings.schedule.enabled ? 'checked' : ''} title="크롤링 작업을 예약하려면 체크하세요."> 스케줄링 활성화</label>
                            </div>
                            <div id="schedule-options" style="display: ${settings.schedule.enabled ? 'block' : 'none'}; margin-top: 10px;">
                                <label for="start-time-input">시작 시간 (HH:MM) <span class="tooltip">ℹ️<span class="tooltiptext">크롤링을 시작할 시간을 설정하세요.</span></span>:</label>
                                <input type="time" id="start-time-input" value="${settings.schedule.startTime}" title="크롤링을 시작할 시간을 설정하세요." style="padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;">
                                <label for="interval-input">크롤링 간격 (분) <span class="tooltip">ℹ️<span class="tooltiptext">크롤링 작업 간의 간격을 분 단위로 설정하세요.</span></span>:</label>
                                <input type="number" id="interval-input" value="${settings.schedule.interval / 60000}" title="크롤링 작업 간의 간격을 분 단위로 설정하세요." min="1" step="1">
                            </div>
                        </div>
                        <!-- 검색 엔진 선택 섹션 -->
                        <div class="settings-section">
                            <label>크롤링할 검색 엔진 선택 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링에 사용할 검색 엔진을 선택하세요.</span></span>:</label>
                            <div class="checkbox-group">
                                <label><input type="checkbox" id="engine-google" ${settings.searchEngines.google ? 'checked' : ''}> Google</label>
                                <label><input type="checkbox" id="engine-bing" ${settings.searchEngines.bing ? 'checked' : ''}> Bing</label>
                                <label><input type="checkbox" id="engine-yahoo" ${settings.searchEngines.yahoo ? 'checked' : ''}> Yahoo</label>
                                <label><input type="checkbox" id="engine-duckduckgo" ${settings.searchEngines.duckduckgo ? 'checked' : ''}> DuckDuckGo</label>
                                <label><input type="checkbox" id="engine-baidu" ${settings.searchEngines.baidu ? 'checked' : ''}> Baidu</label>
                                <label><input type="checkbox" id="engine-yandex" ${settings.searchEngines.yandex ? 'checked' : ''}> Yandex</label>
                                <label><input type="checkbox" id="engine-naver" ${settings.searchEngines.naver ? 'checked' : ''}> Naver</label>
                                <label><input type="checkbox" id="engine-daum" ${settings.searchEngines.daum ? 'checked' : ''}> Daum</label>
                                <label><input type="checkbox" id="engine-ask" ${settings.searchEngines.ask ? 'checked' : ''}> Ask.com</label>
                                <label><input type="checkbox" id="engine-ecosia" ${settings.searchEngines.ecosia ? 'checked' : ''}> Ecosia</label>
                                <label><input type="checkbox" id="engine-startpage" ${settings.searchEngines.startpage ? 'checked' : ''}> Startpage</label>
                                <label><input type="checkbox" id="engine-searx" ${settings.searchEngines.searx ? 'checked' : ''}> Searx</label>
                                <label><input type="checkbox" id="engine-dogpile" ${settings.searchEngines.dogpile ? 'checked' : ''}> Dogpile</label>
                                <label><input type="checkbox" id="engine-bingimages" ${settings.searchEngines.bingimages ? 'checked' : ''}> Bing Images</label>
                                <!-- 추가 검색 엔진 옵션 -->
                            </div>
                        </div>
                        <!-- 블랙리스트/화이트리스트 섹션 -->
                        <div class="settings-section">
                            <label for="blacklist-input">블랙리스트 도메인 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링에서 제외할 도메인을 입력하세요. 각 도메인은 줄바꿈으로 구분됩니다.</span></span>:</label>
                            <textarea id="blacklist-input" rows="5" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="크롤링에서 제외할 도메인을 입력하세요. 각 도메인은 줄바꿈으로 구분됩니다.">${settings.blacklist.join('\n')}</textarea>
                        </div>
                        <div class="settings-section">
                            <label for="whitelist-input">화이트리스트 도메인 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링을 허용할 도메인을 입력하세요. 각 도메인은 줄바꿈으로 구분됩니다.</span></span>:</label>
                            <textarea id="whitelist-input" rows="5" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="크롤링을 허용할 도메인을 입력하세요. 각 도메인은 줄바꿈으로 구분됩니다.">${settings.whitelist.join('\n')}</textarea>
                        </div>
                        <!-- robots.txt 준수 섹션 -->
                        <div class="settings-section">
                            <label for="respect-robots-checkbox">robots.txt 준수 <span class="tooltip">ℹ️<span class="tooltiptext">대상 사이트의 robots.txt 규칙을 준수하여 크롤링을 진행합니다.</span></span>:</label>
                            <input type="checkbox" id="respect-robots-checkbox" ${settings.respectRobots ? 'checked' : ''} title="대상 사이트의 robots.txt 규칙을 준수합니다.">
                        </div>
                        <!-- 동시 요청 섹션 -->
                        <div class="settings-section">
                            <label for="max-concurrent-requests-input">동시 요청 최대 수 <span class="tooltip">ℹ️<span class="tooltiptext">한 번에 요청할 수 있는 최대 동시 요청 수를 설정하세요.</span></span>:</label>
                            <input type="number" id="max-concurrent-requests-input" value="${settings.maxConcurrentRequests}" title="한 번에 요청할 수 있는 최대 동시 요청 수를 설정하세요." min="1" step="1">
                        </div>
                        <!-- 데이터 시각화 섹션 -->
                        <div class="settings-section">
                            <label for="data-visualization-checkbox">데이터 시각화 활성화 <span class="tooltip">ℹ️<span class="tooltiptext">수집된 데이터를 시각화하여 그래프로 표시합니다.</span></span>:</label>
                            <input type="checkbox" id="data-visualization-checkbox" ${settings.dataVisualization ? 'checked' : ''} title="수집된 데이터를 시각화합니다.">
                        </div>
                        <!-- CAPTCHA 처리 섹션 -->
                        <div class="settings-section">
                            <label for="enable-captchas-checkbox">CAPTCHA 처리 활성화 <span class="tooltip">ℹ️<span class="tooltiptext">CAPTCHA가 감지되었을 때 자동 처리를 활성화합니다. (제한적)</span></span>:</label>
                            <input type="checkbox" id="enable-captchas-checkbox" ${settings.enableCaptchas ? 'checked' : ''} title="CAPTCHA가 감지되었을 때 자동 처리를 활성화합니다.">
                        </div>
                        <!-- 내보내기 형식 섹션 -->
                        <div class="settings-section">
                            <label>데이터 내보내기 형식 <span class="tooltip">ℹ️<span class="tooltiptext">수집된 데이터를 내보낼 형식을 선택하세요.</span></span>:</label>
                            <div class="checkbox-group">
                                <label><input type="checkbox" id="export-csv" ${settings.exportFormats.csv ? 'checked' : ''}> CSV</label>
                                <label><input type="checkbox" id="export-json" ${settings.exportFormats.json ? 'checked' : ''}> JSON</label>
                                <label><input type="checkbox" id="export-xml" ${settings.exportFormats.xml ? 'checked' : ''}> XML</label>
                                <label><input type="checkbox" id="export-xls" ${settings.exportFormats.xls ? 'checked' : ''}> XLS</label>
                            </div>
                        </div>
                        <!-- 추가 로깅 섹션 -->
                        <div class="settings-section">
                            <label for="advanced-logging-checkbox">고급 로깅 활성화 <span class="tooltip">ℹ️<span class="tooltiptext">추가적인 로깅 정보를 활성화합니다.</span></span>:</label>
                            <input type="checkbox" id="advanced-logging-checkbox" ${settings.advancedLogging ? 'checked' : ''} title="추가적인 로깅 정보를 활성화합니다.">
                        </div>
                        <!-- 사용자 지정 키워드 분류 섹션 -->
                        <div class="settings-section">
                            <label for="custom-keywords-input">사용자 지정 키워드 분류 <span class="tooltip">ℹ️<span class="tooltiptext">사용자 지정 키워드를 추가하여 데이터 분류를 향상시킵니다. 형식: 카테고리:키워드1,키워드2,...</span></span>:</label>
                            <textarea id="custom-keywords-input" rows="5" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="사용자 지정 키워드를 추가하여 데이터 분류를 향상시킵니다. 형식: 카테고리:키워드1,키워드2,...\n예시:\n'IT:AI,Machine Learning,Blockchain'">${getCustomKeywordsText()}</textarea>
                        </div>
                        <button id="save-settings" title="설정을 저장합니다." style="margin-top: 10px; padding: 10px 20px; border: none; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px;">저장</button>
                        <button id="reset-settings" title="기본 설정으로 초기화합니다." style="margin-top: 10px; padding: 10px 20px; border: none; background-color: #f44336; color: white; border-radius: 4px; font-size: 14px;">초기화</button>
                    </div>
                </div>

                <!-- 모달 창 -->
                <div id="html-modal" class="modal">
                    <div class="modal-content">
                        <span class="close">&times;</span>
                        <pre id="modal-html-content"></pre>
                    </div>
                </div>
            </div>
        `);
        $('body').append(container);

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

        // 단축키 이벤트
        $(document).on('keydown', function(e) {
            if (e.ctrlKey) {
                switch (e.key) {
                    case '1':
                        $('.crawler-tab[data-tab="logs"]').click();
                        break;
                    case '2':
                        $('.crawler-tab[data-tab="data"]').click();
                        break;
                    case '3':
                        $('.crawler-tab[data-tab="settings"]').click();
                        break;
                    case 's':
                        e.preventDefault();
                        saveSettings();
                        break;
                    case 'p':
                        e.preventDefault();
                        if (!isCrawling) startCrawling();
                        break;
                    case 'q':
                        e.preventDefault();
                        if (isCrawling) stopCrawling();
                        break;
                    case 'd':
                        e.preventDefault();
                        downloadCSV();
                        break;
                    case 'j':
                        e.preventDefault();
                        downloadJSON();
                        break;
                    case 'x':
                        e.preventDefault();
                        downloadXML();
                        break;
                    case 'l':
                        e.preventDefault();
                        downloadXLS();
                        break;
                }
            }
        });

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

        // 추가/제거 사용자 에이전트 버튼 이벤트
        $('#add-user-agent').on('click', function() {
            const newUA = prompt("새 사용자 에이전트를 입력하세요:");
            if (newUA) {
                settings.userAgents.push(newUA);
                GM_setValue('userAgents', settings.userAgents);
                updateUserAgentSelect();
                log('새 사용자 에이전트가 추가되었습니다.', 'success');
            }
        });

        $('#remove-user-agent').on('click', function() {
            const selectedOptions = $('#user-agent-select').val();
            if (selectedOptions && selectedOptions.length > 0) {
                settings.userAgents = settings.userAgents.filter((_, idx) => !selectedOptions.includes(idx.toString()));
                GM_setValue('userAgents', settings.userAgents);
                settings.userAgentIndex = 0;
                GM_setValue('userAgentIndex', settings.userAgentIndex);
                updateUserAgentSelect();
                log('선택한 사용자 에이전트가 제거되었습니다.', 'success');
            } else {
                alert('제거할 사용자 에이전트를 선택하세요.');
            }
        });

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

        // 검색 필터 기능
        $('#search-filter').on('input', function() {
            filterTable($(this).val());
        });

        // 스케줄링 옵션 표시/숨기기
        $('#schedule-enabled').on('change', function() {
            if ($(this).is(':checked')) {
                $('#schedule-options').slideDown();
            } else {
                $('#schedule-options').slideUp();
            }
        });

        // 데이터 시각화 토글
        $('#data-visualization-checkbox').on('change', function() {
            settings.dataVisualization = $(this).is(':checked');
            GM_setValue('dataVisualization', settings.dataVisualization);
            $('#data-visualization').toggle(settings.dataVisualization);
            log('데이터 시각화 설정이 변경되었습니다.', 'success');
            if (settings.dataVisualization) {
                renderChart();
            } else {
                $('#data-chart').remove();
            }
        });

        // CAPTCHA 처리 토글
        $('#enable-captchas-checkbox').on('change', function() {
            settings.enableCaptchas = $(this).is(':checked');
            GM_setValue('enableCaptchas', settings.enableCaptchas);
            log('CAPTCHA 처리 설정이 변경되었습니다.', 'success');
        });

        // 사용자 지정 키워드 분류 섹션 추가
        // 이미 textarea에 설정됨

        // 모달 창 기능
        const modal = document.getElementById("html-modal");
        const span = document.getElementsByClassName("close")[0];
        span.onclick = function() { modal.style.display = "none"; }
        window.onclick = function(event) {
            if (event.target == modal) {
                modal.style.display = "none";
            }
        }

        // 시작 및 중지 버튼 이벤트
        $('#start-crawling').on('click', startCrawling);
        $('#stop-crawling').on('click', stopCrawling);

        // export 버튼 추가 이벤트
        // XML 다운로드
        $('#download-xml').on('click', downloadXML);
        // XLS 다운로드
        $('#download-xls').on('click', downloadXLS);

        // 초기 UI 업데이트
        updateLogsUI();
        updateDataUI();
        if (settings.dataVisualization) {
            renderChart();
        }
    }

    function currentTab() {
        return GM_getValue('currentTab', 'logs');
    }

    function saveCurrentTab(tab) {
        GM_setValue('currentTab', tab);
    }

    function updateUserAgentSelect() {
        $('#user-agent-select').empty();
        settings.userAgents.forEach((ua, idx) => {
            const selected = idx === settings.userAgentIndex ? 'selected' : '';
            $('#user-agent-select').append(`<option value="${idx}" ${selected}>${ua}</option>`);
        });
    }

    // 사용자 지정 키워드 텍스트 생성 함수
    function getCustomKeywordsText() {
        const customKeywords = settings.customKeywords || {};
        return Object.entries(customKeywords).map(([category, kws]) => `${category}:${kws.join(',')}`).join('\n');
    }

    // 사용자 지정 키워드 저장 및 로드 함수
    function parseCustomKeywords(text) {
        const lines = text.split('\n').map(line => line.trim()).filter(line => line);
        const customKeywords = {};
        lines.forEach(line => {
            const [category, kws] = line.split(':');
            if (category && kws) {
                customKeywords[category.trim()] = kws.split(',').map(kw => kw.trim()).filter(kw => kw);
            }
        });
        return customKeywords;
    }

    function combineKeywords() {
        const standardKeywords = {
            '교육': ['학교', '교육', '학생', '교수', '강의'],
            '기술': ['기술', '소프트웨어', '하드웨어', '프로그래밍', '개발'],
            '뉴스': ['뉴스', '속보', '기사', '보도'],
            '엔터테인먼트': ['영화', '음악', '연예', '게임', '드라마'],
            '스포츠': ['축구', '야구', '농구', '경기', '토너먼트'],
            '비즈니스': ['비즈니스', '경제', '주식', '투자', '시장'],
            '건강': ['건강', '의료', '헬스', '운동', '영양'],
            '과학': ['과학', '연구', '실험', '발견', '이론'],
            '문학': ['소설', '시', '문학', '작가', '책'],
            '여행': ['여행', '관광', '숙박', '비행기', '여권']
            // 추가 키워드 및 카테고리
        };
        const customKeywords = settings.customKeywords || {};

        for (const [category, kws] of Object.entries(customKeywords)) {
            if (standardKeywords[category]) {
                standardKeywords[category] = [...new Set([...standardKeywords[category], ...kws])];
            } else {
                standardKeywords[category] = kws;
            }
        }

        return standardKeywords;
    }

    // 로깅 함수
    function log(message, type = 'info') {
        const timestamp = new Date().toLocaleString();
        logs.push({ timestamp, message, type });
        GM_setValue('logs', logs);
        updateLogsUI();

        if (settings.advancedLogging) {
            // type을 콘솔 메서드로 매핑
            switch(type) {
                case 'info':
                    console.info(`[${timestamp}] [INFO] ${message}`);
                    break;
                case 'warning':
                    console.warn(`[${timestamp}] [WARNING] ${message}`);
                    break;
                case 'error':
                    console.error(`[${timestamp}] [ERROR] ${message}`);
                    break;
                case 'success':
                    console.log(`%c[${timestamp}] [SUCCESS] ${message}`, 'color: green; font-weight: bold;');
                    break;
                default:
                    console.log(`[${timestamp}] [${type.toUpperCase()}] ${message}`);
            }
        }
    }

    function updateLogsUI() {
        const logHtml = logs.map(log => `<div>[${log.timestamp}] [${log.type.toUpperCase()}] ${log.message}</div>`).join('');
        $('#log-messages').html(logHtml);
        $('.progress-bar-fill').css('width', `${progress.toFixed(2)}%`);
        $('.status-text').text(`진행률: ${progress.toFixed(2)}% (${currentPage}/${totalPages} 페이지)`);
    }

    // 데이터 업데이트 함수
    function updateDataUI() {
        const filter = $('#search-filter').val().toLowerCase();
        let filteredData = data;
        if (filter) {
            filteredData = data.filter(item =>
                (settings.dataFields.name && item.name.toLowerCase().includes(filter)) ||
                (settings.dataFields.url && item.url.toLowerCase().includes(filter)) ||
                (settings.dataFields.description && item.description.toLowerCase().includes(filter)) ||
                (settings.dataFields.html && item.html.toLowerCase().includes(filter)) ||
                (settings.dataFields.category && item.category.toLowerCase().includes(filter)) ||
                (settings.dataFields.language && item.language && item.language.toLowerCase().includes(filter)) ||
                (settings.dataFields.wordCount && item.wordCount.toString().includes(filter)) ||
                (settings.dataFields.metaTags && item.metaTags && Object.values(item.metaTags).some(v => v.toLowerCase().includes(filter))) ||
                (settings.dataFields.headings && item.headings && item.headings.some(h => h.toLowerCase().includes(filter)))
            );
        }

        let tableHtml = filteredData.map(item => `
            <tr>
                ${settings.dataFields.name ? `<td>${escapeHTML(item.name)}</td>` : ''}
                ${settings.dataFields.url ? `<td><a href="${escapeHTML(item.url)}" target="_blank">${escapeHTML(item.url)}</a></td>` : ''}
                ${settings.dataFields.description ? `<td>${escapeHTML(item.description)}</td>` : ''}
                ${settings.dataFields.html ? `<td><button class="view-html" data-html="${encodeURIComponent(item.html)}" title="HTML 내용을 봅니다.">📄 보기</button></td>` : ''}
                ${settings.dataFields.category ? `<td>${escapeHTML(item.category)}</td>` : ''}
                ${settings.dataFields.quality ? `<td>${escapeHTML(item.quality.toString())}</td>` : ''}
                ${settings.dataFields.language ? `<td>${escapeHTML(item.language)}</td>` : ''}
                ${settings.dataFields.wordCount ? `<td>${escapeHTML(item.wordCount.toString())}</td>` : ''}
                ${settings.dataFields.metaTags ? `<td>${escapeHTML(JSON.stringify(item.metaTags))}</td>` : ''}
                ${settings.dataFields.headings ? `<td>${escapeHTML(item.headings.join(', '))}</td>` : ''}
            </tr>
        `).join('');
        $('#data-table-body').html(tableHtml);

        // HTML 보기 모달
        $('.view-html').off('click').on('click', function() {
            const htmlContent = decodeURIComponent($(this).data('html'));
            $('#modal-html-content').text(htmlContent);
            $('#html-modal').show();
        });

        if (settings.dataVisualization) {
            renderChart();
        }
    }

    // 차트 렌더링 함수
    function renderChart() {
        if (!document.getElementById('data-chart').getContext) return;

        const ctx = document.getElementById('data-chart').getContext('2d');

        // 기존 차트 제거
        if (window.dataChartInstance) {
            window.dataChartInstance.destroy();
        }

        const categories = {};
        data.forEach(item => {
            const category = item.category || '기타';
            if (categories[category]) {
                categories[category]++;
            } else {
                categories[category] = 1;
            }
        });

        const chartData = {
            labels: Object.keys(categories),
            datasets: [{
                label: '카테고리별 데이터 수',
                data: Object.values(categories),
                backgroundColor: generateColors(Object.keys(categories).length)
            }]
        };

        window.dataChartInstance = new Chart(ctx, {
            type: 'pie',
            data: chartData,
            options: {
                responsive: true
            }
        });
    }

    // 색상 생성 함수
    function generateColors(count) {
        const colors = [];
        for(let i=0; i<count; i++) {
            const hue = i * (360 / count);
            colors.push(`hsl(${hue}, 70%, 50%)`);
        }
        return colors;
    }

    // HTML 이스케이프 함수
    function escapeHTML(str) {
        if (!str) return '';
        return str.replace(/&/g, "&amp;")
                  .replace(/</g, "&lt;")
                  .replace(/>/g, "&gt;")
                  .replace(/"/g, "&quot;")
                  .replace(/'/g, "&#039;");
    }

    // CSV, JSON, XML, XLS 다운로드 함수
    function downloadCSV() {
        if (data.length === 0) {
            log('다운로드할 데이터가 없습니다.', 'warning');
            return;
        }
        const headers = [];
        if (settings.dataFields.name) headers.push("사이트 이름");
        if (settings.dataFields.url) headers.push("주소");
        if (settings.dataFields.description) headers.push("설명");
        if (settings.dataFields.html) headers.push("📄 HTML 내용");
        if (settings.dataFields.category) headers.push("분류");
        if (settings.dataFields.quality) headers.push("품질점수");
        if (settings.dataFields.language) headers.push("언어");
        if (settings.dataFields.wordCount) headers.push("단어 수");
        if (settings.dataFields.metaTags) headers.push("메타 태그");
        if (settings.dataFields.headings) headers.push("헤딩");

        const csvRows = [
            headers.join(",")
        ];

        data.forEach(d => {
            const row = [];
            if (settings.dataFields.name) row.push(`"${escapeCSV(d.name)}"`);
            if (settings.dataFields.url) row.push(`"${escapeCSV(d.url)}"`);
            if (settings.dataFields.description) row.push(`"${escapeCSV(d.description)}"`);
            if (settings.dataFields.html) row.push(`"${escapeCSV(d.html)}"`);
            if (settings.dataFields.category) row.push(`"${escapeCSV(d.category)}"`);
            if (settings.dataFields.quality) row.push(`"${d.quality}"`);
            if (settings.dataFields.language) row.push(`"${escapeCSV(d.language)}"`);
            if (settings.dataFields.wordCount) row.push(`"${d.wordCount}"`);
            if (settings.dataFields.metaTags) row.push(`"${escapeCSV(JSON.stringify(d.metaTags))}"`);
            if (settings.dataFields.headings) row.push(`"${escapeCSV(d.headings.join('; '))}"`);
            csvRows.push(row.join(","));
        });

        const csvContent = "data:text/csv;charset=utf-8," + csvRows.join("\n");
        const encodedUri = encodeURI(csvContent);
        const link = document.createElement("a");
        link.setAttribute("href", encodedUri);
        const filename = "data.csv";
        link.setAttribute("download", filename);
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        log('CSV 파일이 다운로드되었습니다.', 'success');
    }

    function downloadJSON() {
        if (data.length === 0) {
            log('다운로드할 데이터가 없습니다.', 'warning');
            return;
        }
        const jsonContent = JSON.stringify(data, null, 2);
        const blob = new Blob([jsonContent], { type: "application/json" });
        const url = URL.createObjectURL(blob);
        const link = document.createElement("a");
        link.setAttribute("href", url);
        const filename = "data.json";
        link.setAttribute("download", filename);
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        log('JSON 파일이 다운로드되었습니다.', 'success');
    }

    function downloadXML() {
        if (data.length === 0) {
            log('다운로드할 데이터가 없습니다.', 'warning');
            return;
        }
        let xmlContent = '<?xml version="1.0" encoding="UTF-8"?>\n<data>\n';
        data.forEach(d => {
            xmlContent += `  <entry>\n`;
            if (settings.dataFields.name) xmlContent += `    <name>${escapeXML(d.name)}</name>\n`;
            if (settings.dataFields.url) xmlContent += `    <url>${escapeXML(d.url)}</url>\n`;
            if (settings.dataFields.description) xmlContent += `    <description>${escapeXML(d.description)}</description>\n`;
            if (settings.dataFields.html) xmlContent += `    <html><![CDATA[${d.html}]]></html>\n`;
            if (settings.dataFields.category) xmlContent += `    <category>${escapeXML(d.category)}</category>\n`;
            if (settings.dataFields.quality) xmlContent += `    <quality>${d.quality}</quality>\n`;
            if (settings.dataFields.language) xmlContent += `    <language>${escapeXML(d.language)}</language>\n`;
            if (settings.dataFields.wordCount) xmlContent += `    <wordCount>${d.wordCount}</wordCount>\n`;
            if (settings.dataFields.metaTags) xmlContent += `    <metaTags>${escapeXML(JSON.stringify(d.metaTags))}</metaTags>\n`;
            if (settings.dataFields.headings) xmlContent += `    <headings>${escapeXML(d.headings.join('; '))}</headings>\n`;
            xmlContent += `  </entry>\n`;
        });
        xmlContent += '</data>';

        const blob = new Blob([xmlContent], { type: "application/xml" });
        const url = URL.createObjectURL(blob);
        const link = document.createElement("a");
        link.setAttribute("href", url);
        const filename = "data.xml";
        link.setAttribute("download", filename);
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        log('XML 파일이 다운로드되었습니다.', 'success');
    }

    function downloadXLS() {
        if (data.length === 0) {
            log('다운로드할 데이터가 없습니다.', 'warning');
            return;
        }
        const worksheet = XLSX.utils.json_to_sheet(data.map(d => {
            const entry = {};
            if (settings.dataFields.name) entry['사이트 이름'] = d.name;
            if (settings.dataFields.url) entry['주소'] = d.url;
            if (settings.dataFields.description) entry['설명'] = d.description;
            if (settings.dataFields.html) entry['📄 HTML 내용'] = d.html;
            if (settings.dataFields.category) entry['분류'] = d.category;
            if (settings.dataFields.quality) entry['품질점수'] = d.quality;
            if (settings.dataFields.language) entry['언어'] = d.language;
            if (settings.dataFields.wordCount) entry['단어 수'] = d.wordCount;
            if (settings.dataFields.metaTags) entry['메타 태그'] = JSON.stringify(d.metaTags);
            if (settings.dataFields.headings) entry['헤딩'] = d.headings.join('; ');
            return entry;
        }));
        const workbook = XLSX.utils.book_new();
        XLSX.utils.book_append_sheet(workbook, worksheet, "Data");
        XLSX.writeFile(workbook, "data.xlsx");
        log('XLS 파일이 다운로드되었습니다.', 'success');
    }

    // XML 이스케이프 함수
    function escapeXML(str) {
        if (!str) return '';
        return str.replace(/&/g, "&amp;")
                  .replace(/</g, "&lt;")
                  .replace(/>/g, "&gt;")
                  .replace(/"/g, "&quot;")
                  .replace(/'/g, "&#039;");
    }

    // CSV 이스케이프 함수
    function escapeCSV(str) {
        if (!str) return '';
        return str.replace(/"/g, '""');
    }

    // 테이블 필터링 함수
    function filterTable(keyword) {
        $('#data-table-body tr').each(function() {
            const rowText = $(this).text().toLowerCase();
            if (rowText.includes(keyword.toLowerCase())) {
                $(this).show();
            } else {
                $(this).hide();
            }
        });
    }

    // 설정 저장 함수
    function saveSettings() {
        const delay = parseInt($('#delay-input').val(), 10);
        settings.delay = isNaN(delay) ? defaultSettings.delay : delay;

        const delayRandom = parseInt($('#delay-random-input').val(), 10);
        settings.delayRandom = isNaN(delayRandom) ? defaultSettings.delayRandom : delayRandom;

        const maxPages = parseInt($('#max-pages-input').val(), 10);
        settings.maxPages = isNaN(maxPages) ? defaultSettings.maxPages : maxPages;

        // 사용자 에이전트 선택
        const selectedUAIndex = parseInt($('#user-agent-select').val(), 10);
        settings.userAgentIndex = isNaN(selectedUAIndex) ? 0 : selectedUAIndex;

        const contentInclude = $('#content-include-input').val().split(',').map(ct => ct.trim()).filter(ct => ct);
        const contentExclude = $('#content-exclude-input').val().split(',').map(ct => ct.trim()).filter(ct => ct);
        settings.contentTypes.include = contentInclude.length > 0 ? contentInclude : defaultSettings.contentTypes.include;
        settings.contentTypes.exclude = contentExclude.length > 0 ? contentExclude : defaultSettings.contentTypes.exclude;

        const timeout = parseInt($('#timeout-input').val(), 10);
        settings.timeout = isNaN(timeout) ? defaultSettings.timeout : timeout;

        const includeKeywords = $('#include-keywords-input').val().split('\n').map(kw => kw.trim()).filter(kw => kw);
        settings.includeKeywords = includeKeywords;

        const excludeKeywords = $('#exclude-keywords-input').val().split('\n').map(kw => kw.trim()).filter(kw => kw);
        settings.excludeKeywords = excludeKeywords;

        const regexInclude = $('#regex-include-input').val().split('\n').map(rx => rx.trim()).filter(rx => rx);
        settings.regexInclude = regexInclude;

        const regexExclude = $('#regex-exclude-input').val().split('\n').map(rx => rx.trim()).filter(rx => rx);
        settings.regexExclude = regexExclude;

        const language = $('#language-select').val() || [];
        settings.language = language;

        const minWordCount = parseInt($('#min-word-count-input').val(), 10);
        settings.minWordCount = isNaN(minWordCount) ? defaultSettings.minWordCount : minWordCount;

        const maxWordCount = parseInt($('#max-word-count-input').val(), 10);
        settings.maxWordCount = isNaN(maxWordCount) ? defaultSettings.maxWordCount : maxWordCount;

        const maxDepth = parseInt($('#max-depth-input').val(), 10);
        settings.maxDepth = isNaN(maxDepth) ? defaultSettings.maxDepth : maxDepth;

        const proxy = $('#proxy-input').val().trim();
        settings.proxy = proxy || defaultSettings.proxy;

        // 데이터 필드 설정
        settings.dataFields.name = $('#field-name').is(':checked');
        settings.dataFields.url = $('#field-url').is(':checked');
        settings.dataFields.description = $('#field-description').is(':checked');
        settings.dataFields.html = $('#field-html').is(':checked');
        settings.dataFields.category = $('#field-category').is(':checked');
        settings.dataFields.quality = $('#field-quality').is(':checked');
        settings.dataFields.language = $('#field-language').is(':checked');
        settings.dataFields.wordCount = $('#field-wordCount').is(':checked');
        settings.dataFields.metaTags = $('#field-metaTags').is(':checked');
        settings.dataFields.headings = $('#field-headings').is(':checked');

        // 재시도 설정
        const retryAttempts = parseInt($('#retry-attempts-input').val(), 10);
        settings.retryAttempts = isNaN(retryAttempts) ? defaultSettings.retryAttempts : retryAttempts;

        const retryDelay = parseInt($('#retry-delay-input').val(), 10);
        settings.retryDelay = isNaN(retryDelay) ? defaultSettings.retryDelay : retryDelay;

        // 스케줄링 설정
        settings.schedule.enabled = $('#schedule-enabled').is(':checked');
        const startTime = $('#start-time-input').val();
        settings.schedule.startTime = startTime || defaultSettings.schedule.startTime;
        const interval = parseInt($('#interval-input').val(), 10);
        settings.schedule.interval = isNaN(interval) ? defaultSettings.schedule.interval : interval * 60000; // 분을 밀리초로 변환

        // 검색 엔진 설정
        const searchEngines = {
            google: $('#engine-google').is(':checked'),
            bing: $('#engine-bing').is(':checked'),
            yahoo: $('#engine-yahoo').is(':checked'),
            duckduckgo: $('#engine-duckduckgo').is(':checked'),
            baidu: $('#engine-baidu').is(':checked'),
            yandex: $('#engine-yandex').is(':checked'),
            naver: $('#engine-naver').is(':checked'),
            daum: $('#engine-daum').is(':checked'),
            ask: $('#engine-ask').is(':checked'),
            ecosia: $('#engine-ecosia').is(':checked'),
            startpage: $('#engine-startpage').is(':checked'),
            searx: $('#engine-searx').is(':checked'),
            dogpile: $('#engine-dogpile').is(':checked'),
            bingimages: $('#engine-bingimages').is(':checked')
            // 추가 검색 엔진 옵션
        };
        settings.searchEngines = searchEngines;

        // robots.txt 준수 설정
        settings.respectRobots = $('#respect-robots-checkbox').is(':checked');

        const blacklist = $('#blacklist-input').val().split('\n').map(d => d.trim()).filter(d => d);
        settings.blacklist = blacklist;

        const whitelist = $('#whitelist-input').val().split('\n').map(d => d.trim()).filter(d => d);
        settings.whitelist = whitelist;

        const maxConcurrentRequests = parseInt($('#max-concurrent-requests-input').val(), 10);
        settings.maxConcurrentRequests = isNaN(maxConcurrentRequests) ? defaultSettings.maxConcurrentRequests : maxConcurrentRequests;

        // 데이터 시각화 설정
        settings.dataVisualization = $('#data-visualization-checkbox').is(':checked');
        GM_setValue('dataVisualization', settings.dataVisualization);

        // CAPTCHA 처리 설정
        settings.enableCaptchas = $('#enable-captchas-checkbox').is(':checked');
        GM_setValue('enableCaptchas', settings.enableCaptchas);

        // 내보내기 형식 설정
        settings.exportFormats.csv = $('#export-csv').is(':checked');
        settings.exportFormats.json = $('#export-json').is(':checked');
        settings.exportFormats.xml = $('#export-xml').is(':checked');
        settings.exportFormats.xls = $('#export-xls').is(':checked');

        // 고급 로깅 설정
        settings.advancedLogging = $('#advanced-logging-checkbox').is(':checked');
        GM_setValue('advancedLogging', settings.advancedLogging);

        // 사용자 지정 키워드 분류 저장
        const customKeywordsText = $('#custom-keywords-input').val();
        const customKeywords = parseCustomKeywords(customKeywordsText);
        settings.customKeywords = customKeywords;
        GM_setValue('customKeywords', customKeywords);

        // 저장
        GM_setValue('delay', settings.delay);
        GM_setValue('delayRandom', settings.delayRandom);
        GM_setValue('maxPages', settings.maxPages);
        GM_setValue('userAgentIndex', settings.userAgentIndex);
        GM_setValue('contentTypes', settings.contentTypes);
        GM_setValue('timeout', settings.timeout);
        GM_setValue('includeKeywords', settings.includeKeywords);
        GM_setValue('excludeKeywords', settings.excludeKeywords);
        GM_setValue('regexInclude', settings.regexInclude);
        GM_setValue('regexExclude', settings.regexExclude);
        GM_setValue('language', settings.language);
        GM_setValue('minWordCount', settings.minWordCount);
        GM_setValue('maxWordCount', settings.maxWordCount);
        GM_setValue('maxDepth', settings.maxDepth);
        GM_setValue('proxy', settings.proxy);
        GM_setValue('dataFields', settings.dataFields);
        GM_setValue('retryAttempts', settings.retryAttempts);
        GM_setValue('retryDelay', settings.retryDelay);
        GM_setValue('schedule', settings.schedule);
        GM_setValue('searchEngines', settings.searchEngines);
        GM_setValue('blacklist', settings.blacklist);
        GM_setValue('whitelist', settings.whitelist);
        GM_setValue('respectRobots', settings.respectRobots);
        GM_setValue('maxConcurrentRequests', settings.maxConcurrentRequests);
        GM_setValue('exportFormats', settings.exportFormats);

        // totalPages 갱신
        totalPages = settings.maxPages;
        GM_setValue('totalPages', totalPages);

        log('설정이 저장되었습니다.', 'success');

        // 스케줄링 설정이 활성화된 경우 스케줄링 시작
        if (settings.schedule.enabled) {
            scheduleCrawling();
        }

        // UI 업데이트
        updateDataUI();
        if (settings.dataVisualization) {
            renderChart();
        }
    }

    // 설정 초기화 함수
    function resetSettings() {
        if (confirm("기본 설정으로 초기화하시겠습니까?")) {
            // 기본 설정으로 되돌리기
            GM_deleteValue('delay');
            GM_deleteValue('delayRandom');
            GM_deleteValue('maxPages');
            GM_deleteValue('userAgents');
            GM_deleteValue('userAgentIndex');
            GM_deleteValue('blacklist');
            GM_deleteValue('whitelist');
            GM_deleteValue('contentTypes');
            GM_deleteValue('timeout');
            GM_deleteValue('includeKeywords');
            GM_deleteValue('excludeKeywords');
            GM_deleteValue('regexInclude');
            GM_deleteValue('regexExclude');
            GM_deleteValue('language');
            GM_deleteValue('minWordCount');
            GM_deleteValue('maxWordCount');
            GM_deleteValue('maxDepth');
            GM_deleteValue('proxy');
            GM_deleteValue('dataFields');
            GM_deleteValue('retryAttempts');
            GM_deleteValue('retryDelay');
            GM_deleteValue('schedule');
            GM_deleteValue('searchEngines');
            GM_deleteValue('exportFormats');
            GM_deleteValue('respectRobots');
            GM_deleteValue('maxConcurrentRequests');
            GM_deleteValue('dataVisualization');
            GM_deleteValue('advancedLogging');
            GM_deleteValue('enableCaptchas');
            GM_deleteValue('customKeywords');

            // 기본 설정 적용
            Object.assign(settings, defaultSettings);
            GM_setValue('userAgents', settings.userAgents);
            GM_setValue('userAgentIndex', settings.userAgentIndex);
            GM_setValue('contentTypes', settings.contentTypes);
            GM_setValue('dataFields', settings.dataFields);
            GM_setValue('exportFormats', settings.exportFormats);
            GM_setValue('advancedLogging', settings.advancedLogging);
            GM_setValue('enableCaptchas', settings.enableCaptchas);

            // UI 업데이트
            $('input, textarea, select').each(function() {
                const id = $(this).attr('id');
                if (settings.hasOwnProperty(id)) {
                    $(this).val(settings[id]);
                }
            });

            // 사용자 지정 키워드 초기화
            $('#custom-keywords-input').val('');

            updateUserAgentSelect();
            updateDataUI();
            renderChart();
            log('기본 설정으로 초기화되었습니다.', 'success');
        }
    }

    // 크롤링 시작 함수
    async function startCrawling() {
        if (isCrawling) {
            log('이미 크롤링이 진행 중입니다.', 'warning');
            return;
        }
        // 활성화된 검색 엔진 확인
        if (!isAnySearchEngineEnabled()) {
            log('사용할 검색 엔진이 하나도 선택되지 않았습니다.', 'error');
            return;
        }
        isCrawling = true;
        GM_setValue('isCrawling', isCrawling);
        log('크롤링을 시작합니다.', 'info');
        setButtonStates();
        currentPage = GM_getValue('currentPage', 1);
        totalPages = GM_getValue('totalPages', settings.maxPages);
        GM_setValue('totalPages', totalPages);
        currentDepth = GM_getValue('currentDepth', 0);
        await crawlPage(currentDepth);
    }

    // 크롤링 중지 함수
    function stopCrawling() {
        if (!isCrawling) {
            log('크롤링이 진행 중이지 않습니다.', 'warning');
            return;
        }
        isCrawling = false;
        GM_setValue('isCrawling', isCrawling);
        log('크롤링이 중지되었습니다.', 'warning');
        setButtonStates();
    }

    // 버튼 상태 설정 함수
    function setButtonStates() {
        if (isCrawling) {
            $('#start-crawling').prop('disabled', true);
            $('#stop-crawling').prop('disabled', false);
        } else {
            $('#start-crawling').prop('disabled', false);
            $('#stop-crawling').prop('disabled', true);
        }
    }

    // 크롤링 함수
    async function crawlPage(currentDepthLevel) {
        try {
            while (isCrawling && currentPage <= totalPages && currentDepthLevel <= settings.maxDepth) {
                log(`페이지 ${currentPage} 크롤링 시작. (깊이: ${currentDepthLevel})`, 'info');

                // robots.txt 준수 체크
                if (settings.respectRobots) {
                    const robotsAllowed = await checkRobotsTxt(window.location.href);
                    if (!robotsAllowed) {
                        log(`robots.txt에 의해 크롤링이 금지된 페이지: ${window.location.href}`, 'warning');
                        break;
                    }
                }

                const searchResults = extractSearchResults();
                if (!searchResults) {
                    log('검색 결과를 찾을 수 없습니다.', 'error');
                    break;
                }

                for (let result of searchResults) {
                    if (!isCrawling) break;

                    const name = result.name;
                    const link = result.url;
                    const description = result.description;

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

                    // 블랙리스트/화이트리스트 검사
                    if (settings.whitelist.length > 0 && !settings.whitelist.some(domain => link.includes(domain))) {
                        log(`화이트리스트에 없는 도메인: ${link}`, 'warning');
                        continue;
                    }
                    if (settings.blacklist.some(domain => link.includes(domain))) {
                        log(`블랙리스트에 있는 도메인: ${link}`, 'warning');
                        continue;
                    }

                    // 언어 필터링 검사
                    if (settings.language.length > 0) {
                        const pageLanguage = await getPageLanguage(link);
                        if (!settings.language.includes(pageLanguage)) {
                            log(`선택된 언어에 맞지 않는 페이지: ${link} (${pageLanguage})`, 'warning');
                            continue;
                        }
                    }

                    // 키워드 포함/제외 검사
                    if (settings.includeKeywords.length > 0 && !settings.includeKeywords.some(kw => link.includes(kw) || description.includes(kw))) {
                        log(`포함 키워드가 없는 URL: ${link}`, 'warning');
                        continue;
                    }
                    if (settings.excludeKeywords.some(kw => link.includes(kw) || description.includes(kw))) {
                        log(`제외 키워드가 포함된 URL: ${link}`, 'warning');
                        continue;
                    }

                    // 정규 표현식 필터링
                    if (settings.regexInclude.length > 0) {
                        const matchesInclude = settings.regexInclude.some(pattern => new RegExp(pattern).test(link) || new RegExp(pattern).test(description));
                        if (!matchesInclude) {
                            log(`정규 표현식 포함 조건에 맞지 않는 URL: ${link}`, 'warning');
                            continue;
                        }
                    }
                    if (settings.regexExclude.length > 0) {
                        const matchesExclude = settings.regexExclude.some(pattern => new RegExp(pattern).test(link) || new RegExp(pattern).test(description));
                        if (matchesExclude) {
                            log(`정규 표현식 제외 조건에 맞는 URL: ${link}`, 'warning');
                            continue;
                        }
                    }

                    // 큐에 추가
                    requestQueue.push({ url: link, depth: currentDepthLevel });

                    // 최대 동시 요청 도달 시 대기
                    if (activeRequests >= settings.maxConcurrentRequests) {
                        break;
                    }
                }

                // 큐 처리 시작
                processQueue();

                // 진행률 업데이트
                progress = (currentPage / totalPages) * 100;
                GM_setValue('progress', progress);
                updateLogsUI();

                // 다음 페이지로 이동
                if (currentPage < totalPages && isCrawling) {
                    const nextPageUrl = getNextPageUrl();
                    if (nextPageUrl) {
                        currentPage++;
                        GM_setValue('currentPage', currentPage);
                        currentDepth++;
                        GM_setValue('currentDepth', currentDepth);
                        log(`다음 페이지로 이동: ${currentPage}`, 'info');
                        await dynamicDelay();
                        window.location.href = nextPageUrl;
                        return; // 페이지 이동 후 크롤링 이어서 하기 위해 함수 종료
                    } else {
                        log('다음 페이지 링크를 찾을 수 없습니다.', 'error');
                        break;
                    }
                } else {
                    break;
                }
            }

            // 큐가 비어있고 모든 요청이 완료된 경우 크롤링 완료
            if (isCrawling && currentPage > totalPages && activeRequests === 0 && requestQueue.length === 0) {
                log('크롤링이 완료되었습니다.', 'success');
                isCrawling = false;
                GM_setValue('isCrawling', isCrawling);
                setButtonStates();
                GM_setValue('progress', 100);
                $('.progress-bar-fill').css('width', `100%`);
                $('.status-text').text(`진행률: 100% (${currentPage}/${totalPages} 페이지)`);
            }
        } catch (error) {
            log(`크롤링 중 오류 발생: ${error.message}`, 'error');
        } finally {
            // 크롤링이 아직 진행 중인 경우 state를 유지
            if (!isCrawling) {
                // 크롤링이 중지된 경우에만 상태를 초기화
                GM_setValue('isCrawling', isCrawling);
                setButtonStates();
                log('크롤링이 중지되었습니다.', 'info');
            }
            // 페이지 이동 시에는 상태를 유지하고, 다음 페이지에서 계속 크롤링
        }
    }

    // 동시 요청 제어 함수
    async function processQueue() {
        while (requestQueue.length > 0 && activeRequests < settings.maxConcurrentRequests) {
            const { url, depth } = requestQueue.shift();
            activeRequests++;
            try {
                await handleRequest(url, depth);
            } catch (error) {
                log(`요청 처리 중 오류 발생: ${url} - ${error.message}`, 'error');
            } finally {
                activeRequests--;
                processQueue();
            }
        }
    }

    async function handleRequest(url, depthLevel) {
        if (!isCrawling) return;

        // 중복 검사
        if (visitedUrls.includes(url)) {
            log(`중복된 URL 발견: ${url}`, 'warning');
            return;
        }

        // 블랙리스트/화이트리스트 검사
        if (settings.whitelist.length > 0 && !settings.whitelist.some(domain => url.includes(domain))) {
            log(`화이트리스트에 없는 도메인: ${url}`, 'warning');
            return;
        }
        if (settings.blacklist.some(domain => url.includes(domain))) {
            log(`블랙리스트에 있는 도메인: ${url}`, 'warning');
            return;
        }

        // 언어 필터링 검사
        if (settings.language.length > 0) {
            const pageLanguage = await getPageLanguage(url);
            if (!settings.language.includes(pageLanguage)) {
                log(`선택된 언어에 맞지 않는 페이지: ${url} (${pageLanguage})`, 'warning');
                return;
            }
        }

        // 키워드 포함/제외 검사
        if (settings.includeKeywords.length > 0 && !settings.includeKeywords.some(kw => url.includes(kw))) {
            log(`포함 키워드가 없는 URL: ${url}`, 'warning');
            return;
        }
        if (settings.excludeKeywords.some(kw => url.includes(kw))) {
            log(`제외 키워드가 포함된 URL: ${url}`, 'warning');
            return;
        }

        // 정규 표현식 필터링
        if (settings.regexInclude.length > 0) {
            const matchesInclude = settings.regexInclude.some(pattern => new RegExp(pattern).test(url));
            if (!matchesInclude) {
                log(`정규 표현식 포함 조건에 맞지 않는 URL: ${url}`, 'warning');
                return;
            }
        }
        if (settings.regexExclude.length > 0) {
            const matchesExclude = settings.regexExclude.some(pattern => new RegExp(pattern).test(url));
            if (matchesExclude) {
                log(`정규 표현식 제외 조건에 맞는 URL: ${url}`, 'warning');
                return;
            }
        }

        // 데이터 수집
        try {
            const response = await fetchWithRetry(url, settings.retryAttempts, settings.retryDelay);
            const contentType = getContentType(response);
            if (!isContentTypeAllowed(contentType)) {
                log(`허용되지 않은 콘텐츠 유형(${contentType})의 URL: ${url}`, 'warning');
                return;
            }

            const pageHtml = response.responseText;
            const parser = new DOMParser();
            const pageDoc = parser.parseFromString(pageHtml, 'text/html');
            const textContent = pageDoc.body ? pageDoc.body.innerText : '';

            // CAPTCHA 감지 및 처리
            if (settings.enableCaptchas && detectCaptcha(pageDoc)) {
                await handleCaptcha(url);
                return;
            }

            // 단어 수 검사
            const wordCount = textContent.split(/\s+/).length;
            if (wordCount < settings.minWordCount) {
                log(`단어 수가 최소 기준 (${settings.minWordCount})에 미치지 못하는 URL: ${url} (${wordCount} 단어)`, 'warning');
                return;
            }
            if (wordCount > settings.maxWordCount) {
                log(`단어 수가 최대 기준 (${settings.maxWordCount})을 초과하는 URL: ${url} (${wordCount} 단어)`, 'warning');
                return;
            }

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

            // 언어 추출
            const language = pageDoc.documentElement.lang || 'unknown';

            // 키워드 분류
            const category = classifyKeywords(textContent);

            // 메타 태그 및 헤딩 추출
            const metaTags = extractMetaTags(pageDoc);
            const headings = extractHeadings(pageDoc);

            // 조건에 맞는 경우에만 데이터 추가
            if (category !== '기타' || qualityScore > 100) { // 예시 조건
                const dataEntry = {};
                if (settings.dataFields.name) dataEntry.name = extractName(pageDoc) || 'No Title';
                if (settings.dataFields.url) dataEntry.url = url;
                if (settings.dataFields.description) dataEntry.description = extractDescription(pageDoc) || 'No Description';
                if (settings.dataFields.html) dataEntry.html = pageHtml;
                if (settings.dataFields.category) dataEntry.category = category;
                if (settings.dataFields.quality) dataEntry.quality = qualityScore;
                if (settings.dataFields.language) dataEntry.language = language;
                if (settings.dataFields.wordCount) dataEntry.wordCount = wordCount;
                if (settings.dataFields.metaTags) dataEntry.metaTags = metaTags;
                if (settings.dataFields.headings) dataEntry.headings = headings;

                data.push(dataEntry);
                GM_setValue('data', data);
                updateDataUI();
                log(`데이터 수집 완료: ${url}`, 'success');

                // 데이터 시각화 업데이트
                if (settings.dataVisualization) {
                    renderChart();
                }
            } else {
                log(`조건에 맞지 않아 데이터에 추가되지 않은 URL: ${url}`, 'info');
            }

            // 링크 추적을 위한 추가 로직 (심층 크롤링)
            if (depthLevel < settings.maxDepth) {
                const additionalLinks = extractLinks(pageDoc);
                for (let additionalLink of additionalLinks) {
                    if (!visitedUrls.includes(additionalLink) && isValidURL(additionalLink)) {
                        requestQueue.push({ url: additionalLink, depth: depthLevel + 1 });
                    }
                }
            }

        } catch (error) {
            log(`데이터 수집 실패: ${url} - ${error.message}`, 'error');
            // 실패한 경우 data에 추가하지 않음
        }

        visitedUrls.push(url);
        GM_setValue('visitedUrls', visitedUrls);
    }

    // 크롤링 함수에 재시도 추가
    async function fetchWithRetry(url, attempts, delayMs) {
        for (let attempt = 1; attempt <= attempts; attempt++) {
            try {
                const response = await GM_xmlhttpRequestPromise(url);
                return response;
            } catch (error) {
                log(`요청 실패 (${attempt}/${attempts}): ${url} - ${error.message}`, 'warning');
                if (attempt < attempts) {
                    await delay(delayMs);
                } else {
                    throw error;
                }
            }
        }
    }

    // 활성화된 검색 엔진이 있는지 확인
    function isAnySearchEngineEnabled() {
        return Object.values(settings.searchEngines).some(enabled => enabled);
    }

    // 사용자 지정 키워드 분류 통합
    function classifyKeywords(text) {
        const standardKeywords = combineKeywords();
        for (const [category, kws] of Object.entries(standardKeywords)) {
            if (kws.some(kw => text.includes(kw))) {
                return category;
            }
        }
        return '기타';
    }

    // 검색 결과 추출 함수 (각 검색 엔진에 맞게 수정됨)
    function extractSearchResults() {
        let results = [];
        // 구글 검색 결과 추출 (업데이트된 셀렉터)
        if (window.location.hostname.includes('google.com') && settings.searchEngines.google) {
            const items = document.querySelectorAll('div.g, div[data-hveid]');
            items.forEach(item => {
                const nameElement = item.querySelector('h3');
                const linkElement = item.querySelector('a');
                let descElement = item.querySelector('.aCOpRe');
                if (!descElement) {
                    descElement = item.querySelector('.VwiC3b');
                }

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement.href : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // 빙 검색 결과 추출 (업데이트된 셀렉터)
        else if (window.location.hostname.includes('bing.com') && settings.searchEngines.bing) {
            const items = document.querySelectorAll('.b_algo');
            items.forEach(item => {
                const nameElement = item.querySelector('h2');
                const linkElement = nameElement ? nameElement.querySelector('a') : null;
                const descElement = item.querySelector('.b_caption p');

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement.href : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // 야후 검색 결과 추출 (업데이트된 셀렉터)
        else if (window.location.hostname.includes('search.yahoo.com') && settings.searchEngines.yahoo) {
            const items = document.querySelectorAll('div.dd.algo');
            items.forEach(item => {
                const nameElement = item.querySelector('h3.title');
                const linkElement = nameElement ? nameElement.querySelector('a') : null;
                const descElement = item.querySelector('.compText p');

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement.href : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // 네이버 검색 결과 추출 (업데이트된 셀렉터)
        else if (window.location.hostname.includes('naver.com') && settings.searchEngines.naver) {
            const items = document.querySelectorAll('.news.mynews.section._prs_nws');
            items.forEach(item => {
                const nameElement = item.querySelector('.news_area .news_tit');
                const linkElement = nameElement ? nameElement.href : '';
                const descElement = item.querySelector('.dsc_wrap');

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // 다음 검색 결과 추출 (업데이트된 셀렉터)
        else if (window.location.hostname.includes('daum.net') && settings.searchEngines.daum) {
            const items = document.querySelectorAll('.wrap_cont div');
            items.forEach(item => {
                const nameElement = item.querySelector('a.f_link_b');
                const linkElement = nameElement ? nameElement.href : '';
                const descElement = item.querySelector('.desc');

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // DuckDuckGo 검색 결과 추출 (업데이트된 셀렉터)
        else if (window.location.hostname.includes('duckduckgo.com') && settings.searchEngines.duckduckgo) {
            const items = document.querySelectorAll('.result');
            items.forEach(item => {
                const nameElement = item.querySelector('h2 a.result__a');
                const linkElement = nameElement ? nameElement.href : '';
                const descElement = item.querySelector('.result__snippet');

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // Baidu 검색 결과 추출 (예시)
        else if (window.location.hostname.includes('baidu.com') && settings.searchEngines.baidu) {
            const items = document.querySelectorAll('.result');
            items.forEach(item => {
                const nameElement = item.querySelector('h3 a');
                const linkElement = nameElement ? nameElement.href : '';
                const descElement = item.querySelector('.c-abstract');

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // Yandex 검색 결과 추출 (예시)
        else if (window.location.hostname.includes('yandex.com') && settings.searchEngines.yandex) {
            const items = document.querySelectorAll('.serp-item');
            items.forEach(item => {
                const nameElement = item.querySelector('.organic__title-link');
                const linkElement = nameElement ? nameElement.href : '';
                const descElement = item.querySelector('.text-container');

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // Ask.com 검색 결과 추출 (추가된 검색 엔진)
        else if (window.location.hostname.includes('ask.com') && settings.searchEngines.ask) {
            const items = document.querySelectorAll('.PartialSearchResults-item');
            items.forEach(item => {
                const nameElement = item.querySelector('h2 a');
                const linkElement = nameElement ? nameElement.href : '';
                const descElement = item.querySelector('.PartialSearchResults-item-abstract');

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // Ecosia 검색 결과 추출 (추가된 검색 엔진)
        else if (window.location.hostname.includes('ecosia.org') && settings.searchEngines.ecosia) {
            const items = document.querySelectorAll('.result-item');
            items.forEach(item => {
                const nameElement = item.querySelector('.result-title a');
                const linkElement = nameElement ? nameElement.href : '';
                const descElement = item.querySelector('.result-description');

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // Startpage 검색 결과 추출 (추가된 검색 엔진)
        else if (window.location.hostname.includes('startpage.com') && settings.searchEngines.startpage) {
            const items = document.querySelectorAll('.w-gl__result');
            items.forEach(item => {
                const nameElement = item.querySelector('.w-gl__result-title a');
                const linkElement = nameElement ? nameElement.href : '';
                const descElement = item.querySelector('.w-gl__result-text');

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // Searx 검색 결과 추출 (추가된 검색 엔진)
        else if (window.location.hostname.includes('searx') && settings.searchEngines.searx) {
            const items = document.querySelectorAll('.result');
            items.forEach(item => {
                const nameElement = item.querySelector('.result_title a');
                const linkElement = nameElement ? nameElement.href : '';
                const descElement = item.querySelector('.result_desc');

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // Dogpile 검색 결과 추출 (추가된 검색 엔진)
        else if (window.location.hostname.includes('dogpile.com') && settings.searchEngines.dogpile) {
            const items = document.querySelectorAll('.result_info');
            items.forEach(item => {
                const nameElement = item.querySelector('h3 a');
                const linkElement = nameElement ? nameElement.href : '';
                const descElement = item.querySelector('.result_snippet');

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // Bing Images 검색 결과 추출 (추가된 검색 엔진)
        else if (window.location.hostname.includes('bing.com') && settings.searchEngines.bingimages) {
            const items = document.querySelectorAll('.iusc');
            items.forEach(item => {
                const nameElement = item.querySelector('a.iusc');
                const linkElement = nameElement ? nameElement.href : '';
                const descElement = item.querySelector('.txt');

                const name = nameElement ? nameElement.innerText.trim() : 'No Title';
                const link = linkElement ? linkElement : '';
                const description = descElement ? descElement.innerText.trim() : 'No Description';

                if (link && isValidURL(link)) results.push({ name, url: link, description });
            });
        }
        // 기타 검색 엔진에 대한 추가 로직 필요
        else {
            log('지원되지 않거나 비활성화된 검색 엔진입니다.', 'warning');
        }

        return results.length > 0 ? results : null;
    }

    // 다음 페이지 URL 얻는 함수 (각 검색 엔진에 맞게 수정됨)
    function getNextPageUrl() {
        let nextPageUrl = null;
        // 구글 다음 페이지 링크
        if (window.location.hostname.includes('google.com') && settings.searchEngines.google) {
            const nextLink = document.querySelector('a#pnnext');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // 빙 다음 페이지 링크
        else if (window.location.hostname.includes('bing.com') && settings.searchEngines.bing) {
            const nextLink = document.querySelector('a.sb_pagN');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // 야후 다음 페이지 링크
        else if (window.location.hostname.includes('search.yahoo.com') && settings.searchEngines.yahoo) {
            const nextLink = document.querySelector('a.next');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // 네이버 다음 페이지 링크
        else if (window.location.hostname.includes('naver.com') && settings.searchEngines.naver) {
            const nextLink = document.querySelector('.sc_page_inner a.btn_next');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // 다음 다음 페이지 링크
        else if (window.location.hostname.includes('daum.net') && settings.searchEngines.daum) {
            const nextLink = document.querySelector('.paging a.btn_next');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // DuckDuckGo 다음 페이지 링크 (예시)
        else if (window.location.hostname.includes('duckduckgo.com') && settings.searchEngines.duckduckgo) {
            const nextLink = document.querySelector('a.js-next-page');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // Baidu 다음 페이지 링크 (예시)
        else if (window.location.hostname.includes('baidu.com') && settings.searchEngines.baidu) {
            const nextLink = document.querySelector('.n');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // Yandex 다음 페이지 링크 (예시)
        else if (window.location.hostname.includes('yandex.com') && settings.searchEngines.yandex) {
            const nextLink = document.querySelector('.pager__item_kind_next a');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // Ask.com 다음 페이지 링크 (추가된 검색 엔진)
        else if (window.location.hostname.includes('ask.com') && settings.searchEngines.ask) {
            const nextLink = document.querySelector('.pagination a.next');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // Ecosia 다음 페이지 링크 (추가된 검색 엔진)
        else if (window.location.hostname.includes('ecosia.org') && settings.searchEngines.ecosia) {
            const nextLink = document.querySelector('.pagination a.next');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // Startpage 다음 페이지 링크 (추가된 검색 엔진)
        else if (window.location.hostname.includes('startpage.com') && settings.searchEngines.startpage) {
            const nextLink = document.querySelector('.pagination a.next');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // Searx 다음 페이지 링크 (추가된 검색 엔진)
        else if (window.location.hostname.includes('searx') && settings.searchEngines.searx) {
            const nextLink = document.querySelector('.pagination a.next');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // Dogpile 다음 페이지 링크 (추가된 검색 엔진)
        else if (window.location.hostname.includes('dogpile.com') && settings.searchEngines.dogpile) {
            const nextLink = document.querySelector('.pagination a.next');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // Bing Images 다음 페이지 링크 (추가된 검색 엔진)
        else if (window.location.hostname.includes('bing.com') && settings.searchEngines.bingimages) {
            const nextLink = document.querySelector('a.sb_pagN');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // 기타 검색 엔진에 대한 추가 로직 필요

        log(`다음 페이지 URL: ${nextPageUrl || '없음'}`, 'info');
        return nextPageUrl;
    }

    // GM_xmlhttpRequest를 Promise로 래핑
    function GM_xmlhttpRequestPromise(url) {
        return new Promise((resolve, reject) => {
            let requestUrl = url;
            if (settings.proxy) {
                // 프록시 요청을 위한 URL 구성
                requestUrl = `${settings.proxy}?url=${encodeURIComponent(url)}`;
            }

            GM_xmlhttpRequest({
                method: "GET",
                url: requestUrl,
                headers: {
                    'User-Agent': getCurrentUserAgent(),
                    'Accept': '*/*'
                },
                timeout: settings.timeout,
                onload: function(response) {
                    if (response.status >= 200 && response.status < 300) {
                        resolve(response);
                    } else {
                        reject(new Error(`HTTP Status ${response.status}`));
                    }
                },
                onerror: function(error) {
                    reject(new Error('Request failed'));
                },
                ontimeout: function() {
                    reject(new Error('Request timed out'));
                }
            });
        });
    }

    // 현재 사용자 에이전트 얻는 함수
    function getCurrentUserAgent() {
        return settings.userAgents[settings.userAgentIndex % settings.userAgents.length];
    }

    // 콘텐츠 유형 얻는 함수
    function getContentType(response) {
        const contentTypeHeader = response.responseHeaders.split('\n').find(header => header.toLowerCase().startsWith('content-type'));
        return contentTypeHeader ? contentTypeHeader.split(':')[1].trim().split(';')[0] : '';
    }

    // 콘텐츠 유형 허용 여부 검사
    function isContentTypeAllowed(contentType) {
        if (settings.contentTypes.include.length > 0 && !settings.contentTypes.include.includes(contentType)) {
            return false;
        }
        if (settings.contentTypes.exclude.length > 0 && settings.contentTypes.exclude.includes(contentType)) {
            return false;
        }
        return true;
    }

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

    // 키워드 분류 함수 (사용자 지정 키워드 포함)
    function classifyKeywords(text) {
        const standardKeywords = combineKeywords();
        for (const [category, kws] of Object.entries(standardKeywords)) {
            if (kws.some(kw => text.includes(kw))) {
                return category;
            }
        }
        return '기타';
    }

    // 메타 태그 추출 함수
    function extractMetaTags(pageDoc) {
        const metaTags = {};
        const metas = pageDoc.querySelectorAll('meta');
        metas.forEach(meta => {
            const name = meta.getAttribute('name') || meta.getAttribute('property');
            const content = meta.getAttribute('content');
            if (name && content) {
                metaTags[name.toLowerCase()] = content;
            }
        });
        return metaTags;
    }

    // 헤딩 추출 함수
    function extractHeadings(pageDoc) {
        const headings = [];
        for (let i = 1; i <= 6; i++) {
            const hTags = pageDoc.querySelectorAll(`h${i}`);
            hTags.forEach(h => {
                headings.push(h.innerText.trim());
            });
        }
        return headings;
    }

    // 사용자 지정 키워드 분류 섹션 추가
    // 이미 textarea에 설정됨

    // URL 유효성 검사 함수
    function isValidURL(url) {
        try {
            new URL(url);
            return true;
        } catch (_) {
            return false;
        }
    }

    // robots.txt 확인 함수
    async function checkRobotsTxt(url) {
        try {
            const parsedUrl = new URL(url);
            const robotsUrl = `${parsedUrl.origin}/robots.txt`;
            const response = await GM_xmlhttpRequestPromise(robotsUrl);
            const robotsText = response.responseText;
            const parser = new RobotsParser(robotsUrl, robotsText);
            return parser.isAllowed(url, getCurrentUserAgent());
        } catch (error) {
            log(`robots.txt 확인 실패: ${error.message}`, 'warning');
            // robots.txt를 가져올 수 없으면 기본적으로 허용
            return true;
        }
    }

    // robots-parser 라이브러리 (간단한 구현)
    class RobotsParser {
        constructor(url, text) {
            this.rules = [];
            this.parse(text);
        }

        parse(text) {
            const lines = text.split('\n');
            let userAgents = [];
            for (let line of lines) {
                line = line.trim();
                if (line.startsWith('#') || line === '') continue;
                const parts = line.split(':');
                if (parts.length < 2) continue;
                const key = parts[0].trim().toLowerCase();
                const value = parts.slice(1).join(':').trim();
                if (key === 'user-agent') {
                    userAgents = [value.toLowerCase()];
                } else if (key === 'disallow') {
                    this.rules.push({ userAgents: [...userAgents], disallow: value });
                } else if (key === 'allow') {
                    this.rules.push({ userAgents: [...userAgents], allow: value });
                }
            }
        }

        isAllowed(url, ua) {
            const path = new URL(url).pathname;
            let allowed = true;
            for (let rule of this.rules) {
                if (rule.userAgents.includes('*') || rule.userAgents.includes(ua.toLowerCase())) {
                    if (rule.disallow && path.startsWith(rule.disallow)) {
                        allowed = false;
                    }
                    if (rule.allow && path.startsWith(rule.allow)) {
                        allowed = true;
                    }
                }
            }
            return allowed;
        }
    }

    // 큐에 있는 요청을 처리하는 딜레이 함수
    async function processQueueWithDelay() {
        if (requestQueue.length > 0 && activeRequests < settings.maxConcurrentRequests) {
            const { url, depth } = requestQueue.shift();
            activeRequests++;
            try {
                await handleRequest(url, depth);
            } catch (error) {
                log(`요청 처리 중 오류 발생: ${url} - ${error.message}`, 'error');
            } finally {
                activeRequests--;
                processQueueWithDelay();
            }
        }
    }

    // 크롤링 시작: 기존 페이지에서 크롤링 재개
    async function resumeCrawling() {
        if (isCrawling) {
            await crawlPage(currentDepth);
        }
    }

    // 크롤링 함수 (재작성)
    async function crawlPage(currentDepthLevel) {
        try {
            while (isCrawling && currentPage <= totalPages && currentDepthLevel <= settings.maxDepth) {
                log(`페이지 ${currentPage} 크롤링 시작. (깊이: ${currentDepthLevel})`, 'info');

                // robots.txt 준수 체크
                if (settings.respectRobots) {
                    const robotsAllowed = await checkRobotsTxt(window.location.href);
                    if (!robotsAllowed) {
                        log(`robots.txt에 의해 크롤링이 금지된 페이지: ${window.location.href}`, 'warning');
                        break;
                    }
                }

                const searchResults = extractSearchResults();
                if (!searchResults) {
                    log('검색 결과를 찾을 수 없습니다.', 'error');
                    break;
                }

                for (let result of searchResults) {
                    if (!isCrawling) break;

                    const name = result.name;
                    const link = result.url;
                    const description = result.description;

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

                    // 블랙리스트/화이트리스트 검사
                    if (settings.whitelist.length > 0 && !settings.whitelist.some(domain => link.includes(domain))) {
                        log(`화이트리스트에 없는 도메인: ${link}`, 'warning');
                        continue;
                    }
                    if (settings.blacklist.some(domain => link.includes(domain))) {
                        log(`블랙리스트에 있는 도메인: ${link}`, 'warning');
                        continue;
                    }

                    // 언어 필터링 검사
                    if (settings.language.length > 0) {
                        const pageLanguage = await getPageLanguage(link);
                        if (!settings.language.includes(pageLanguage)) {
                            log(`선택된 언어에 맞지 않는 페이지: ${link} (${pageLanguage})`, 'warning');
                            continue;
                        }
                    }

                    // 키워드 포함/제외 검사
                    if (settings.includeKeywords.length > 0 && !settings.includeKeywords.some(kw => link.includes(kw) || description.includes(kw))) {
                        log(`포함 키워드가 없는 URL: ${link}`, 'warning');
                        continue;
                    }
                    if (settings.excludeKeywords.some(kw => link.includes(kw) || description.includes(kw))) {
                        log(`제외 키워드가 포함된 URL: ${link}`, 'warning');
                        continue;
                    }

                    // 정규 표현식 필터링
                    if (settings.regexInclude.length > 0) {
                        const matchesInclude = settings.regexInclude.some(pattern => new RegExp(pattern).test(link) || new RegExp(pattern).test(description));
                        if (!matchesInclude) {
                            log(`정규 표현식 포함 조건에 맞지 않는 URL: ${link}`, 'warning');
                            continue;
                        }
                    }
                    if (settings.regexExclude.length > 0) {
                        const matchesExclude = settings.regexExclude.some(pattern => new RegExp(pattern).test(link) || new RegExp(pattern).test(description));
                        if (matchesExclude) {
                            log(`정규 표현식 제외 조건에 맞는 URL: ${link}`, 'warning');
                            continue;
                        }
                    }

                    // 큐에 추가
                    requestQueue.push({ url: link, depth: currentDepthLevel });

                    // 최대 동시 요청 도달 시 대기
                    if (activeRequests >= settings.maxConcurrentRequests) {
                        break;
                    }
                }

                // 큐 처리 시작
                processQueueWithDelay();

                // 진행률 업데이트
                progress = (currentPage / totalPages) * 100;
                GM_setValue('progress', progress);
                updateLogsUI();

                // 다음 페이지로 이동
                if (currentPage < totalPages && isCrawling) {
                    const nextPageUrl = getNextPageUrl();
                    if (nextPageUrl) {
                        currentPage++;
                        GM_setValue('currentPage', currentPage);
                        currentDepth++;
                        GM_setValue('currentDepth', currentDepth);
                        log(`다음 페이지로 이동: ${currentPage}`, 'info');
                        await dynamicDelay();
                        window.location.href = nextPageUrl;
                        return; // 페이지 이동 후 크롤링 이어서 하기 위해 함수 종료
                    } else {
                        log('다음 페이지 링크를 찾을 수 없습니다.', 'error');
                        break;
                    }
                } else {
                    break;
                }
            }

            // 큐가 비어있고 모든 요청이 완료된 경우 크롤링 완료
            if (isCrawling && currentPage > totalPages && activeRequests === 0 && requestQueue.length === 0) {
                log('크롤링이 완료되었습니다.', 'success');
                isCrawling = false;
                GM_setValue('isCrawling', isCrawling);
                setButtonStates();
                GM_setValue('progress', 100);
                $('.progress-bar-fill').css('width', `100%`);
                $('.status-text').text(`진행률: 100% (${currentPage}/${totalPages} 페이지)`);
            }
        } catch (error) {
            log(`크롤링 중 오류 발생: ${error.message}`, 'error');
        } finally {
            // 크롤링이 아직 진행 중인 경우 state를 유지
            if (!isCrawling) {
                // 크롤링이 중지된 경우에만 상태를 초기화
                GM_setValue('isCrawling', isCrawling);
                setButtonStates();
                log('크롤링이 중지되었습니다.', 'info');
            }
            // 페이지 이동 시에는 상태를 유지하고, 다음 페이지에서 계속 크롤링
        }
    }

    // 동시 요청 제어 함수
    async function handleRequest(url, depthLevel) {
        if (!isCrawling) return;

        // 중복 검사
        if (visitedUrls.includes(url)) {
            log(`중복된 URL 발견: ${url}`, 'warning');
            return;
        }

        // 블랙리스트/화이트리스트 검사
        if (settings.whitelist.length > 0 && !settings.whitelist.some(domain => url.includes(domain))) {
            log(`화이트리스트에 없는 도메인: ${url}`, 'warning');
            return;
        }
        if (settings.blacklist.some(domain => url.includes(domain))) {
            log(`블랙리스트에 있는 도메인: ${url}`, 'warning');
            return;
        }

        // 언어 필터링 검사
        if (settings.language.length > 0) {
            const pageLanguage = await getPageLanguage(url);
            if (!settings.language.includes(pageLanguage)) {
                log(`선택된 언어에 맞지 않는 페이지: ${url} (${pageLanguage})`, 'warning');
                return;
            }
        }

        // 키워드 포함/제외 검사
        if (settings.includeKeywords.length > 0 && !settings.includeKeywords.some(kw => url.includes(kw))) {
            log(`포함 키워드가 없는 URL: ${url}`, 'warning');
            return;
        }
        if (settings.excludeKeywords.some(kw => url.includes(kw))) {
            log(`제외 키워드가 포함된 URL: ${url}`, 'warning');
            return;
        }

        // 정규 표현식 필터링
        if (settings.regexInclude.length > 0) {
            const matchesInclude = settings.regexInclude.some(pattern => new RegExp(pattern).test(url));
            if (!matchesInclude) {
                log(`정규 표현식 포함 조건에 맞지 않는 URL: ${url}`, 'warning');
                return;
            }
        }
        if (settings.regexExclude.length > 0) {
            const matchesExclude = settings.regexExclude.some(pattern => new RegExp(pattern).test(url));
            if (matchesExclude) {
                log(`정규 표현식 제외 조건에 맞는 URL: ${url}`, 'warning');
                return;
            }
        }

        // 데이터 수집
        try {
            const response = await fetchWithRetry(url, settings.retryAttempts, settings.retryDelay);
            const contentType = getContentType(response);
            if (!isContentTypeAllowed(contentType)) {
                log(`허용되지 않은 콘텐츠 유형(${contentType})의 URL: ${url}`, 'warning');
                return;
            }

            const pageHtml = response.responseText;
            const parser = new DOMParser();
            const pageDoc = parser.parseFromString(pageHtml, 'text/html');
            const textContent = pageDoc.body ? pageDoc.body.innerText : '';

            // CAPTCHA 감지 및 처리
            if (settings.enableCaptchas && detectCaptcha(pageDoc)) {
                await handleCaptcha(url);
                return;
            }

            // 단어 수 검사
            const wordCount = textContent.split(/\s+/).length;
            if (wordCount < settings.minWordCount) {
                log(`단어 수가 최소 기준 (${settings.minWordCount})에 미치지 못하는 URL: ${url} (${wordCount} 단어)`, 'warning');
                return;
            }
            if (wordCount > settings.maxWordCount) {
                log(`단어 수가 최대 기준 (${settings.maxWordCount})을 초과하는 URL: ${url} (${wordCount} 단어)`, 'warning');
                return;
            }

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

            // 언어 추출
            const language = pageDoc.documentElement.lang || 'unknown';

            // 키워드 분류
            const category = classifyKeywords(textContent);

            // 메타 태그 및 헤딩 추출
            const metaTags = extractMetaTags(pageDoc);
            const headings = extractHeadings(pageDoc);

            // 조건에 맞는 경우에만 데이터 추가
            if (category !== '기타' || qualityScore > 100) { // 예시 조건
                const dataEntry = {};
                if (settings.dataFields.name) dataEntry.name = extractName(pageDoc) || 'No Title';
                if (settings.dataFields.url) dataEntry.url = url;
                if (settings.dataFields.description) dataEntry.description = extractDescription(pageDoc) || 'No Description';
                if (settings.dataFields.html) dataEntry.html = pageHtml;
                if (settings.dataFields.category) dataEntry.category = category;
                if (settings.dataFields.quality) dataEntry.quality = qualityScore;
                if (settings.dataFields.language) dataEntry.language = language;
                if (settings.dataFields.wordCount) dataEntry.wordCount = wordCount;
                if (settings.dataFields.metaTags) dataEntry.metaTags = metaTags;
                if (settings.dataFields.headings) dataEntry.headings = headings;

                data.push(dataEntry);
                GM_setValue('data', data);
                updateDataUI();
                log(`데이터 수집 완료: ${url}`, 'success');

                // 데이터 시각화 업데이트
                if (settings.dataVisualization) {
                    renderChart();
                }
            } else {
                log(`조건에 맞지 않아 데이터에 추가되지 않은 URL: ${url}`, 'info');
            }

            // 링크 추적을 위한 추가 로직 (심층 크롤링)
            if (depthLevel < settings.maxDepth) {
                const additionalLinks = extractLinks(pageDoc);
                for (let additionalLink of additionalLinks) {
                    if (!visitedUrls.includes(additionalLink) && isValidURL(additionalLink)) {
                        requestQueue.push({ url: additionalLink, depth: depthLevel + 1 });
                    }
                }
            }

        } catch (error) {
            log(`데이터 수집 실패: ${url} - ${error.message}`, 'error');
            // 실패한 경우 data에 추가하지 않음
        }

        visitedUrls.push(url);
        GM_setValue('visitedUrls', visitedUrls);
    }

    // XML 이스케이프 함수
    function escapeXML(str) {
        if (!str) return '';
        return str.replace(/&/g, "&amp;")
                  .replace(/</g, "&lt;")
                  .replace(/>/g, "&gt;")
                  .replace(/"/g, "&quot;")
                  .replace(/'/g, "&#039;");
    }

    // CSV 이스케이프 함수
    function escapeCSV(str) {
        if (!str) return '';
        return str.replace(/"/g, '""');
    }

    // XML 이스케이프 함수
    function escapeXML(str) {
        if (!str) return '';
        return str.replace(/&/g, "&amp;")
                  .replace(/</g, "&lt;")
                  .replace(/>/g, "&gt;")
                  .replace(/"/g, "&quot;")
                  .replace(/'/g, "&#039;");
    }

    // robots-parser 클래스 이미 정의됨

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

    // 설정 초기화 함수 이미 구현됨

    // 모달 창 기능 이미 구현됨

    // 시작 및 중지 버튼 기능 이미 구현됨

    // 기타 함수 이미 포함됨

    function init() {
        createUI();
        if (isCrawling) {
            log('크롤링이 이전에 중단된 상태에서 다시 시작됩니다.', 'info');
            setButtonStates();
            crawlPage(currentDepth); // 페이지 로드 후 크롤링 재개
        } else {
            log('크롤러가 초기화되었습니다.', 'info');
        }
    }

    // 페이지 로드 시 초기화
    window.addEventListener('load', init);

    // 추가적인 기능: HTML 내용 수집 및 텍스트 추출, 데이터 테이블에 표시, 모달 창으로 HTML 보기
    // 이미 기존 스크립트에서 구현됨

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

시작이나 중지나 초기화같은 기본적인 기능 같은 건 당연히 있어야 함 스크립트 안에 검색 기능 넣으면 뒤짐. "내가" 검색창에 직접 검색해야 함..

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

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

브라우저 내가 새로고침할 시 바로 크롤링 초기화되는 기능 추가

진행바를 더 자세히 ㄱㄱ

그 사이트에 대해 알 수 있는 방법

사이트 들어가면 자동 사진 캡쳐 방법........?

그 사이트 정보 통째로 따오기.....?

그 사이트 hlml 따오는 방법...?

광고 제외 기능 추가 더욱 고도화

기능 끌 수 있는 기능 추가

창 조절 기능 추가

크롤링 초기화 기능 더욱 고도화

그리고 초기화한 주제들을 기록하는 기능 추가해

(https://codebeautify.org/htmlviewer 이거 hlml 보여주는 사이트 거든?? 한 번 넣어보셈 ㄱㄱ)

아님 이 사이트를 바탕으로 html 보여주는 방식으로 ㄱㄱ

로그 기능 추가:
진행 상황이나 발생한 에러 등을 로그로 저장하여 추후 문제 해결이나 데이터 분석에 활용할 수 있습니다.

중복 검사:
이미 방문한 사이트나 수집한 데이터를 중복해서 처리하지 않도록 중복 검사를 추가하면 효율성을 높일 수 있습니다. 수집 속도 조절:
서버에 과부하를 주지 않도록 일정한 딜레이를 추가하거나, 요청 빈도를 조절하는 기능을 추가하면 좋습니다.

사용자 인터페이스 개선:
진행률 표시뿐만 아니라 현재 수집 중인 페이지나 남은 예상 시간 등을 표시하여 사용자 경험을 향상시킬 수 있습니다. 또 광고 사이트 걸러내는 것도 추가하면 괜찮을 듯 해.

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

자 이제 스크립트를 만들어줘 (그리고 크롤링의 기본적인 기능은 있어야 되는 건 알지?)

주요 추가 기능들이야 추가해

탭 인터페이스

탭 메뉴: 📜 로그, 📊 데이터, ⚙️ 설정의 세 가지 탭으로 구성되어 있습니다.

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

그냥 너는 아무 말없이 최고의 퀄리티로 무조건 되며 어마무시하게 개쩔게 만들어 알겠지?

데이터 칸을 더 효율성 좋게 그리고 가독성 좋게 그리고 디자인 좋게 다시 해줘.알겟찌? 그리고 가독성 좋게 스크롤바나 그런 것들을 추가해!

에러 핸들링 강화: 현재 기본적인 에러 로깅만 가능하지만, 특정 에러 상황에 따라 자동 복구나 재시도 로직을 추가할 수 있습니다.

사용자 인터페이스 향상: UI를 더 직관적으로 만들기 위해 CSS 및 JavaScript를 추가로 개선할 수 있습니다. 예를 들어, 진행률 바를 그래픽으로 표시하거나,
데이터 테이블에 페이징 기능을 추가하는 등의 개선이 가능합니다.
크롤링 속도 조절: 현재는 3초의 딜레이를 두고 있지만, 사용자 설정을 통해 조절할 수 있도록 하면 더 유용할 것입니다.

중복 데이터 방지: 이미 방문한 URL을 GM_setValue를 통해 저장하여 중복 크롤링을 방지하고 있습니다. 추가적으로 더 정교한 중복 검사 로직을 구현할 수 있습니다.

또 크롤링의 기본적인 기능은 무조건 있어야 해 알겠지?' 명령문을 확실히 똑같이 이행해서 만들어 알겠어?

인터페이스로 검색 기능 추가 금지.

명령문을 확실히 똑같이 이행해서 만들어 알겠어?
명령문을 확실히 똑같이 이행해서 만들어 알겠어?
명령문을 확실히 똑같이 이행해서 만들어 알겠어?

이거 딴 거 하려니깐 안 됨 초기화ㅇㅇ 그냥 진행률이나 그런 거 초기화 하는 기능추가. 그리고 초기화한 주제들을 기록하는 기능 추가해

검색 엔진별 크롤링 로직의 일관성 유지
현재 extractSearchResults 함수 내에서 Google과 Bing에 대한 크롤링 로직이 구현되어 있으나, 다른 검색 엔진에 대한 로직은 주석으로 처리되어 있습니다. 이는 스크립트의 완전성을 저해하며, 유지보수를 어렵게 만듭니다.

개선 방안:

검색 엔진별 핸들러 객체 , 에러 핸들링 개선
현재 크롤링 중 에러가 발생하면 로그를 남기지만, 에러의 원인을 보다 상세히 파악할 수 있는 방법이 부족합니다. 또한, 네트워크 요청 실패 시 모든 재시도 시도가 완료되었을 때의 처리가 부족합니다.

개선 방안:

브라우저 내비게이션 대신 AJAX 요청 사용: 페이지 전환 없이 배경에서 다음 페이지의 내용을 가져와 처리할 수 있습니다. 이를 통해 스크립트의 실행이 중단되지 않도록 합니다.
싱글 페이지 앱(SPA) 방식 고려: 만약 검색 엔진이 SPA 방식이라면, URL 변경 시 이벤트를 감지하여 크롤링을 계속할 수 있습니다. , 로딩 상태 표시: 대량의 데이터를 처리할 때 사용자에게 진행 상태를 명확히 알려주는 로딩 스피너나 애니메이션을 추가합니다.
에러 메시지 가시성 향상: 중요한 에러 메시지는 사용자에게 더 눈에 띄게 표시하여 즉각적으로 대응할 수 있도록 합니다.
설정 검증: 사용자 입력값에 대한 유효성을 검사하고, 잘못된 입력 시 경고를 표시하여 오류를 예방합니다. ,
내가 준 코드에 이 코드를 넣어줘야지 개선한 코드를 넣어줘야지

일단 최대한 니가 쓸 수 있는데 까지는 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그냥 코드만 써. ㅇㅇ최대한 길게 써. 최대한 길게!! 그리고 ㅈㄴ 오래길게 생각해봐 그게 맞는지 아닌지 ㅈㄴ 오래 생각해봐 ㅈㄴ 짫게 쓰면 사용작가 지랄할 지 안할지
일단 최대한 니가 쓸 수 있는데 까지는 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그냥 코드만 써. ㅇㅇ최대한 길게 써. 최대한 길게!! 그리고 ㅈㄴ 오래길게 생각해봐 그게 맞는지 아닌지 ㅈㄴ 오래 생각해봐 ㅈㄴ 짫게 쓰면 사용작가 지랄할 지 안할지
일단 최대한 니가 쓸 수 있는데 까지는 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그냥 코드만 써. ㅇㅇ최대한 길게 써. 최대한 길게!! 그리고 ㅈㄴ 오래길게 생각해봐 그게 맞는지 아닌지 ㅈㄴ 오래 생각해봐 ㅈㄴ 짫게 쓰면 사용작가 지랄할 지 안할지
일단 최대한 니가 쓸 수 있는데 까지는 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그냥 코드만 써. ㅇㅇ최대한 길게 써. 최대한 길게!! 그리고 ㅈㄴ 오래길게 생각해봐 그게 맞는지 아닌지 ㅈㄴ 오래 생각해봐 ㅈㄴ 짫게 쓰면 사용작가 지랄할 지 안할지
일단 최대한 니가 쓸 수 있는데 까지는 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그냥 코드만 써. ㅇㅇ최대한 길게 써. 최대한 길게!! 그리고 ㅈㄴ 오래길게 생각해봐 그게 맞는지 아닌지 ㅈㄴ 오래 생각해봐 ㅈㄴ 짫게 쓰면 사용작가 지랄할 지 안할지
일단 최대한 니가 쓸 수 있는데 까지는 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그냥 코드만 써. ㅇㅇ최대한 길게 써. 최대한 길게!! 그리고 ㅈㄴ 오래길게 생각해봐 그게 맞는지 아닌지 ㅈㄴ 오래 생각해봐 ㅈㄴ 짫게 쓰면 사용작가 지랄할 지 안할지
일단 최대한 니가 쓸 수 있는데 까지는 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그냥 코드만 써. ㅇㅇ최대한 길게 써. 최대한 길게!! 그리고 ㅈㄴ 오래길게 생각해봐 그게 맞는지 아닌지 ㅈㄴ 오래 생각해봐 ㅈㄴ 짫게 쓰면 사용작가 지랄할 지 안할지

일단 최대한 니가 쓸 수 있는데 까지는 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그냥 코드만 써. ㅇㅇ최대한 길게 써. 최대한 길게!! 그리고 ㅈㄴ 오래길게 생각해봐 그게 맞는지 아닌지 ㅈㄴ 오래 생각해봐 ㅈㄴ 짫게 쓰면 사용작가 지랄할 지 안할지

일단 최대한 니가 쓸 수 있는데 까지는 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그냥 코드만 써. ㅇㅇ최대한 길게 써. 최대한 길게!! 그리고 ㅈㄴ 오래길게 생각해봐 그게 맞는지 아닌지 ㅈㄴ 오래 생각해봐 ㅈㄴ 짫게 쓰면 사용작가 지랄할 지 안할지

일단 최대한 니가 쓸 수 있는데 까지는 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그냥 코드만 써. ㅇㅇ최대한 길게 써. 최대한 길게!! 그리고 ㅈㄴ 오래길게 생각해봐 그게 맞는지 아닌지 ㅈㄴ 오래 생각해봐 ㅈㄴ 짫게 쓰면 사용작가 지랄할 지 안할지

일단 최대한 니가 쓸 수 있는데 까지는 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그냥 코드만 써. ㅇㅇ최대한 길게 써. 최대한 길게!! 그리고 ㅈㄴ 오래길게 생각해봐 그게 맞는지 아닌지 ㅈㄴ 오래 생각해봐 ㅈㄴ 짫게 쓰면 사용작가 지랄할 지 안할지

일단 최대한 니가 쓸 수 있는데 까지는 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그냥 코드만 써. ㅇㅇ최대한 길게 써. 최대한 길게!! 그리고 ㅈㄴ 오래길게 생각해봐 그게 맞는지 아닌지 ㅈㄴ 오래 생각해봐 ㅈㄴ 짫게 쓰면 사용작가 지랄할 지 안할지
일단 최대한 니가 쓸 수 있는데 까지는 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그냥 코드만 써. ㅇㅇ최대한 길게 써. 최대한 길게!! 그리고 ㅈㄴ 오래길게 생각해봐 그게 맞는지 아닌지 ㅈㄴ 오래 생각해봐 ㅈㄴ 짫게 쓰면 사용작가 지랄할 지 안할지 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11 내가 말한 거 꼭 지키면 서 만들어!!!!!!!!!!!11