// ==UserScript==
// @name CNKI保存结果页面
// @version 1.4
// @description 在结果页面右下角添加保存按钮,点击后保存数据为JSON并下载所有文献详情页为HTML
// @match https://kns.cnki.net/kns8s/search*
// @match https://kns.cnki.net/kns8s/defaultresult/index*
// @grant GM_xmlhttpRequest
// @grant GM_download
// @grant GM_registerMenuCommand
// @namespace https://greasyfork.org/users/1237542
// ==/UserScript==
(function() {
'use strict';
let executeScript = false;
// 主函数:提取数据并触发保存
function main() {
if (!executeScript) return;
executeScript = false; // 防止重复执行
const rows = document.querySelectorAll('tbody tr');
const data = Array.from(rows).map(row => {
const titleElement = row.querySelector('.name a.fz14');
const authors = Array.from(row.querySelectorAll('.author a'))
.map(a => a.textContent.trim())
.filter(a => a && !a.includes('+')); // 过滤无效作者
return {
title: titleElement ? sanitizeTitle(titleElement.textContent.trim()) : '',
href: titleElement?.href || '',
authors: authors,
source: row.querySelector('.source a')?.textContent.trim() || '',
date: row.querySelector('.date')?.textContent.trim() || '',
type: row.querySelector('.data span')?.textContent.trim() || ''
};
});
// 保存原始数据为JSON
const defaultFilename = getDefaultFilename() + '.json';
saveData(JSON.stringify(data, null, 2), defaultFilename, 'application/json');
// 逐个下载HTML详情页(随机延迟防止封禁)
data.forEach((item, index) => {
if (!item.href) return;
setTimeout(() => {
GM_xmlhttpRequest({
method: 'GET',
url: item.href,
onload: (resp) => {
const filename = `${sanitizeTitle(item.title)}.html`;
saveData(resp.responseText, filename, 'text/html');
},
onerror: (err) => console.error('下载失败:', item.title, err)
});
}, index * (1500 + Math.random() * 1000)); // 1.5-2.5秒随机延迟
});
}
// 生成默认文件名(基于检索条件中的作者)
function getDefaultFilename() {
/* 新选择器层级说明:
span.conditions → 条件栏容器
a → 包含检索条件的链接
文本结构:">作者:陈国伟"
*/
const conditionText = document.querySelector('span.conditions a')?.textContent || '';
// 使用更精确的正则表达式提取
const authorMatch = conditionText.match(/作者:\s*([^<]+)/);
if(authorMatch && authorMatch[1]) {
return sanitizeTitle(authorMatch[1].split(/[;;]/)[0]); // 取第一个作者
}
return 'CNKI_Data'; // 默认值
}
// 清理文件名函数(增强特殊字符处理)
function sanitizeTitle(str) {
return str
.replace(/[\u00A0]/g, ' ') // 替换不间断空格
.replace(/[\\/:*?"<>|《》]/g, '_')
.replace(/\s{2,}/g, ' ') // 合并多个空格
.trim()
.substring(0, 50); // 限制长度
}
// 通用保存函数(自动根据类型添加后缀)
function saveData(content, filename, type = 'text/plain') {
const blob = new Blob([content], { type });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
setTimeout(() => {
URL.revokeObjectURL(url);
link.remove();
}, 100);
}
// 添加悬浮按钮
const button = document.createElement('div');
Object.assign(button.style, {
position: 'fixed',
bottom: '20px',
right: '20px',
width: '50px',
height: '50px',
background: '#2196F3',
borderRadius: '50%',
textAlign: 'center',
lineHeight: '50px',
color: 'white',
cursor: 'pointer',
boxShadow: '0 2px 5px rgba(0,0,0,0.3)',
zIndex: 9999
});
button.textContent = '↓';
button.title = '保存本页数据(JSON + HTML)';
button.onclick = () => {
executeScript = true;
main();
button.textContent = '...';
setTimeout(() => button.textContent = '↓', 3000);
};
document.body.appendChild(button);
// 监听AJAX加载(适用于分页场景)
let lastPageCount = 0;
const observer = new MutationObserver(() => {
const currentCount = document.querySelectorAll('tbody tr').length;
if (currentCount !== lastPageCount) {
lastPageCount = currentCount;
console.log('检测到DOM更新,重新挂载按钮');
button.style.display = 'block'; // 防止分页时按钮消失
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();