// ==UserScript== // @name 知网资源批量下载 // @namespace https://cnki.net/ // @version 1.1.0 // @description 知网全自动批量下载PDF(从详情页解析PDF直链)- 断点续传,验证码暂停,批次日志 // @icon data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2032%2032%22%3E%3Crect%20x=%221%22%20y=%221%22%20width=%2230%22%20height=%2230%22%20rx=%228%22%20fill=%22%2312141a%22%20stroke=%22%235a4a24%22%20stroke-width=%222%22/%3E%3Cpath%20d=%22M9%208h14%22%20stroke=%22%238b93a3%22%20stroke-width=%222.6%22%20stroke-linecap=%22round%22/%3E%3Cpath%20d=%22M9%2013h14%22%20stroke=%22%238b93a3%22%20stroke-width=%222.6%22%20stroke-linecap=%22round%22/%3E%3Cpath%20d=%22M16%2017v7%22%20stroke=%22%23f5a524%22%20stroke-width=%223.2%22%20stroke-linecap=%22round%22/%3E%3Cpath%20d=%22M11.5%2020.5L16%2025l4.5-4.5%22%20fill=%22none%22%20stroke=%22%23f5a524%22%20stroke-width=%223.2%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22/%3E%3C/svg%3E // @match *://*.cnki.net/* // @match *://*cnki*.*/* // @match *://*.atrust.seu.edu.cn/* // @match *://119.45.145.238/* // @match *://119.45.237.51/* // @author lunan // @license MIT // @grant none // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // 持续检查页面元素(翻页后 DOM 会重建)。 // 找不到时给一次控制台提示,方便区分「脚本没装成功」和「知网改版导致失效」 var pageEnteredAt = Date.now(); var notFoundHinted = false; var mainInterval = setInterval(function() { var batchOpsBox = document.querySelector('#batchOpsBox'); var tableBody = document.querySelector('#gridTable table.result-table-list tbody'); if (!batchOpsBox || !tableBody) { if (!notFoundHinted && Date.now() - pageEnteredAt > 8000) { notFoundHinted = true; console.info('[知网资源批量下载] 本页没有检索结果表格(#batchOpsBox / #gridTable),面板只在检索结果页出现;若确认已在结果页,多半是知网改版导致脚本失效。'); } return; } if (document.getElementById('cnki-panel')) return; clearInterval(mainInterval); initPanel(); }, 1000); function initPanel() { var CONFIG = { restEvery: 50, restMin: 15, restMax: 30, maxPerSession: 200, cajFallback: false, // 找不到 PDF 直链时,是否退回下载默认格式(CAJ) autoCloseTab: false // 触发下载后自动关闭新标签页(默认关,避免误关验证页) }; var SETTINGS_KEY = 'cnki_settings'; var STORAGE_KEY = 'cnki_downloaded'; function loadSettings() { try { var saved = JSON.parse(localStorage.getItem(SETTINGS_KEY)); if (saved) { for (var k in saved) { if (CONFIG.hasOwnProperty(k)) CONFIG[k] = saved[k]; } } } catch(e) {} } function saveSettings() { localStorage.setItem(SETTINGS_KEY, JSON.stringify(CONFIG)); } loadSettings(); // 记录键归一化:抹平「扩展名 / 副本序号 / CNKI 附带的作者后缀 / 空白差异」 // 页面标题和下载文件名走的是同一套归一化,才能互相对得上 function normTitle(raw) { var s = String(raw == null ? '' : raw).trim(); s = s.replace(/\.(pdf|caj|nh|kdh|txt)$/i, ''); s = s.replace(/[((]\s*\d+\s*[))]\s*$/, ''); // 题名(1) s = s.replace(/[-_—]\s*\d+\s*$/, ''); // 题名-1 / 题名_2 // 只处理「下划线 + 2~4 字姓名」这种 CNKI 作者后缀,且要求前面题名够长 // 刻意不动空格和连字符:题名本身常含空格(如「图像隐写 算法」),误删会导致漏下 s = s.replace(/^(.{6,})_[^_\s\u3000]{2,4}$/, '$1'); s = s.replace(/[\s\u3000]+/g, ''); // 去掉所有空白 return s; } // 缓存已下载列表,避免高频 JSON.parse var _downloadedCache = null; function getDownloaded() { if (!_downloadedCache) { var raw = []; try { raw = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); } catch (e) { raw = []; } if (!Array.isArray(raw)) raw = []; var out = [], seen = {}, dirty = false; raw.forEach(function (t) { var k = normTitle(t); if (!k || seen[k]) { dirty = true; return; } seen[k] = 1; out.push(k); if (k !== t) dirty = true; }); _downloadedCache = out; if (dirty) localStorage.setItem(STORAGE_KEY, JSON.stringify(out)); } return _downloadedCache; } // 写入前先跟 localStorage 合并:多标签页同时跑时不会互相覆盖记录 function persistDownloaded(extra) { var fresh = []; try { fresh = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); } catch (e) { fresh = []; } if (!Array.isArray(fresh)) fresh = []; var seen = {}, out = []; fresh.concat(extra || []).forEach(function (t) { var k = normTitle(t); if (!k || seen[k]) return; seen[k] = 1; out.push(k); }); _downloadedCache = out; localStorage.setItem(STORAGE_KEY, JSON.stringify(out)); return out; } function saveDownloaded(title) { var key = normTitle(title); if (!key || getDownloaded().indexOf(key) !== -1) return; persistDownloaded([key]); } function isDownloaded(title) { return getDownloaded().indexOf(normTitle(title)) !== -1; } function getDownloadedCount() { return getDownloaded().length; } function clearDownloaded() { _downloadedCache = []; localStorage.removeItem(STORAGE_KEY); } // 别的标签页动了记录就丢掉本地缓存,下次读取时重新加载 window.addEventListener('storage', function (e) { if (!e.key || e.key === STORAGE_KEY) _downloadedCache = null; }); // ── CSS ── var style = document.createElement('style'); style.textContent = '\ #cnki-panel{--bg:#12141a;--bg2:#191c23;--line:#2a2f3a;--ink:#e7e9ee;--dim:#8b93a3;--amber:#f5a524;--green:#35c07a;--red:#f2545b;--blue:#5aa9f7;--violet:#b48ef0;--sans:"PingFang SC","HarmonyOS Sans SC","Source Han Sans SC","Microsoft YaHei",sans-serif;--mono:ui-monospace,"JetBrains Mono","Cascadia Mono",Consolas,monospace;position:fixed;bottom:22px;right:22px;z-index:99999;width:352px;max-height:90vh;overflow-y:auto;padding:0;color:var(--ink);font:13px/1.5 var(--sans);cursor:move;background:linear-gradient(180deg,#171a21,#0f1116);border:1px solid var(--line);border-radius:14px;box-shadow:0 18px 48px rgba(0,0,0,.55),0 1px 0 rgba(255,255,255,.04) inset;-webkit-font-smoothing:antialiased}\ #cnki-panel *{box-sizing:border-box}\ #cnki-panel::-webkit-scrollbar,#cnki-log::-webkit-scrollbar{width:7px;height:7px}\ #cnki-panel::-webkit-scrollbar-track,#cnki-log::-webkit-scrollbar-track{background:transparent}\ #cnki-panel::-webkit-scrollbar-thumb,#cnki-log::-webkit-scrollbar-thumb{background:#2c313c;border-radius:99px}\ #cnki-panel::-webkit-scrollbar-thumb:hover,#cnki-log::-webkit-scrollbar-thumb:hover{background:#3a4150}\ #cnki-head{display:flex;align-items:center;gap:8px;padding:12px 14px;border-bottom:1px solid var(--line);cursor:move}\ #cnki-dot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--dim);box-shadow:0 0 0 3px rgba(139,147,163,.12);transition:background .2s}\ #cnki-title{font:800 13.5px/1.2 var(--sans);letter-spacing:.06em;white-space:nowrap}\ #cnki-count{margin-left:auto;flex:none;font:10px/1 var(--mono);letter-spacing:.03em;color:#ffd479;background:rgba(245,165,36,.1);border:1px solid rgba(245,165,36,.26);border-radius:99px;padding:3px 7px;white-space:nowrap}\ #cnki-ver{flex:none;font:10px/1 var(--mono);letter-spacing:.03em;color:var(--dim);border:1px solid var(--line);border-radius:99px;padding:3px 7px}\ #cnki-panel.is-running #cnki-dot{background:var(--amber);box-shadow:0 0 0 3px rgba(245,165,36,.16);animation:cnkiPulse 1.6s ease-in-out infinite}\ #cnki-panel.is-resting #cnki-dot{background:var(--violet);box-shadow:0 0 0 3px rgba(180,142,240,.16);animation:cnkiPulse 1.1s ease-in-out infinite}\ #cnki-panel.is-paused #cnki-dot{background:var(--blue);box-shadow:0 0 0 3px rgba(90,169,247,.16)}\ #cnki-hud{padding:11px 14px 12px;border-bottom:1px solid var(--line)}\ #cnki-progress{min-height:17px;font-weight:600;font-size:12px;line-height:1.45;word-break:break-all}\ #cnki-countdown{margin-top:3px;min-height:16px;font:11px/1.5 var(--mono);color:var(--dim);word-break:break-all}\ #cnki-rail{margin-top:9px;height:3px;border-radius:99px;background:#22262f;overflow:hidden}\ #cnki-rail i{display:block;height:100%;width:var(--p,0%);border-radius:99px;background:linear-gradient(90deg,var(--amber),#ffd479);transition:width .35s cubic-bezier(.4,0,.2,1)}\ #cnki-panel.is-running #cnki-rail i{background-image:linear-gradient(90deg,var(--amber),#ffe3a3,var(--amber));background-size:200% 100%;animation:cnkiSheen 1.5s linear infinite}\ #cnki-body{display:flex;flex-direction:column;gap:9px;padding:11px 14px 14px}\ #cnki-panel button{display:block;width:100%;padding:10px 12px;border:1px solid transparent;border-radius:9px;cursor:pointer;font:600 13px/1 var(--sans);transition:background .15s,border-color .15s,color .15s,filter .15s,transform .08s}\ #cnki-panel button:active{transform:translateY(1px)}\ #cnki-btn-start{background:linear-gradient(180deg,#f7b53c,#e8960f);color:#241a00;box-shadow:0 6px 18px rgba(245,165,36,.2)}\ #cnki-btn-start:hover{filter:brightness(1.07)}\ #cnki-btn-pause{background:#22262f;color:var(--ink);border-color:var(--line)}\ #cnki-btn-pause:hover{border-color:#3a4150}\ #cnki-btn-stop{background:rgba(242,84,91,.12);color:#ff9197;border-color:rgba(242,84,91,.34)}\ #cnki-btn-stop:hover{background:rgba(242,84,91,.2)}\ #cnki-btn-skip-rest{background:rgba(53,192,122,.14);color:#5cd99a;border-color:rgba(53,192,122,.34)}\ #cnki-btn-skip-rest:hover{background:rgba(53,192,122,.22)}\ #cnki-btn-settings{background:#1c2028;color:var(--dim);border-color:var(--line);font-weight:500}\ #cnki-btn-settings:hover{color:var(--ink);border-color:#3a4150}\ #cnki-btn-import{background:#1c2028;color:#bda9f2;border-color:rgba(180,142,240,.28)}\ #cnki-btn-import:hover{border-color:rgba(180,142,240,.55);color:#d3c6fa}\ #cnki-btn-export{background:#1c2028;color:#9ccbfb;border-color:rgba(90,169,247,.28)}\ #cnki-btn-export:hover{border-color:rgba(90,169,247,.55);color:#c6e0fd}\ #cnki-btn-clear{background:transparent;color:#7b8394;border-color:var(--line);font-weight:500;font-size:12px}\ #cnki-btn-clear:hover{color:#ff9197;border-color:rgba(242,84,91,.4)}\ .cnki-util{display:grid;grid-template-columns:1fr 1fr 1fr;gap:7px}\ .cnki-util button{padding:9px 4px;font-size:11.5px}\ #cnki-btn-min{align-self:center;width:auto;background:transparent;color:#6f7787;border-color:transparent;font-weight:500;font-size:11.5px;padding:5px 14px}\ #cnki-btn-min:hover{color:var(--ink)}\ #cnki-captcha{display:none;margin:0 14px;background:rgba(245,165,36,.1);border:1px solid rgba(245,165,36,.45);border-radius:10px;padding:10px 11px;font-size:12px;line-height:1.6;color:#ffd479;text-align:center}\ #cnki-mode-area{background:var(--bg2);border:1px solid var(--line);border-radius:10px;padding:9px 10px}\ .cnki-cap{font:600 10px/1 var(--mono);letter-spacing:.14em;color:var(--dim);margin-bottom:8px}\ #cnki-mode-area label{display:flex;align-items:center;gap:8px;padding:6px 8px;border:1px solid transparent;border-radius:7px;cursor:pointer;font-size:12.5px;color:var(--dim);transition:background .15s,color .15s,border-color .15s}\ #cnki-mode-area label:hover{background:#1e222b;color:var(--ink)}\ #cnki-mode-area label:has(input:checked){border-color:rgba(245,165,36,.4);background:rgba(245,165,36,.08);color:var(--ink)}\ #cnki-mode-area input[type=radio]{margin:0;accent-color:var(--amber)}\ #cnki-settings{display:none;background:var(--bg2);border:1px solid var(--line);border-radius:10px;padding:11px 11px 3px}\ #cnki-settings .setting-group{margin-bottom:11px}\ #cnki-settings .setting-label{font:600 10px/1 var(--mono);letter-spacing:.12em;color:var(--amber);margin-bottom:6px}\ #cnki-settings .setting-desc{font-size:11px;line-height:1.55;color:var(--dim);margin-bottom:8px}\ #cnki-settings .input-row{display:flex;align-items:center;flex-wrap:wrap;gap:5px;font-size:12px;color:var(--dim)}\ #cnki-settings .input-row input[type=number]{width:48px;padding:5px 4px;background:#0d0f14;border:1px solid var(--line);border-radius:6px;color:var(--ink);text-align:center;font:600 12px/1 var(--mono)}\ #cnki-settings .input-row input[type=number]:focus{outline:none;border-color:var(--amber)}\ #cnki-settings>div>label{display:flex;align-items:center;gap:7px;cursor:pointer;font-size:12px;color:var(--dim)}\ #cnki-settings input[type=checkbox]{margin:0;accent-color:var(--amber)}\ .cnki-select-all-bar{display:none;align-items:center;background:rgba(90,169,247,.1);border:1px solid rgba(90,169,247,.3);border-radius:9px;padding:8px 10px;font-size:12px;line-height:1.5;color:#9ccbfb}\ #cnki-log{max-height:158px;overflow-y:auto;background:#0c0e13;border:1px solid var(--line);border-radius:10px;padding:7px 9px;font:11px/1.7 var(--mono);color:var(--dim)}\ #cnki-log>div{padding:2px 0 2px 7px;border-left:2px solid currentColor;border-bottom:1px solid #16181e;word-break:break-all}\ #cnki-log>div:last-child{border-bottom:none}\ .cnki-ts{color:#575e6c;margin-right:6px}\ @keyframes cnkiPulse{0%,100%{opacity:1}50%{opacity:.35}}\ @keyframes cnkiSheen{0%{background-position:-110% 0}100%{background-position:210% 0}}\ '; document.head.appendChild(style); // ── 面板 ── var panel = document.createElement('div'); panel.id = 'cnki-panel'; panel.innerHTML = '\
\ \ 知网资源批量下载\ 已记录 0\ v1.1.0\
\
\
\u5c31\u7eea
\
\
\
\ \
\
\
\u4e0b\u8f7d\u6a21\u5f0f
\ \ \
\
\ \u8bf7\u5728\u5de6\u4fa7\u5217\u8868\u4e2d\u52fe\u9009\u8981\u4e0b\u8f7d\u7684\u6587\u732e\
\ \
\
\
\u2615 \u4e2d\u573a\u4f11\u606f
\
\u9632\u6b62\u77ed\u65f6\u95f4\u5927\u91cf\u8bf7\u6c42\u89e6\u53d1\u53cd\u722c\uff0c\u4f11\u606f\u65f6\u6709\u9192\u76ee\u5012\u8ba1\u65f6
\
\ \u6bcf\ \ \u7bc7\u4f11\u606f\ \ ~\ \ \u79d2\
\
\
\
\ud83d\udcc4 下载格式
\
默认只下 PDF(从详情页解析 PDF 直链);勾选后在找不到 PDF 时退回 CAJ
\ \ \
\
\
\ud83d\udce6 单批上限
\
一批最多触发多少篇,达到后自动结束本批(自动翻页也按整批累计)
\
\ \ \u7bc7\
\
\
\ \ \ \ \
\ \ \ \
\ \
\
\ '; document.body.appendChild(panel); // ── 拖动(鼠标 + 触屏);位置存 localStorage,自动翻页重载后不会跳回右下角 ── var POS_KEY = 'cnki_panel_pos'; var isDragging = false, dragX = 0, dragY = 0; function clamp(v, min, max) { return Math.min(Math.max(v, min), max); } function isInteractiveEl(t) { return !!t && /^(BUTTON|INPUT|LABEL|SELECT|TEXTAREA|A)$/.test(t.tagName); } function applyPanelPos(left, top) { var maxL = Math.max(0, window.innerWidth - panel.offsetWidth); var maxT = Math.max(0, window.innerHeight - 44); panel.style.left = clamp(left, 0, maxL) + 'px'; panel.style.top = clamp(top, 0, maxT) + 'px'; panel.style.right = 'auto'; panel.style.bottom = 'auto'; } function startDrag(x, y) { isDragging = true; var rect = panel.getBoundingClientRect(); dragX = x - rect.left; dragY = y - rect.top; } function moveDrag(x, y) { if (!isDragging) return; applyPanelPos(x - dragX, y - dragY); } function endDrag() { if (!isDragging) return; isDragging = false; var rect = panel.getBoundingClientRect(); localStorage.setItem(POS_KEY, JSON.stringify({ left: Math.round(rect.left), top: Math.round(rect.top) })); } try { var savedPos = JSON.parse(localStorage.getItem(POS_KEY) || 'null'); if (savedPos && typeof savedPos.left === 'number' && typeof savedPos.top === 'number') { applyPanelPos(savedPos.left, savedPos.top); } } catch (e) {} panel.addEventListener('mousedown', function (e) { if (isInteractiveEl(e.target)) return; startDrag(e.clientX, e.clientY); }); document.addEventListener('mousemove', function (e) { moveDrag(e.clientX, e.clientY); }); document.addEventListener('mouseup', endDrag); panel.addEventListener('touchstart', function (e) { if (isInteractiveEl(e.target) || !e.touches.length) return; startDrag(e.touches[0].clientX, e.touches[0].clientY); }, { passive: true }); document.addEventListener('touchmove', function (e) { if (!isDragging || !e.touches.length) return; e.preventDefault(); moveDrag(e.touches[0].clientX, e.touches[0].clientY); }, { passive: false }); document.addEventListener('touchend', endDrag); document.addEventListener('touchcancel', endDrag); // ── DOM ── var logBox = document.getElementById('cnki-log'); var progressBox = document.getElementById('cnki-progress'); var countdownBox = document.getElementById('cnki-countdown'); var captchaBox = document.getElementById('cnki-captcha'); var btnStart = document.getElementById('cnki-btn-start'); var btnPause = document.getElementById('cnki-btn-pause'); var btnStop = document.getElementById('cnki-btn-stop'); var btnMin = document.getElementById('cnki-btn-min'); var btnSettings = document.getElementById('cnki-btn-settings'); var settingsPanel = document.getElementById('cnki-settings'); var selectBar = document.getElementById('cnki-select-bar'); var btnSkipRest = document.getElementById('cnki-btn-skip-rest'); var cajFallbackEl = document.getElementById('cnki-caj-fallback'); var autoCloseEl = document.getElementById('cnki-auto-close'); var maxSessionEl = document.getElementById('cnki-max-session'); var running = false, paused = false, skipRest = false; var downloadMode = 'sequential'; btnSkipRest.addEventListener('click', function() { skipRest = true; }); btnSettings.addEventListener('click', function() { var show = settingsPanel.style.display === 'none' || !settingsPanel.style.display; settingsPanel.style.display = show ? 'block' : 'none'; btnSettings.textContent = show ? '\u2699 \u6536\u8d77\u8bbe\u7f6e' : '\u2699 \u8bbe\u7f6e'; }); ['cnki-rest-every','cnki-rest-min','cnki-rest-max','cnki-max-session'].forEach(function(id) { var el = document.getElementById(id); if (el) el.addEventListener('change', function() { var map = { 'cnki-rest-every': 'restEvery', 'cnki-rest-min': 'restMin', 'cnki-rest-max': 'restMax', 'cnki-max-session': 'maxPerSession' }; CONFIG[map[id]] = parseInt(el.value) || 1; if (CONFIG.restMax < CONFIG.restMin) CONFIG.restMax = CONFIG.restMin; saveSettings(); }); }); if (cajFallbackEl) cajFallbackEl.addEventListener('change', function() { CONFIG.cajFallback = cajFallbackEl.checked; saveSettings(); }); if (autoCloseEl) autoCloseEl.addEventListener('change', function() { CONFIG.autoCloseTab = autoCloseEl.checked; saveSettings(); }); document.querySelectorAll('input[name="cnki-mode"]').forEach(function(r) { r.addEventListener('change', function() { downloadMode = this.value; selectBar.style.display = downloadMode === 'selected' ? 'flex' : 'none'; }); }); // 日志配色:调用处沿用的旧亮色统一映射到暗色面板上可读的色值 var LOG_COLORS = { '#1a73e8': '#5aa9f7', '#0d904f': '#35c07a', '#e67e22': '#f5a524', '#ea4335': '#f2545b', '#8e24aa': '#b48ef0', '#5c6bc0': '#8ab4f8' }; function log(text, color) { var item = document.createElement('div'); item.style.color = LOG_COLORS[color] || color || 'var(--blue)'; var now = new Date(); var ts = [now.getHours(), now.getMinutes(), now.getSeconds()] .map(function(n){ return n.toString().padStart(2,'0'); }).join(':'); var stamp = document.createElement('span'); stamp.className = 'cnki-ts'; stamp.textContent = ts; item.appendChild(stamp); item.appendChild(document.createTextNode(text)); logBox.insertBefore(item, logBox.firstChild); // 长批次下日志节点会一直堆积,只保留最近 200 条 while (logBox.childNodes.length > 200) logBox.removeChild(logBox.lastChild); } // 顶部读数 + 进度条(--p 驱动 #cnki-rail i 的宽度) function setProgress(text, pct) { progressBox.textContent = text; if (typeof pct === 'number') { panel.style.setProperty('--p', Math.max(0, Math.min(100, pct)) + '%'); } } // 面板状态:'' / running / resting / paused,驱动头部指示灯 function setState(state) { panel.classList.remove('is-running', 'is-resting', 'is-paused'); if (state) panel.classList.add('is-' + state); } function sleep(ms) { return new Promise(function(r){ setTimeout(r, ms); }); } async function waitIfPaused() { while (paused && running) { countdownBox.textContent = '\u5df2\u6682\u505c\uff0c\u70b9\u51fb\u201c\u7ee7\u7eed\u201d\u6062\u590d'; await sleep(500); } } async function restDelay() { skipRest = false; var totalSec = Math.floor(Math.random() * (CONFIG.restMax - CONFIG.restMin)) + CONFIG.restMin; log('\u4e2d\u573a\u4f11\u606f ' + totalSec + ' \u79d2\uff08\u53ef\u8df3\u8fc7\uff09', '#e67e22'); countdownBox.style.cssText = 'margin-top:6px;font:600 12px/1.5 var(--mono);color:#ffd479;background:rgba(245,165,36,.1);border:1px solid rgba(245,165,36,.4);border-radius:8px;padding:7px 9px;text-align:center'; setProgress('\u4e2d\u573a\u4f11\u606f\u4e2d\u2026'); setState('resting'); btnSkipRest.style.display = 'block'; for (var s = totalSec; s > 0; s--) { if (!running || skipRest) break; await waitIfPaused(); var m = Math.floor(s / 60), sec = s % 60; countdownBox.textContent = '\u4f11\u606f\u4e2d ' + m + ':' + sec.toString().padStart(2,'0') + ' \uff08\u70b9\u4e0b\u65b9\u8df3\u8fc7\uff09'; await sleep(1000); } btnSkipRest.style.display = 'none'; if (skipRest) log('\u5df2\u8df3\u8fc7\u4f11\u606f', '#1a73e8'); countdownBox.textContent = ''; countdownBox.style.cssText = ''; if (running) setState(paused ? 'paused' : 'running'); } // 每次重新获取表格(翻页后 DOM 会变) function getTableBody() { return document.querySelector('#gridTable table.result-table-list tbody'); } // 从操作列中找下载入口:真正的 PDF 链接优先,其次才是通用下载链接 // 返回 { href, kind },kind 为 'pdf' | 'caj';找不到返回 null // 注意:新版知网列表页只给一个「下载」入口(默认格式 CAJ),PDF 入口只在详情页 function findDownloadEntry(operatCell) { var allLinks = operatCell.querySelectorAll('a'); var cajHref = ''; for (var i = 0; i < allLinks.length; i++) { var a = allLinks[i]; var href = a.href || a.getAttribute('href') || ''; var text = (a.textContent || '').trim(); var cls = a.className || ''; var onclick = a.getAttribute('onclick') || ''; // 跳过空链接和 javascript: 伪链接(含 javascript:; 这类写法) if (!href || href.indexOf('javascript:') === 0) continue; // 明确的 PDF 链接 if (cls.indexOf('pdf') >= 0 || text.indexOf('PDF') >= 0 || href.indexOf('pdfdown') >= 0 || /\/PDFDownload\//i.test(href) || onclick.indexOf('pdf') >= 0 || onclick.indexOf('PDF') >= 0) { return { href: href, kind: 'pdf' }; } // 记录通用下载链接作为 CAJ 兜底候选 if (!cajHref && (cls.indexOf('downloadlink') >= 0 || cls.indexOf('download') >= 0)) { cajHref = href; } } return cajHref ? { href: cajHref, kind: 'caj' } : null; } // 详情页被验证/安全拦截时的特征串。只看页面头部,避免命中正文里讨论「验证码」的论文 var BLOCK_MARKERS = /验证码|安全验证|滑动验证|请完成验证|完成验证后|人机验证|verifyCode|captcha/i; var _activeAbort = null; function sameOrigin(url) { try { return new URL(url, location.href).origin === location.origin; } catch (e) { return false; } } // 单次请求详情页:15 秒超时,且可被「终止」按钮打断 async function fetchPdfOnce(detailHref) { var ctrl = (typeof AbortController !== 'undefined') ? new AbortController() : null; var timer = null; _activeAbort = ctrl; if (ctrl) timer = setTimeout(function () { try { ctrl.abort(); } catch (e) {} }, 15000); try { var res = await fetch(detailHref, { credentials: 'include', signal: ctrl ? ctrl.signal : undefined }); if (!res.ok) return { href: '', blocked: false }; var html = await res.text(); var doc = new DOMParser().parseFromString(html, 'text/html'); var links = doc.querySelectorAll('a'); for (var i = 0; i < links.length; i++) { var a = links[i]; var href = a.getAttribute('href') || ''; if (!href || href.indexOf('javascript:') === 0) continue; if (href.indexOf('//') === 0) href = location.protocol + href; else if (href.charAt(0) === '/') href = location.origin + href; if (!/^https?:/i.test(href)) continue; if (href.indexOf('/download/order') < 0 && href.indexOf('pdfdown') < 0) continue; if (/pdf/i.test((a.textContent || '') + (a.className || ''))) { return { href: href, blocked: false }; } } // 没拿到下载链接:若页面头部带验证特征,判定为被拦下 return { href: '', blocked: BLOCK_MARKERS.test(html.slice(0, 4000)) }; } catch (e) { return { href: '', blocked: false }; } finally { if (timer) clearTimeout(timer); _activeAbort = null; } } // 从文献详情页解析 PDF 下载直链,失败重试一次(网络抖动 / 知网偶发 5xx) // 直链形如 https://bar.cnki.net/bar/download/order?id=<密文>,格式编码在密文里, // 所以只能从详情页重新取,改列表页那个链接的 URL 参数是无效的 async function fetchPdfHref(detailHref) { for (var attempt = 0; attempt < 2; attempt++) { var r = await fetchPdfOnce(detailHref); if (r.href || r.blocked || !running) return r; if (attempt === 0) await sleep(700 + Math.random() * 600); } return { href: '', blocked: false }; } // ── 从表格行中收集下载信息 ── function collectDownloadItems(mode) { var items = []; var tb = getTableBody(); if (!tb) return items; var rows = tb.querySelectorAll('tr'); rows.forEach(function(row) { if (mode === 'selected') { var cb = row.querySelector('input[type="checkbox"]'); if (!cb || !cb.checked) return; } var titleLink = row.querySelector('td.name a.fz14') || row.querySelector('a.fz14'); var title = titleLink ? titleLink.textContent.trim() : ''; var operatCell = row.querySelector('td.operat'); if (!operatCell) return; var dl = findDownloadEntry(operatCell); if (title && dl) { items.push({ title: title, dl: dl, detailHref: titleLink ? titleLink.href : '', row: row, titleLink: titleLink }); } }); return items; } function markDownloaded() { var tb = getTableBody(); if (!tb) return; var rows = tb.querySelectorAll('tr'); rows.forEach(function(row) { var titleLink = row.querySelector('td.name a.fz14') || row.querySelector('a.fz14'); if (titleLink && isDownloaded(titleLink.textContent.trim())) { titleLink.style.backgroundColor = '#d4edda'; } }); } // ── 最小化 ── btnMin.addEventListener('click', function() { panel.style.display = 'none'; var mb = document.createElement('button'); mb.style.cssText = 'position:fixed;top:80px;right:22px;z-index:99999;display:flex;align-items:center;justify-content:center;width:46px;height:46px;background:linear-gradient(180deg,#f7b53c,#e8960f);color:#241a00;border:none;border-radius:14px;font-size:18px;line-height:1;cursor:pointer;box-shadow:0 10px 26px rgba(0,0,0,.45)'; mb.title = '\u5c55\u5f00\u9762\u677f'; mb.textContent = '\u25bc'; mb.addEventListener('click', function(){ panel.style.display='block'; mb.remove(); }); document.body.appendChild(mb); }); // ── 记录的导出 / 导入 ── var btnImport = document.getElementById('cnki-btn-import'); var btnExport = document.getElementById('cnki-btn-export'); var countEl = document.getElementById('cnki-count'); function updateRecordCount() { if (countEl) countEl.textContent = '\u5df2\u8bb0\u5f55 ' + getDownloadedCount(); } function addRecord(raw) { var key = normTitle(raw); if (!key) return 'bad'; if (getDownloaded().indexOf(key) !== -1) return 'dup'; saveDownloaded(key); return 'new'; } function timeStamp() { var d = new Date(); var p = function (n) { return String(n).padStart(2, '0'); }; return String(d.getFullYear()) + p(d.getMonth() + 1) + p(d.getDate()) + '-' + p(d.getHours()) + p(d.getMinutes()); } function downloadText(text, filename, mime) { var blob = new Blob([text], { type: (mime || 'text/plain') + ';charset=utf-8' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); setTimeout(function () { URL.revokeObjectURL(url); }, 5000); } // ── 批次日志(CSV) ── // 一批 = 一次「开始批量下载」到结束;自动翻页续跑的后续页面算同一批 var BATCH_LOG_KEY = 'cnki_batch_log'; var BATCH_STAT_KEY = 'cnki_batch_stat'; var BATCH_CONTINUE_KEY = 'cnki_batch_continue'; var _batchLog = null; var _batchStat = null; function getBatchLog() { if (!_batchLog) { try { _batchLog = JSON.parse(localStorage.getItem(BATCH_LOG_KEY) || '[]'); } catch (e) { _batchLog = []; } if (!Array.isArray(_batchLog)) _batchLog = []; } return _batchLog; } function batchAppend(title, status) { var list = getBatchLog(); list.push({ t: String(title == null ? '' : title), s: status }); localStorage.setItem(BATCH_LOG_KEY, JSON.stringify(list)); } function batchBegin() { _batchLog = []; _batchStat = null; localStorage.removeItem(BATCH_LOG_KEY); localStorage.removeItem(BATCH_STAT_KEY); } // 批次计数器(跨页持久化):单批上限和中场休息节奏必须按整批累计, // 放在页面级变量里的话,每次自动翻页都会归零 function getBatchStat() { if (!_batchStat) { var s = null; try { s = JSON.parse(localStorage.getItem(BATCH_STAT_KEY) || 'null'); } catch (e) { s = null; } _batchStat = (s && typeof s === 'object') ? s : {}; ['ok', 'failed', 'skipped', 'processed'].forEach(function (k) { if (typeof _batchStat[k] !== 'number') _batchStat[k] = 0; }); } return _batchStat; } function bumpBatchStat(field, n) { var st = getBatchStat(); st[field] = (st[field] || 0) + (n == null ? 1 : n); localStorage.setItem(BATCH_STAT_KEY, JSON.stringify(st)); return st[field]; } function csvCell(v) { var s = String(v == null ? '' : v); return /[",\r\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s; } // 批次结束:写出 CSV(带 BOM,Excel 才认 UTF-8 中文)并清掉累计 function batchEnd() { var list = getBatchLog(); _batchLog = null; _batchStat = null; localStorage.removeItem(BATCH_LOG_KEY); localStorage.removeItem(BATCH_STAT_KEY); if (!list.length) return; var lines = ['\u5e8f\u53f7,\u9898\u76ee,\u72b6\u6001']; list.forEach(function (r, i) { lines.push((i + 1) + ',' + csvCell(r.t) + ',' + csvCell(r.s)); }); var name = 'cnki-batch-' + timeStamp() + '.csv'; downloadText('\ufeff' + lines.join('\r\n') + '\r\n', name, 'text/csv'); log('\u672c\u6279\u8bb0\u5f55\u5df2\u5bfc\u51fa\uff1a' + name + '\uff08' + list.length + ' \u884c\uff09', '#8e24aa'); } // 导出为 JSON:{type,version,exportedAt,count,titles:[...]} function exportRecords() { var list = getDownloaded(); if (!list.length) { log('\u8fd8\u6ca1\u6709\u8bb0\u5f55\u53ef\u5bfc\u51fa', '#e67e22'); return; } var payload = { type: 'cnki-auto-download-records', version: 1, exportedAt: new Date().toISOString(), count: list.length, titles: list }; downloadText(JSON.stringify(payload, null, 2), 'cnki-downloaded-' + timeStamp() + '.json', 'application/json'); log('\u5df2\u5bfc\u51fa ' + list.length + ' \u6761\u8bb0\u5f55', '#8e24aa'); } // 兼容本脚本导出的 JSON(也接受纯标题数组)与一行一个标题的 txt function parseRecordFile(text, isJson) { if (isJson) { try { var data = JSON.parse(text); if (Array.isArray(data)) return data; if (data && Array.isArray(data.titles)) return data.titles; } catch (e) {} return []; } return text.split(/\r?\n/); } // 一次导入多个来源:导出的 json、一行一标题的 txt、以及已下载的 pdf/caj function importFiles(files) { var stat = { scanned: files.length, added: 0, dup: 0, bad: 0 }; var readers = []; Array.prototype.slice.call(files).forEach(function (f) { var name = f.name || ''; if (/\.(json|txt)$/i.test(name)) { var isJson = /\.json$/i.test(name); readers.push(new Promise(function (resolve) { var fr = new FileReader(); fr.onload = function () { resolve(parseRecordFile(String(fr.result || ''), isJson)); }; fr.onerror = function () { resolve([]); }; fr.readAsText(f); })); return; } if (!/\.(pdf|caj|nh|kdh)$/i.test(name)) { stat.bad++; return; } var r = addRecord(name); if (r === 'new') stat.added++; else if (r === 'dup') stat.dup++; else stat.bad++; }); Promise.all(readers).then(function (lists) { lists.forEach(function (titles) { titles.forEach(function (t) { var r = addRecord(t); if (r === 'new') stat.added++; else if (r === 'dup') stat.dup++; else stat.bad++; }); }); log('\u5bfc\u5165\u5b8c\u6210\uff1a\u626b\u63cf ' + stat.scanned + ' \u4e2a\u6587\u4ef6\uff0c\u65b0\u589e ' + stat.added + ' \u6761\uff0c\u91cd\u590d ' + stat.dup + ' \u6761' + (stat.bad ? '\uff0c\u5ffd\u7565 ' + stat.bad + ' \u9879' : ''), '#8e24aa'); updateRecordCount(); markDownloaded(); }); } btnExport.addEventListener('click', exportRecords); btnImport.addEventListener('click', function () { var picker = document.createElement('input'); picker.type = 'file'; picker.multiple = true; picker.accept = '.json,.txt,.pdf,.caj,.nh,.kdh'; picker.style.display = 'none'; document.body.appendChild(picker); picker.addEventListener('change', function () { var files = picker.files; picker.remove(); if (!files || !files.length) return; importFiles(files); }); picker.click(); }); document.getElementById('cnki-btn-clear').addEventListener('click', function() { if (confirm('\u786e\u5b9a\u6e05\u7a7a\u6240\u6709\u4e0b\u8f7d\u8bb0\u5f55\uff1f\uff08\u5efa\u8bae\u5148\u5bfc\u51fa\uff09')) { clearDownloaded(); updateRecordCount(); log('\u5df2\u6e05\u7a7a', '#ea4335'); var tb = getTableBody(); if (!tb) return; var rows = tb.querySelectorAll('tr'); rows.forEach(function(row) { var t = row.querySelector('td.name a.fz14') || row.querySelector('a.fz14'); if (t) t.style.backgroundColor = ''; }); } }); btnPause.addEventListener('click', function() { paused = !paused; btnPause.textContent = paused ? '\u7ee7\u7eed' : '\u6682\u505c'; setState(paused ? 'paused' : 'running'); log(paused ? '\u5df2\u6682\u505c' : '\u5df2\u6062\u590d', paused ? '#e67e22' : '#0d904f'); }); btnStop.addEventListener('click', function() { running = false; paused = false; // 立刻掐掉正在进行的详情页请求,不用等它自己超时 if (_activeAbort) { try { _activeAbort.abort(); } catch (e) {} } btnStop.style.display = 'none'; btnPause.style.display = 'none'; btnStart.style.display = 'block'; captchaBox.style.display = 'none'; btnPause.textContent = '\u6682\u505c'; countdownBox.textContent = ''; countdownBox.style.cssText = ''; setState(''); log('\u5df2\u7ec8\u6b62', '#ea4335'); setProgress('\u5df2\u7ec8\u6b62', 0); }); // ── 开始下载 ── btnStart.addEventListener('click', async function() { var items = collectDownloadItems(downloadMode); if (items.length === 0) { if (downloadMode === 'selected') { log('\u8bf7\u5148\u52fe\u9009\u8981\u4e0b\u8f7d\u7684\u6587\u732e\uff01', '#ea4335'); } else { log('\u672a\u627e\u5230\u6709\u6548\u7684\u4e0b\u8f7d\u94fe\u63a5\uff01', '#ea4335'); } // 自动翻页续跑到一个没有可下载项的新页面,视为这批结束 if (localStorage.getItem(BATCH_CONTINUE_KEY) === 'true') { localStorage.removeItem(BATCH_CONTINUE_KEY); batchEnd(); } return; } CONFIG.restEvery = parseInt(document.getElementById('cnki-rest-every').value) || 50; CONFIG.restMin = parseInt(document.getElementById('cnki-rest-min').value) || 15; CONFIG.restMax = parseInt(document.getElementById('cnki-rest-max').value) || 30; if (CONFIG.restMax < CONFIG.restMin) CONFIG.restMax = CONFIG.restMin; CONFIG.cajFallback = !!(cajFallbackEl && cajFallbackEl.checked); CONFIG.autoCloseTab = !!(autoCloseEl && autoCloseEl.checked); CONFIG.maxPerSession = parseInt(maxSessionEl && maxSessionEl.value) || CONFIG.maxPerSession || 200; saveSettings(); settingsPanel.style.display = 'none'; btnSettings.textContent = '\u2699 \u8bbe\u7f6e'; running = true; paused = false; skipRest = false; btnStart.style.display = 'none'; btnPause.style.display = 'block'; btnStop.style.display = 'block'; btnPause.textContent = '\u6682\u505c'; logBox.innerHTML = ''; setState('running'); setProgress('\u51c6\u5907\u4e2d\u2026', 0); // 自动翻页续跑的页面沿用同一批记录,手动点开始则新开一批 if (localStorage.getItem(BATCH_CONTINUE_KEY) === 'true') { localStorage.removeItem(BATCH_CONTINUE_KEY); } else { batchBegin(); } var modeLabel = downloadMode === 'selected' ? '\u52fe\u9009' : '\u987a\u5e8f'; log(modeLabel + '\u6a21\u5f0f\uff0c\u5171 ' + items.length + ' \u7bc7\uff0c\u7d2f\u8ba1\u5df2\u4e0b\u8f7d ' + getDownloadedCount(), '#1a73e8'); log('\u8bf7\u786e\u4fdd\u6d4f\u89c8\u5668\u5141\u8bb8\u5f39\u51fa\u7a97\u53e3\uff01', '#e67e22'); // 计数放进批次统计(跨页持久化);页面级只留「本页成功数」用于判断要不要翻页 var stat = getBatchStat(); var pageOk = 0; var popupBlocked = false; var captchaStreak = 0; var crossOriginWarned = false; for (var i = 0; i < items.length; i++) { if (!running) break; await waitIfPaused(); if (!running) break; if (stat.ok >= CONFIG.maxPerSession) { log('\u672c\u6279\u5df2\u8fbe\u4e0a\u9650 ' + stat.ok + ' \u7bc7\uff0c\u7ed3\u675f\u672c\u6279', '#e67e22'); running = false; break; } if (stat.processed > 0 && stat.processed % CONFIG.restEvery === 0) { await restDelay(); if (!running) break; } var item = items[i]; if (isDownloaded(item.title)) { if (item.titleLink) item.titleLink.style.backgroundColor = '#d4edda'; batchAppend(item.title, '跳过'); bumpBatchStat('skipped'); continue; } setProgress('[' + (i+1) + '/' + items.length + '] ' + item.title.substring(0, 22) + '...', (i / items.length) * 100); log('[' + (i+1) + '] ' + item.title, '#1a73e8'); if (item.titleLink) { item.titleLink.style.backgroundColor = '#fff3cd'; item.titleLink.scrollIntoView({behavior:'smooth', block:'center'}); } // 解析真正的 PDF 直链:列表页直接给 PDF 就用,否则去详情页取 var dlHref = '', fmt = ''; if (item.dl && item.dl.kind === 'pdf') { dlHref = item.dl.href; fmt = 'PDF'; } // 页面不在知网域下(校内代理、镜像站)时浏览器不允许读详情页,只能降级走列表入口 var crossOrigin = !!item.detailHref && !sameOrigin(item.detailHref); if (crossOrigin && !crossOriginWarned) { crossOriginWarned = true; log('当前页面与知网不同域,无法解析详情页,本批改用列表页入口(多为 CAJ)', '#e67e22'); } if (!dlHref && item.detailHref && !crossOrigin) { setProgress('[' + (i+1) + '/' + items.length + '] 解析 PDF 直链...', (i / items.length) * 100); var r = await fetchPdfHref(item.detailHref); if (!running) break; if (r.href) { dlHref = r.href; fmt = 'PDF'; captchaStreak = 0; } else { captchaStreak = r.blocked ? captchaStreak + 1 : 0; if (captchaStreak >= 2) { log('连续 2 篇被验证页拦下,已暂停本批:请在页面完成验证后重新开始', '#ea4335'); captchaBox.style.display = 'block'; batchAppend(item.title, '失败'); bumpBatchStat('failed'); bumpBatchStat('processed'); running = false; break; } } } if (!dlHref && (CONFIG.cajFallback || crossOrigin) && item.dl && item.dl.kind === 'caj') { dlHref = item.dl.href; fmt = 'CAJ'; } if (!dlHref) { log(' 未取到 PDF 直链,已跳过(可在设置里开启 CAJ 兜底)', '#ea4335'); if (item.titleLink) item.titleLink.style.backgroundColor = ''; batchAppend(item.title, '失败'); bumpBatchStat('failed'); bumpBatchStat('processed'); continue; } // 打开下载链接(不操作子窗口 DOM) var newWin = window.open(dlHref, '_blank'); if (!newWin) { log('\u5f39\u7a97\u88ab\u62e6\u622a\uff01\u8bf7\u5141\u8bb8\u5f39\u7a97\u540e\u91cd\u8bd5', '#ea4335'); captchaBox.style.display = 'block'; batchAppend(item.title, '失败'); bumpBatchStat('failed'); bumpBatchStat('processed'); popupBlocked = true; running = false; break; } // 可选:下载已交给浏览器,顺手把新标签页收掉(用闭包捕获,避免被后续循环改写) if (CONFIG.autoCloseTab) { (function (w) { setTimeout(function () { try { w.close(); } catch (e) {} }, 1500); })(newWin); } // 记录为已下载 if (item.titleLink) item.titleLink.style.backgroundColor = '#d4edda'; saveDownloaded(item.title); updateRecordCount(); batchAppend(item.title, '已触发'); bumpBatchStat('ok'); bumpBatchStat('processed'); pageOk++; log(' \u2713 \u5df2\u89e6\u53d1\u4e0b\u8f7d [' + fmt + '] [\u7d2f\u8ba1' + getDownloadedCount() + ']', '#0d904f'); // 篇间等待(给浏览器处理下载的时间,也防反爬) if (i < items.length - 1 && running) { var waitSec = 3 + Math.floor(Math.random() * 3); for (var s = waitSec; s > 0; s--) { if (!running) break; await waitIfPaused(); countdownBox.textContent = s + '\u79d2\u540e\u4e0b\u4e00\u7bc7...'; await sleep(1000); } countdownBox.textContent = ''; } } // ── 翻页(顺序模式自动翻,勾选模式如果本页有下载也翻到下一页继续) ── if (running) { var shouldTurnPage = downloadMode === 'sequential' || pageOk > 0; if (shouldTurnPage) { var nextBtn = document.querySelector('a#PageNext') || document.querySelector('a.next'); if (!nextBtn) { document.querySelectorAll('a').forEach(function(a) { if (!nextBtn && (a.textContent.trim() === '\u4e0b\u4e00\u9875' || a.textContent.trim() === '>')) nextBtn = a; }); } if (nextBtn) { log('\u672c\u9875\u5b8c\u6210\uff01' + pageOk + '\u7bc7 \u7d2f\u8ba1' + getDownloadedCount(), '#0d904f'); log('\u7ffb\u9875\u7ee7\u7eed...', '#1a73e8'); await sleep(1000); if (running) { localStorage.setItem('cnki_auto_continue', 'true'); localStorage.setItem('cnki_dl_mode', downloadMode); localStorage.setItem(BATCH_CONTINUE_KEY, 'true'); nextBtn.click(); return; } } else { log('\u6ca1\u6709\u4e0b\u4e00\u9875\u4e86\uff0c\u5168\u90e8\u5b8c\u6210\uff01', '#0d904f'); } } } running = false; paused = false; btnStop.style.display = 'none'; btnPause.style.display = 'none'; btnStart.style.display = 'block'; btnPause.textContent = '\u6682\u505c'; countdownBox.textContent = ''; countdownBox.style.cssText = ''; if (!popupBlocked) captchaBox.style.display = 'none'; setState(''); // 这批到此为止(继续翻页的分支已经 return):先取批次计数,再输出本批 CSV var batchTotal = { ok: stat.ok, failed: stat.failed, skipped: stat.skipped }; batchEnd(); var doneQuotes = [ '\u5168\u90e8\u641e\u5b9a\u5566\uff5e\u7d2f\u6b7b\u6211\u4e86\uff0c\u8001\u677f\u52a0\u9e21\u817f\uff01\ud83c\udf57', '\u6d3b\u513f\u5e72\u5b8c\u4e86\uff01\u6211\u5148\u8e7a\u4e00\u4f1a\u513f\u2026\ud83d\ude34', '\u53ee\uff5e\u60a8\u7684\u6587\u732e\u5957\u9910\u5df2\u9001\u8fbe\uff0c\u597d\u8bc4\u8bb0\u5f97\u7ed9\u4e94\u661f\u54e6\uff01\u2b50', '\u5458\u5de5\u5df2\u7b4b\u75b2\u529b\u5c3d\uff0c\u8bf7\u6295\u5582\u5496\u5561\u2615\u540e\u518d\u8bd5', '\u77e5\u7f51\u8d44\u6e90\u6279\u91cf\u4e0b\u8f7d\u5927\u529f\u544a\u6210\uff01\u53c8\u662f\u5145\u5b9e\u7684\u4e00\u5929\uff01\ud83c\udf1f', '\u4e0b\u8f7d\u5b8c\u6bd5\uff01\u6211\u5148\u53bb\u505a\u4e2aSPA\u653e\u677e\u4e00\u4e0b\u2026\ud83d\udec0', '\u53ee\u549a\uff01\u6587\u732e\u5df2\u5168\u90e8\u5230\u8d26\uff0c\u8bf7\u67e5\u6536\uff01\ud83d\udce6', '\u6211\u5df2\u7ecf\u5c3d\u529b\u4e86\uff0c\u5269\u4e0b\u7684\u4ea4\u7ed9\u4f60\u7684\u8111\u5b50\u5566\uff01\ud83e\udde0' ]; var quote = doneQuotes[Math.floor(Math.random() * doneQuotes.length)]; setProgress(quote, 100); log('\u2705 \u672c\u6279\u7ed3\u675f\uff1a\u5df2\u89e6\u53d1 ' + batchTotal.ok + ' \u7bc7\uff0c\u8df3\u8fc7 ' + batchTotal.skipped + '\uff0c\u5931\u8d25 ' + batchTotal.failed + '\uff0c\u7d2f\u8ba1\u8bb0\u5f55 ' + getDownloadedCount() + ' \u6761', '#0d904f'); log(quote, '#8e24aa'); }); // 尝试自动将每页显示数量调到50 try { var pageSizeEl = document.querySelector('#perPageDiv .sort-default span, .countPageMark'); var curSize = pageSizeEl ? parseInt(pageSizeEl.textContent) : 0; if (curSize && curSize < 50) { var perPageDiv = document.querySelector('#perPageDiv, .countPageDiv'); if (perPageDiv) { // 先触发展开下拉 var trigger = perPageDiv.querySelector('.sort-default') || perPageDiv; if (trigger) trigger.click(); setTimeout(function() { var opts = perPageDiv.querySelectorAll('li a, ul a'); opts.forEach(function(opt) { if (opt.textContent.trim() === '50') { log('\u81ea\u52a8\u5207\u6362\u6bcf\u987550\u7bc7', '#5c6bc0'); opt.click(); } }); }, 300); } } } catch(e) {} markDownloaded(); updateRecordCount(); var initCount = 0; var tb = getTableBody(); if (tb) { tb.querySelectorAll('tr').forEach(function(row) { var t = row.querySelector('td.name a.fz14') || row.querySelector('a.fz14'); if (t && isDownloaded(t.textContent.trim())) initCount++; }); } log('v1.1.0 \u5df2\u52a0\u8f7d\uff0c\u7d2f\u8ba1 ' + getDownloadedCount() + ' \u7bc7', '#0d904f'); if (initCount > 0) log('\u672c\u9875 ' + initCount + ' \u7bc7\u5df2\u4e0b\u8f7d\uff08\u7eff\u8272\uff09', '#0d904f'); if (localStorage.getItem('cnki_auto_continue') === 'true') { localStorage.removeItem('cnki_auto_continue'); var savedMode = localStorage.getItem('cnki_dl_mode'); if (savedMode) { downloadMode = savedMode; localStorage.removeItem('cnki_dl_mode'); var radio = document.querySelector('input[name="cnki-mode"][value="' + savedMode + '"]'); if (radio) radio.checked = true; if (savedMode === 'selected') selectBar.style.display = 'flex'; } log('\u7ffb\u9875\u540e\u81ea\u52a8\u7ee7\u7eed...', '#1a73e8'); setTimeout(function(){ btnStart.click(); }, 2000); } } })();