설정 쪽  다양하면서도 세심하게 만드셈

크롤링하는 사이트를 더 확실하게 할 수 있는 방법들을 추가하셈

                                      창이 너무 커졌어 적당하게 조절 하는 기능 추가해                                                                                                                                                                                                                                                     // ==UserScript==
// @name         Advanced Web Crawler Enhanced
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  고급 웹 크롤러 스크립트 with Enhanced Settings and 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*
// @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
// ==/UserScript==

(function() {
    'use strict';

    // 기본 설정
    const defaultSettings = {
        delay: 3000, // 기본 딜레이 3초
        delayRandom: 1000, // 딜레이 랜덤 범위 ±1초
        maxPages: 5, // 기본 최대 크롤링 페이지 수
        userAgents: [ // 사용자 에이전트 리스트
            'AdvancedWebCrawler/3.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',
            // 추가 사용자 에이전트
        ],
        userAgentIndex: 0, // 현재 사용자 에이전트 인덱스
        blacklist: [],
        whitelist: [],
        contentTypes: {
            include: ['text/html'],
            exclude: []
        },
        timeout: 10000, // 기본 타임아웃 10초
        includeKeywords: [],
        excludeKeywords: [],
        language: [], // 언어 필터링 (예: ['en', 'ko'])
        minWordCount: 100, // 최소 단어 수
        maxWordCount: 10000, // 최대 단어 수
        maxDepth: 2, // 링크 추적 최대 깊이
        proxy: '', // 프록시 서버 URL
        regexInclude: [], // 포함 키워드 정규 표현식
        regexExclude: [], // 제외 키워드 정규 표현식
        dataFields: {
            name: true,
            url: true,
            description: true,
            html: false,
            category: true,
            quality: true
        },
        schedule: {
            enabled: false,
            startTime: '00:00', // 형식: 'HH:MM'
            interval: 3600000 // 밀리초 단위 (예: 1시간)
        },
        searchEngines: {
            google: true,
            bing: true,
            yahoo: true,
            duckduckgo: true,
            baidu: true,
            yandex: true,
            naver: true,
            daum: true
        },
        retryAttempts: 3, // 재시도 횟수
        retryDelay: 2000, // 재시도 간 딜레이 (ms)
        respectRobots: true, // robots.txt 준수 여부
        maxConcurrentRequests: 5 // 동시 요청 최대 수
    };

    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),
        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),
        regexInclude: GM_getValue('regexInclude', defaultSettings.regexInclude),
        regexExclude: GM_getValue('regexExclude', defaultSettings.regexExclude),
        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)
    };

    // 크롤링 상태 복원
    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: 10px; right: 10px; width: 900px; height: 950px; 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; }
            #crawler-tabs { display: flex; background: #f1f1f1; }
            .crawler-tab { flex: 1; padding: 10px; text-align: center; cursor: pointer; }
            .crawler-tab.active { background: #ddd; }
            #crawler-content { padding: 10px; overflow: auto; height: 830px; }
            /* 추가적인 스타일 */
            table { width: 100%; border-collapse: collapse; }
            th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
            th { background-color: #f2f2f2; }
            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: 25px; 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; }
            .close { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
            .control-buttons { margin: 10px 0; }
            .control-buttons button { padding: 10px 20px; margin-right: 10px; cursor: pointer; }
            .status-text { margin-top: 10px; font-weight: bold; }
            /* 설정 섹션 스타일 */
            .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; }
            .settings-section .checkbox-group { display: flex; flex-wrap: wrap; }
            .settings-section .checkbox-group label { margin-right: 10px; }
            .settings-section .radio-group { display: flex; flex-wrap: wrap; }
            .settings-section .radio-group label { margin-right: 10px; }
            /* Tooltip 스타일 */
            .tooltip {
                position: relative;
                display: inline-block;
            }
            .tooltip .tooltiptext {
                visibility: hidden;
                width: 200px;
                background-color: #555;
                color: #fff;
                text-align: center;
                border-radius: 6px;
                padding: 5px;
                position: absolute;
                z-index: 1;
                bottom: 125%;
                left: 50%;
                margin-left: -100px;
                opacity: 0;
                transition: opacity 0.3s;
            }
            .tooltip:hover .tooltiptext {
                visibility: visible;
                opacity: 1;
            }
        `;
        $('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>
                            <button id="download-csv" title="수집된 데이터를 CSV 파일로 다운로드합니다.">📥 CSV 다운로드</button>
                            <button id="download-json" title="수집된 데이터를 JSON 파일로 다운로드합니다.">📥 JSON 다운로드</button>
                        </div>
                        <div id="log-messages" style="height: 400px; overflow-y: auto;">로그 내용이 여기에 표시됩니다.</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;">
                        <table>
                            <thead>
                                <tr>
                                    ${settings.dataFields.name ? '<th>사이트 이름</th>' : ''}
                                    ${settings.dataFields.url ? '<th>주소</th>' : ''}
                                    ${settings.dataFields.description ? '<th>설명</th>' : ''}
                                    ${settings.dataFields.html ? '<th>HTML</th>' : ''}
                                    ${settings.dataFields.category ? '<th>분류</th>' : ''}
                                    ${settings.dataFields.quality ? '<th>품질</th>' : ''}
                                </tr>
                            </thead>
                            <tbody id="data-table-body">
                                <!-- 데이터 행이 여기에 추가됩니다. -->
                            </tbody>
                        </table>
                    </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="5">
                                ${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;">사용자 에이전트 추가</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%;" 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%;" 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%;" 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%;" 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="5" title="크롤링할 언어를 선택하세요. 선택하지 않으면 모든 언어가 포함됩니다.">
                                <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>
                                <!-- 추가 언어 옵션 -->
                            </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="프록시 서버 주소를 입력하세요. (옵션)">
                        </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>
                            </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="크롤링을 시작할 시간을 설정하세요.">
                                <label for="interval-input">크롤링 간격 (밀리초) <span class="tooltip">ℹ️<span class="tooltiptext">크롤링 작업 간의 간격을 밀리초 단위로 설정하세요.</span></span>:</label>
                                <input type="number" id="interval-input" value="${settings.schedule.interval}" title="크롤링 작업 간의 간격을 밀리초 단위로 설정하세요." min="60000" step="60000">
                            </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>
                                <!-- 추가 검색 엔진 옵션 -->
                            </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%;" 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%;" title="크롤링을 허용할 도메인을 입력하세요. 각 도메인은 줄바꿈으로 구분됩니다.">${settings.whitelist.join('\n')}</textarea>
                        </div>
                        <!-- 기타 설정 섹션 -->
                        <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>
                        <button id="save-settings" title="설정을 저장합니다." style="margin-top: 10px; padding: 5px 10px;">저장</button>
                    </div>
                </div>

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

        // 스타일 적용
        $('head').append(`<style>${style}</style>`);

        // 탭 전환 이벤트
        $('.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;
                }
            }
        });

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

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

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

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

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

        // 모달 창 기능
        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);

        // 초기 UI 업데이트
        updateLogsUI();
        updateDataUI();
    }

    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 log(message, type = 'info') {
        const timestamp = new Date().toLocaleString();
        logs.push({ timestamp, message, type });
        GM_setValue('logs', logs);
        updateLogsUI();

        // 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.category && item.category.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>` : ''}
            </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();
        });
    }

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

    // 설정 저장 함수
    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');

        // 재시도 설정
        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;

        // 검색 엔진 설정
        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')
        };
        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;

        // 저장
        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);

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

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

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

    // 크롤링 시작 함수
    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(currentDepth) {
        try {
            while (isCrawling && currentPage <= totalPages && currentDepth <= settings.maxDepth) {
                log(`페이지 ${currentPage} 크롤링 시작. (깊이: ${currentDepth})`, '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;
                        }
                    }

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

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

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

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

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

                        // 조건에 맞는 경우에만 데이터 추가
                        if (category !== '기타' || qualityScore > 100) { // 예시 조건
                            const dataEntry = {};
                            if (settings.dataFields.name) dataEntry.name = name;
                            if (settings.dataFields.url) dataEntry.url = link;
                            if (settings.dataFields.description) dataEntry.description = description;
                            if (settings.dataFields.html) dataEntry.html = pageHtml;
                            if (settings.dataFields.category) dataEntry.category = category;
                            if (settings.dataFields.quality) dataEntry.quality = qualityScore;

                            data.push(dataEntry);
                            GM_setValue('data', data);
                            updateDataUI();
                            log(`데이터 수집 완료: ${link}`, 'success');
                        } else {
                            log(`조건에 맞지 않아 데이터에 추가되지 않은 URL: ${link}`, 'info');
                        }

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

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

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

                // 진행률 업데이트
                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) {
                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, depth) {
        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 : '';

            // 단어 수 검사
            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 category = classifyKeywords(textContent);

            // 조건에 맞는 경우에만 데이터 추가
            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;

                data.push(dataEntry);
                GM_setValue('data', data);
                updateDataUI();
                log(`데이터 수집 완료: ${url}`, 'success');
            } else {
                log(`조건에 맞지 않아 데이터에 추가되지 않은 URL: ${url}`, 'info');
            }

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

    // 크롤링 로직 수정: 동시 요청 제어 추가
    async function crawlPage(currentDepth) {
        try {
            while (isCrawling && currentPage <= totalPages && currentDepth <= settings.maxDepth) {
                log(`페이지 ${currentPage} 크롤링 시작. (깊이: ${currentDepth})`, '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: currentDepth });

                    // 최대 동시 요청 도달 시 대기
                    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');
            }
            // 페이지 이동 시에는 상태를 유지하고, 다음 페이지에서 계속 크롤링
        }
    }

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

    // 검색 결과 추출 함수 (각 검색 엔진에 맞게 수정됨)
    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 });
            });
        }
        // 기타 검색 엔진에 대한 추가 로직 필요
        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;
        }
        // 기타 검색 엔진에 대한 추가 로직 필요

        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()
                },
                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(error);
                },
                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 keywords = {
            '교육': ['학교', '교육', '학생', '교수', '강의'],
            '기술': ['기술', '소프트웨어', '하드웨어', '프로그래밍', '개발'],
            '뉴스': ['뉴스', '속보', '기사', '보도'],
            '엔터테인먼트': ['영화', '음악', '연예', '게임', '드라마'],
            '스포츠': ['축구', '야구', '농구', '경기', '토너먼트']
            // 추가 키워드 및 카테고리
        };
        for (const [category, kws] of Object.entries(keywords)) {
            if (kws.some(kw => text.includes(kw))) {
                return category;
            }
        }
        return '기타';
    }

    // CSV 다운로드 함수
    function downloadCSV() {
        if (data.length === 0) {
            log('다운로드할 데이터가 없습니다.', 'warning');
            return;
        }
        const csvRows = [
            ["사이트 이름", "주소", "설명", "분류", "품질점수"]
        ];

        data.forEach(d => {
            csvRows.push([
                `"${escapeCSV(d.name)}"`,
                `"${escapeCSV(d.url)}"`,
                `"${escapeCSV(d.description)}"`,
                `"${escapeCSV(d.category)}"`,
                `"${d.quality}"`
            ].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);
        link.setAttribute("download", "data.csv");
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        log('CSV 파일이 다운로드되었습니다.', 'success');
    }

    // JSON 다운로드 함수
    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);
        link.setAttribute("download", "data.json");
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        log('JSON 파일이 다운로드되었습니다.', 'success');
    }

    // 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();
            }
        });
    }

    // 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 userAgent = '*';
            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') {
                    userAgent = value.toLowerCase();
                } else if (key === 'disallow') {
                    this.rules.push({ userAgent, disallow: value });
                } else if (key === 'allow') {
                    this.rules.push({ userAgent, allow: value });
                }
            }
        }

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

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

    // 큐에 있는 요청을 처리하는 딜레이 함수
    async function dynamicDelay() {
        const randomDelay = settings.delay + (Math.random() * settings.delayRandom * 2 - settings.delayRandom);
        await delay(randomDelay);
    }

    // 딜레이 함수
    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);

    // 모달 링크 추출 함수 (심층 크롤링 보조)
    function extractLinks(pageDoc) {
        const links = [];
        const anchorTags = pageDoc.querySelectorAll('a[href]');
        anchorTags.forEach(a => {
            let href = a.getAttribute('href');
            if (href.startsWith('/url?q=')) {
                href = new URLSearchParams(href.substring(7)).get('q');
            }
            if (isValidURL(href)) {
                links.push(href);
            }
        });
        return links;
    }

    // 페이지 언어 감지 함수
    async function getPageLanguage(url) {
        try {
            const response = await fetchWithRetry(url, settings.retryAttempts, settings.retryDelay);
            const pageHtml = response.responseText;
            const parser = new DOMParser();
            const pageDoc = parser.parseFromString(pageHtml, 'text/html');
            const lang = pageDoc.documentElement.lang || '';
            return lang.split('-')[0]; // 예: 'en-US' -> 'en'
        } catch (error) {
            log(`언어 감지 실패: ${url} - ${error.message}`, 'error');
            return '';
        }
    }

    // 스케줄링 크롤링 함수
    function scheduleCrawling() {
        if (!settings.schedule.enabled) return;

        const [hours, minutes] = settings.schedule.startTime.split(':').map(Number);
        const now = new Date();
        const start = new Date();
        start.setHours(hours, minutes, 0, 0);

        if (start < now) {
            start.setDate(start.getDate() + 1); // 다음 날로 설정
        }

        const firstDelay = start - now;
        setTimeout(() => {
            startCrawling();
            setInterval(() => {
                if (!isCrawling) {
                    startCrawling();
                }
            }, settings.schedule.interval);
        }, firstDelay);

        log(`크롤링 스케줄이 설정되었습니다. 시작 시간: ${settings.schedule.startTime}, 간격: ${settings.schedule.interval / 1000}초`, 'info');
    }

    // 이름 추출 함수 (사이트별 맞춤 구현 가능)
    function extractName(pageDoc) {
        const title = pageDoc.querySelector('title');
        return title ? title.innerText.trim() : 'No Title';
    }

    // 설명 추출 함수 (사이트별 맞춤 구현 가능)
    function extractDescription(pageDoc) {
        const metaDesc = pageDoc.querySelector('meta[name="description"]');
        return metaDesc ? metaDesc.getAttribute('content').trim() : 'No Description';
    }

})();