- // ==UserScript==
- // @name 中国知网CNKI硕博论文PDF下载
- // @version 3.2.1
- // @namespace https://gf.qytechs.cn/users/244539
- // @icon https://www.cnki.net/favicon.ico
- // @description 知网文献、硕博论文PDF批量下载,下载硕博论文章节目录
- // @author @爱与自由、@TheOneAdonis
- // @match *://*.cnki.net/*
- // @match 需要匹配的域名
- // @run-at document-idle
- // @grant unsafeWindow
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_xmlhttpRequest
- // @grant GM_download
- // ==/UserScript==
-
- // 如果没有显示批量下载的按钮,大概率是因为你的知网地址没有匹配到。
- // 解决办法:将你的域名按照上面的 @match 的格式,添加到后面,保存脚本,刷新网页,应该就可以了。(最后要加个*)
- // 例:// @match https://webvpn.zjnu.edu.cn/*
-
- (function() {
- 'use strict';
-
-
- let useWebVPN = GM_getValue('useWebVPN', false); // 默认不使用WebVPN
-
- const $ = unsafeWindow.jQuery;
-
- function loadCss(code) {
- const style = document.createElement('style');
- style.textContent = code;
- document.head.appendChild(style);
- }
-
- function createLoading(text, duration) {
- const loadingContent = document.createElement('div');
- loadingContent.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%, -50%);text-align:center;color:#333;font-size:16px;width:400px;height:40px;line-height:40px;z-index:9999;border:1px solid #0f5de5;padding:5px;background-color:#fff';
- loadingContent.textContent = text;
- document.body.appendChild(loadingContent);
- setTimeout(() => document.body.removeChild(loadingContent), duration);
- return loadingContent;
- }
-
- function createPopupButton() {
- const popupButton = document.createElement('button');
- popupButton.textContent = '批量下载PDF';
- popupButton.id = 'super-pdf'
- popupButton.style.cssText = 'position:fixed;right:50px;top:50%;transform:translateY(-50%);z-index:9999;padding:10px;background-color:#0f5de5;color:#fff;border:none;cursor:pointer;border-radius:5px;';
- popupButton.addEventListener('click', () => {
- if (!localStorage.getItem('cnkiFirstTimePopupShown')) {
- showFirstTimePopup();
- } else {
- createPopup();
- loadSavedData();
- }
- });
- document.body.appendChild(popupButton);
- }
- function showFirstTimePopup() {
- const popup = document.createElement('div');
- popup.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,0.5);z-index:10000;display:flex;justify-content:center;align-items:center;';
-
- const content = document.createElement('div');
- content.style.cssText = 'background-color:#fff;padding:20px;border-radius:10px;max-width:80%;max-height:80%;overflow-y:auto;';
- content.innerHTML = `
- <h2>使用说明 v3.2 </h2>
- <p> </p>
- <ul class="first_show_li">
- <li><b>首先请确保脚本为最新版本,如果不是,请<a target="_blank" style="color:#df1919" href="https://gf.qytechs.cn/zh-CN/scripts/389343">点击这里</a>进行更新。</b></li>
- <li>1.脚本只能获取当前页的文献,知网默认每页20篇,如果想更多,可将每页数量设置为50。</li>
- <li>2.如需清除已获取的数据 / 获取新数据,请先点击"<b>清除数据</b>"按钮。</li>
- <li>3.<b>如果只能下载一个,可能是浏览器拦截,允许弹出多窗口即可。</b></li>
- <li>4.脚本只支持<b>新版知网</b>,不能在<b>隐私模式、无痕窗口</b>运行。</li>
- <li>5.增加了批量下载延迟(2-5秒),增加了关键词筛选功能(文献关键词keywords)</li>
- <li>6.如果有问题,或者脚本报错,请在脚本网站留言。</li>
- <li><b>注意:请不要频繁获取数据,容易提示频繁操作。严重有封ip风险。</b></li>
- </ul>
- <button id="closeFirstTimePopup" style="padding:10px;background-color:#0f5de5;color:#fff;border:none;cursor:pointer;border-radius:5px;float:right">我知道了</button>`;
-
- popup.appendChild(content);
- document.body.appendChild(popup);
-
- document.getElementById('closeFirstTimePopup').addEventListener('click', () => {
- document.body.removeChild(popup);
- localStorage.setItem('cnkiFirstTimePopupShown', 'true');
- createPopup();
- loadSavedData();
- });
- }
- function createPopup() {
- if (document.getElementById('popup')) return;
-
- const popup = document.createElement('div');
- popup.id = 'popup';
- popup.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,0.5);z-index:9999;';
- const content = document.createElement('div');
- content.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:80%;max-height:80%;overflow-y:auto;background-color:#fff;padding:20px;border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,0.5);min-height:200px';
-
- const closeButton = createButton('关闭', () => document.body.removeChild(popup),'red');
- closeButton.style.float = 'right';
- const linkButton = createButton('获取链接', getLinks);
- const selectAllButton = createButton('全选', selectAll);
- const deselectAllButton = createButton('取消全选', deselectAll);
- const downloadButton = createButton('下载', downloadSelected,'#008000');
- downloadButton.title = "点击此处下载选中的文件";
- downloadButton.id = "dl_sel";
- const clearDataButton = createButton('清除数据', clearData,'#ff0000');
- const webVPNToggle = createButton(useWebVPN ? '禁用WebVPN' : '启用WebVPN', toggleWebVPN);
- webVPNToggle.id = 'webvpn-toggle';
- const tips = createTips('关闭窗口数据不会消失,如需获取新数据,先点击清除数据,重新获取链接。 | 如果是远程登录(不可用)的webvpn,获取出错可以尝试把webvpn开关打开。', '#0b1f64');
- const tips2 = createTips('如果报错,请在脚本网站留言反馈。提供地址、控制台的截图(右键检查或审查元素,转到控制台)。', '#0b1f64');
- const tips3 = document.createElement('div');
- tips3.id = 'script_author';
- tips3.innerHTML = '更新不易,希望大家关注我的公众号和视频频道,感谢支持~ 公众号:木星宝库 YouTube:<a target="_blank" href="https://www.youtube.com/channel/UCC1ExQh99BVTaPbGbGTcUzg/">点击这里</a> B站:<a target="_blank" href="https://space.bilibili.com/7241318">点击这里</a> ';
-
- // 新增关键词筛选功能
- const keywordFilterContainer = document.createElement('div');
- keywordFilterContainer.style.cssText = 'display:flex;margin-bottom:10px;margin-top:5px;';
- const keywordInput = document.createElement('input');
- keywordInput.type = 'text';
- keywordInput.placeholder = '输入关键词筛选(多个关键词用逗号分隔)';
- keywordInput.style.cssText = 'flex-grow:1;padding:5px 5px 5px 8px;margin-right:10px;border:1px solid #eee;border-radius:3px;';
- const filterButton = createButton('筛选', filterByKeywords, '#ff6600');
- const resetFilterButton = createButton('重置', resetFilter, '#888');
-
- keywordFilterContainer.append(keywordInput, filterButton, resetFilterButton);
-
- content.append(closeButton, linkButton, selectAllButton, deselectAllButton, downloadButton, clearDataButton, webVPNToggle,tips, tips2, tips3, keywordFilterContainer);
-
- const table = document.createElement('table');
- table.id = 'my-table';
- table.style.cssText = 'width:100%;margin-top:10px;border-collapse:collapse;';
- table.innerHTML = '<thead><tr><th style="width:3%">多选</th><th style="width:3%">序号</th><th style="width:60%">名称</th><th style="width:7%">链接</th><th>关键词</th></tr></thead><tbody></tbody>';
-
- content.appendChild(table);
- popup.appendChild(content);
- document.body.appendChild(popup);
-
- // 加载保存的数据
- loadSavedData();
- // 获取关键词筛选输入框和按钮的引用
- window.keywordInput = keywordInput;
- }
-
- function filterByKeywords() {
- const keywords = window.keywordInput.value.split(/[,,]/).map(k => k.trim()).filter(Boolean);
- if (keywords.length === 0) return;
-
- const table = document.querySelector('#my-table tbody');
- const rows = table.querySelectorAll('tr');
-
- rows.forEach(row => {
- const keywordsCell = row.querySelector('td:last-child');
- const keywordText = keywordsCell.textContent.toLowerCase();
-
- const shouldShow = keywords.some(keyword =>
- keywordText.includes(keyword.toLowerCase())
- );
-
- row.style.display = shouldShow ? '' : 'none';
- });
-
- createLoading(`已筛选出包含关键词的论文`, 2000);
- }
-
- // 重置筛选
- function resetFilter() {
- const table = document.querySelector('#my-table tbody');
- const rows = table.querySelectorAll('tr');
-
- rows.forEach(row => {
- row.style.display = '';
- });
-
- window.keywordInput.value = '';
- createLoading(`已重置筛选`, 2000);
- }
- // 清除数据函数
- function clearData() {
- localStorage.removeItem('cnkiTableData');
- const table = document.querySelector('#my-table tbody');
- table.innerHTML = '';
- updateSelectCount();
- createLoading('数据已清除', 2000);
- }
- function createButton(text, onClick, color = '#0f5de5') {
- const button = document.createElement('button');
- button.textContent = text;
- button.className = 'diy-btn';
- button.style.color = color;
- button.addEventListener('click', onClick);
- return button;
- }
-
- function createTips(text, color) {
- const tips = document.createElement('span');
- tips.textContent = text;
- tips.className = 'diy-span';
- tips.style.color = color;
- return tips;
- }
-
- function selectAll() {
- document.querySelectorAll('.selectItem').forEach(item => item.checked = true);
- updateSelectCount();
- }
-
- function deselectAll() {
- document.querySelectorAll('.selectItem').forEach(item => item.checked = false);
- updateSelectCount();
- }
-
- function delay(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
- }
-
- async function downloadSelected() {
- const selectedItems = document.querySelectorAll('.selectItem:checked');
- if (selectedItems.length === 0) {
- createLoading('请选择要下载的项目!', 2000);
- return;
- }
-
- for (const item of selectedItems) {
- const link = item.closest('tr').querySelector('a[href^="http"]');
- if (link) {
- // 打开链接
- window.open(link.href, '_blank');
-
- // 生成随机延迟时间:2-5秒(步长0.5秒)
- const randomDelay = Math.floor(Math.random() * 7) * 500 + 2000;
- console.log(`延迟 ${randomDelay / 1000} 秒后继续下载...`);
-
- // 延迟下一次下载
- await delay(randomDelay);
- }
- }
-
- }
-
-
- function updateSelectCount() {
- const count = document.querySelectorAll('.selectItem:checked').length;
- document.querySelector('#dl_sel').textContent = `下载 (${count})`;
- }
-
- // 查找包含“PDF下载”文字内容的链接并获取其链接地址
- function findPDFDownloadLink(element, textContent) {
- // 获取所有包含链接的 li 元素
- var listItems = element.querySelectorAll('li');
- // 遍历所有 li 元素,查找包含指定文本内容的链接
- for (var i = 0; i < listItems.length; i++) {
- var link = listItems[i].querySelector('a');
- if (link && link.innerText.includes(textContent)) {
- // 获取链接地址
- var href = link.href;
- console.log('链接地址:', href);
- return href; // 返回找到的链接地址
- }
- }
- console.log('未找到包含指定文字内容的链接');
- return null; // 如果未找到匹配的链接,返回null
- }
-
- // 切换WebVPN的函数
- function toggleWebVPN() {
- useWebVPN = !useWebVPN;
- GM_setValue('useWebVPN', useWebVPN);
- const toggleButton = document.getElementById('webvpn-toggle');
- if (toggleButton) {
- toggleButton.textContent = useWebVPN ? '禁用WebVPN' : '启用WebVPN';
- }
- createLoading(`WebVPN ${useWebVPN ? '已启用' : '已禁用'}`, 2000);
- }
- function convertToWebVPNLink(originalLink) {
- if (!useWebVPN) {
- return originalLink;
- }
- const webVPNDomain = window.location.origin;
- const path = originalLink.replace(/^(https?:\/\/)?(www\.)?[^\/]+/, '');
- return `${webVPNDomain}${path}`;
- }
- async function getLinks() {
- const table = document.querySelector('#my-table tbody');
- if (table.children.length > 0) {
- createLoading('已有数据!请先清除数据后再获取新链接。', 2000);
- return;
- }
-
- const links = Array.from(document.querySelectorAll('.fz14')).map(link => {
- const originalHref = link.href;
- const webVPNHref = convertToWebVPNLink(originalHref);
- console.log('Original link:', originalHref);
- console.log('WebVPN link:', webVPNHref);
- return webVPNHref;
- });
- const loading = createLoading('获取链接中(请耐心等待)……', 60000);
-
- try {
- console.log(links);
- const batchSize = 5; // 每批处理的链接数
- for (let i = 0; i < links.length; i += batchSize) {
- const batch = links.slice(i, i + batchSize);
- const responses = await Promise.all(batch.map(link =>
- fetch(link, {
- method: 'GET',
- mode: 'same-origin',
- referrerPolicy: 'unsafe-url',
- }).then(response => response.text())
- ));
-
- responses.forEach((html, index) => {
- const doc = new DOMParser().parseFromString(html, 'text/html');
- const h1Element = doc.querySelector('.wx-tit h1');
- h1Element.querySelectorAll('span').forEach(element => element.remove());
- const title = h1Element?.textContent.trim() || '无标题';
- const author = Array.from(doc.querySelectorAll('.author')).map(a => a.textContent).join(' ');
- const pdfLink = findPDFDownloadLink(doc.querySelector('.operate-btn'),'PDF下载');
- const keywords = Array.from(doc.querySelectorAll('.keywords a')).map(keywords => `<span class="diy-btn" style="font-size:12px">${keywords.textContent.replace(";","")}</span>`).join('');
- const row = table.insertRow();
- row.innerHTML = `
- <td style="text-align:center"><input type="checkbox" class="selectItem"></td>
- <td style="text-align:center">${i + index + 1}</td>
- <td><span class="diy_title">${title}</span><span class="diy_author">${author}</span></td>
- <td style="text-align:center">${pdfLink !== null ? `<a href="${pdfLink}" target="_blank" class="diy-btn">PDF下载</a>` : '获取的链接为空,请检查!'}</td>
- <td>${keywords}</td>
- `;
-
- row.querySelector('input[type="checkbox"]').addEventListener('change', updateSelectCount);
- });
-
- // 更新加载提示
- loading.textContent = `正在获取链接...(${Math.min((i + batchSize) / links.length * 100, 100).toFixed(0)}%)`;
- }
-
- document.body.removeChild(loading);
- createLoading('获取完毕!', 2000);
-
- // 在获取完链接后保存数据
- saveData();
- } catch (error) {
- console.error('Error fetching links:', error);
- document.body.removeChild(loading);
- createLoading(`获取链接出错:${error.message}\n links: ${links}`, 3000);
- }
- }
-
- // 新增:保存数据到localStorage
- function saveData() {
- const tableRows = Array.from(document.querySelectorAll('#my-table tbody tr')).map(row => ({
- title: row.querySelector('.diy_title').textContent,
- author: row.querySelector('.diy_author').textContent,
- pdfLink: row.querySelector('a[href^="http"]')?.href || '',
- keywords: row.querySelector('td:last-child').innerHTML
- }));
- localStorage.setItem('cnkiTableData', JSON.stringify(tableRows));
- }
-
- // 新增:从localStorage加载数据
- function loadSavedData() {
- const savedData = localStorage.getItem('cnkiTableData');
- if (savedData) {
- const tableData = JSON.parse(savedData);
- const table = document.querySelector('#my-table tbody');
- table.innerHTML = ''; // 清空现有内容
- tableData.forEach((item, index) => {
- const row = table.insertRow();
- row.innerHTML = `
- <td style="text-align:center"><input type="checkbox" class="selectItem"></td>
- <td style="text-align:center">${index + 1}</td>
- <td><span class="diy_title">${item.title}</span><span class="diy_author">${item.author}</span></td>
- <td style="text-align:center">${item.pdfLink ? `<a href="${item.pdfLink}" target="_blank" class="diy-btn">PDF下载</a>` : '获取链接失败!'}</td>
- <td>${item.keywords}</td>
- `;
- row.querySelector('input[type="checkbox"]').addEventListener('change', updateSelectCount);
- });
- updateSelectCount();
- }
- }
-
- // 目录下载功能
- function addCategoryDownloadButton() {
- const otherBtns = document.querySelector('.other-btns');
- if (!otherBtns) return;
-
- const li = document.createElement('li');
- li.className = 'btn-diy';
- li.style.cssText = 'width:auto;height:23px;line-height:22px;background-color:#3f8af0;border-radius:3px;';
-
- const a = document.createElement('a');
- a.textContent = '目录下载';
- a.className = 'a-diy';
- a.style.cssText = 'color:#ffffff;padding:2px 10px;';
- a.href = 'javascript:void(0)';
- a.addEventListener('click', downloadCategory);
-
- li.appendChild(a);
- otherBtns.appendChild(li);
- }
-
- async function downloadCategory() {
- const hrefLink = findPDFDownloadLink(document.querySelector('.operate-btn'),'章节下载');
- console.log("目录链接:" + hrefLink);
- if (!hrefLink) {
- createLoading('无法获取目录链接', 2000);
- return;
- }
-
- try {
- const response = await fetch(hrefLink);
- const html = await response.text();
- const doc = new DOMParser().parseFromString(html, 'text/html');
- const title = document.querySelector('.wx-tit h1')?.textContent.trim() || '未知标题';
- const chapters = Array.from(doc.querySelectorAll('.ls-chapters li'))
- .map(chapter => chapter.textContent.trim())
- .filter(Boolean)
- .map(text => {
- const parts = text.split('-');
- return `${parts[0].replace(/\n/g, '\t')} \n`;
- })
- .join('');
- console.log(chapters);
- saveFile(title, chapters);
- } catch (error) {
- console.error('Error downloading category:', error);
- createLoading('下载目录失败', 2000);
- }
- }
-
- function saveFile(name, data) {
- const blob = new Blob([data], { type: 'text/plain' });
- const link = document.createElement('a');
- link.href = URL.createObjectURL(blob);
- link.download = `${name}.txt`;
- link.click();
- URL.revokeObjectURL(link.href);
- }
-
-
- // 初始化函数
- function init() {
- const url = window.location.href.toLowerCase();
- console.log(url);
-
- // 在所有页面上添加批量下载按钮
- // createPopupButton();
- // 如果不显示按钮,将下面三行注释,取消上一行的代码注释。
- if (url.includes('defaultresult') || url.includes('advsearch') || url.includes('search')) {
- createPopupButton();
- }
-
- // 在文献详情页面添加目录下载按钮
- if (url.includes('abstract')) {
- addCategoryDownloadButton();
- }
-
- // 加载自定义CSS
- loadCss(`
- .diy-btn {
- display: inline-block;
- vertical-align: middle;
- padding: 2px 8px;
- line-height: 18px;
- color: #0f5de5;
- font-size: 14px;
- text-align: center;
- background-color: #e3ecfd;
- border: 1px solid #fff;
- cursor: pointer;
- }
- .diy-span {
- display: inline-block;
- vertical-align: middle;
- padding: 2px 8px;
- line-height: 18px;
- color: #0f5de5;
- font-size: 12px;
- text-align: center;
- }
- #popup table tr td,
- #popup table tr th {
- line-height: 20px;
- height: 20px;
- padding: 5px;
- border: 1px solid #eee;
- }
- .diy_title {
- display: block;
- color: #524d4d;
- font-size: 14px;
- font-weight: bold;
- }
- .diy_author {
- color: #666;
- }
- .font-size-button {
- font-size: 14px;
- display: block;
- line-height: 18px;
- border: 1px solid #e2e2e2;
- border-radius: 2px;
- background-color: #f5f5f5;
- color: #504f4f;
- float: left;
- padding: 3px;
- position: absolute;
- right: 0;
- width: 28px;
- cursor: pointer;
- }
- #script_author {
- color: rgb(14 93 228);
- display: block;
- background-color: #e3ecfd;
- margin-top: 5px;
- text-align: center;
- padding: 4px;
- }
- .first_show_li li{
- line-height:30px;
- }
- `);
- }
-
- // 在页面加载完成后运行初始化函数
-
- // 在页面加载完成后运行初始化函数
- window.addEventListener('load', init);
-
- })();
-