// ==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 보기
    // 이미 기존 스크립트에서 구현됨

})();                                                                         설정 쪽  다양하면서도 세심하게 만드셈 그리고 다양한 기능들을 엄청 많이 추가하셈 굉장히 실용적이면서도 참신해야 함 

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



그리고 오류나 그런것들만 남겨놓그 로그 창에 ㅇㅇ 크롤링 되고 있다 는 것들은 로그 창 밑에 남겨 두셈 . 그리고 오류나 그런 것들의 메세지를 빨간색으로 바꿔두셈 ㅇㅇ 그게 더 내가 보기 편함