NSFC系统改版后结题报告批量下载失效?三步修复浏览器脚本
2026/9/18 6:02:34 网站建设 项目流程

每年结题季,对攒了一堆国家自然科学基金项目的人来说,都是一场折磨。学校科研处催着交存档,手里五六份结题报告要一篇篇从NSFC系统里下载,可ISIS系统翻遍页面就是没有批量下载功能。2023年我写了一段浏览器脚本,一键把列表页的报告抓下来,当时用着挺顺手。2024年NSFC系统改版后,脚本再跑直接失灵,下载按钮点了没反应,控制台里全是红色报错。这篇文章就是我那次完整抢救过程的记录:不重新开发脚本,手动修复下载按钮问题,三步就能让批量下载重新跑起来。整个过程不需要装任何额外软件,浏览器自带的开发者工具加一段控制台代码就能搞定,适合所有被结题报告归档折磨的科研人员、科研秘书和实验室管理员。

1. 先弄清2024年NSFC系统改版后下载按钮到底变成了什么样

旧脚本失效,第一反应不是急着改代码,而是先搞清楚界面到底发生了什么变化。NSFC的ISIS系统在2024年做了一次比较大的前端重构,肉眼可见的是页面风格变了、布局换了、登录后的功能菜单也被重新组织过。但真正让脚本报废的,是藏在表面之下的三个变化:DOM结构重写、事件绑定方式变更、下载接口调整。

1.1 旧脚本为什么失效:选择器、事件绑定、接口三个层面逐项排查

老的下载按钮在HTML里长这样:一个带固定id的a标签,直接挂着下载接口的href地址,脚本只需要一句话document.getElementById('downloadBtn').click()就能完成下载。新版系统改成了前端框架动态渲染的模式,按钮id变成了随机字符串,href属性消失,取而代之的是一堆data属性和事件监听。如果脚本还在用旧的id定位元素,拿到的就是null,后面的click自然无从执行。

即便选择器侥幸能抓到元素,事件绑定方式也可能变了。老站点大量使用inline的onclick属性,脚本直接调用click()等同于触发浏览器的原生点击流程;新版前端框架用的是addEventListener挂载事件,如果脚本还想着通过$('#btn').trigger('click')这类方式触发,不一定能唤起框架内部绑定的处理函数,就会出现"元素找到了、代码没报错、页面没反应"的怪象。

再往下挖,下载接口本身也可能调整了。旧接口直接返回PDF文件流,浏览器接收到就可以保存;新接口改成了POST请求,参数做了签名加密,用旧接口地址去请求多半会拿到403状态码或者一段JSON错误提示。要判断具体是哪一层出了问题,最有效的办法不是猜,而是开浏览器开发者工具,按下面三板斧去排查。

1.2 从开发者工具里快速锁定新下载逻辑的三板斧

第一板斧是取元素。打开结题报告列表页,按F12键进入开发者工具,点左上角的箭头图标,再点页面上的"下载"按钮,Elements面板会直接定位到对应节点。这时候把外层HTML完整展开,认真记录按钮的id、class、data属性,以及外层包裹的容器结构。新版系统的按钮可能是Element UI风格的<button class="el-button el-button--small">加上自定义的><button class="el-button el-button--small">// 旧代码 document.getElementById('downloadBtn').click(); // 新代码:根据data属性定位 document.querySelector('button[data-action="download"]').click();

这里有一个重点提示:模拟点击有个前提,按钮事件必须挂在原生DOM上,且页面渲染完成后脚本才执行。如果换成新选择器后点了依然没反应,就别在按钮上继续死磕,直接改成调接口,这一步往往才是真正的修复点。

调接口的具体做法是:在Network面板里找到下载请求,右键点击该请求,选择Copy > Copy as fetch。浏览器会生成一段完整的fetch命令,包含URL、请求头和方法,直接粘贴到Console面板运行,如果返回正常文件流,就把这段fetch代码封成函数:

function downloadReport(reportId) { return fetch(`/api/report/download?reportId=${reportId}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': getCsrfToken() }, body: JSON.stringify({ reportId }) }) .then(res => res.blob()) .then(blob => { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `结题报告_${reportId}.pdf`; a.click(); URL.revokeObjectURL(url); }); }

CSRF token的获取方式需要留意。新版系统通常会在页面头部放一个meta标签,内容类似<meta name="csrf-token" content="abc...">,在函数里读取就行:

function getCsrfToken() { const meta = document.querySelector('meta[name="csrf-token"]'); return meta ? meta.content : ''; }

如果页面里没有meta标签,打开Network面板里的任意一个POST请求,从请求头里找X-CSRF-TOKENX-XSRF-TOKEN字段,手动填进去也能用,只是不如meta标签稳定。

2.3 第三步:控制台逐条验证,再放量批量下载

函数封装好后,先在控制台单条验证一个,这是很多人容易跳过但最关键的环节:

downloadReport('76123').then(() => console.log('单条下载成功'));

确认文件正常落地后,再放量。批量下载前,先获取当前页所有报告的id集合。常见的方式是遍历所有下载按钮:

const ids = Array.from(document.querySelectorAll('button[data-report-id]')) .map(btn => btn.dataset.reportId);

然后逐条调用,同时加延时,避免请求太密触发限流:

async function batchDownload(ids, delay = 1500) { for (const id of ids) { try { await downloadReport(id); console.log(`已完成: ${id}`); } catch (e) { console.error(`失败: ${id}`, e); } await new Promise(r => setTimeout(r, delay)); } } batchDownload(ids);

NSFC系统单页展示的记录数量有限,如果报告量超过一页,建议在页面底部把可加载的内容全部加载出来后再抓取id列表。有人可能想直接在脚本里循环翻页,但那样涉及页面状态切换,容易因为异步渲染没完成而抓漏,不如手动加载完再批量跑,操作成本和出错概率都更低。

3. 修复过程中踩过的坑:常见报错与排查思路

修复脚本这事,最费时间的从来不是改代码,而是排查那些莫名其妙的报错。下面几个典型问题,是我在2024版系统上实际遇到过的,整理出来给后来人省点时间。

3.1 "Cannot read properties of null":不是代码问题,是时序问题

脚本跑起来第一行就报Cannot read properties of null (reading 'click'),十有八九是document.getElementById('downloadBtn')返回了null。新系统的按钮是异步渲染的,页面DOM加载完成并不代表按钮已经出现在页面上。解决思路有两个:一是把脚本放到页面完全加载之后再执行;二是写一个等待轮询,每隔200毫秒检查一次元素是否出现。我在修复时直接用了轮询方式,代码里加一个waitForElement方法:

const waitForElement = (selector, timeout = 15000) => { return new Promise((resolve, reject) => { const startTime = Date.now(); const timer = setInterval(() => { const el = document.querySelector(selector); if (el) { clearInterval(timer); resolve(el); } else if (Date.now() - startTime > timeout) { clearInterval(timer); reject(new Error(`元素 ${selector} 在 ${timeout}ms 内未出现`)); } }, 200); }); };

这样脚本就会等按钮渲染出来后自动继续,不再依赖手动刷新页面。实测下来,网络慢的时候等待时间可能长达3到5秒,15秒的超时上限基本够用。

3.2 下载下来是HTML文件:登录态失效的表现

第二次踩坑是下载"成功"了,但文件打开后是一段HTML源代码,里面写着"请先登录"或"系统错误"。这说明请求虽然发出去了,但服务端没有返回真正的PDF,而是直接把登录页当响应内容返回了。原因通常是脚本请求时没有携带完整的登录凭证,或者请求URL与Network里抓到的版本不一致。

排查思路是核对请求细节。先确认URL是否与Network里的完全一致,包括路径大小写和query参数顺序;再检查Request Headers里是否带上了CookieUser-Agent。用fetch请求时,如果遇到跨域或凭证丢失,在fetch参数里加上credentials: 'include',多数登录态失效问题都能解决。这里再强调一句:下载操作只对你自己的账号登录态有效,不要用他人账号去尝试。

3.3 批量触发风控拦截:节流与随机化

批量下载一旦跑得太快,系统可能返回429状态码,或者弹出滑块验证。这个"太快"没有固定标准,我遇到过连续请求10个就触发的情况,也见过一口气下载几十个没问题的。处理方法是在循环里增加随机延时,每次间隔在1200到2500毫秒之间浮动,模拟真人手工点击的节奏,而不是固定间隔:

const randomDelay = (min = 1200, max = 2500) => new Promise(r => setTimeout(r, Math.floor(Math.random() * (max - min)) + min));

除了延时,还可以把下载顺序打乱,避免每次都是同一个顺序访问。这套操作能显著减少被拦的概率,但并不保证一定能绕过所有风控。如果系统限制严格,稳妥的做法是减小批次,分几天跑完,别一口气全下完。

4. 实在不想碰脚本?给你两条临时备用路线

有些朋友对写脚本确实没兴趣,只是这一次要用,不想折腾。这种情况下也有偷懒的办法,效果一样,过程更简单。

4.1 用浏览器控制台手动循环点按钮

如果只是偶尔下载几篇报告,不用写完整脚本,直接在浏览器控制台里写一个循环,强制触发页面上所有下载按钮的click事件。在Console面板里粘贴这段就能跑:

const btnList = document.querySelectorAll('button[data-action="download"]'); let idx = 0; const timer = setInterval(() => { if (idx >= btnList.length) { clearInterval(timer); console.log('所有下载按钮均已触发'); return; } btnList[idx].click(); idx++; }, 2000);

这样每隔两秒自动点一次下载,浏览器按提示依次保存文件。注意每次点击后要等浏览器弹窗处理完,再继续下一轮,不然容易漏掉文件。这个方案的优点是零门槛,复制粘贴就能跑;缺点是保存弹窗需要人工逐个确认,报告数量很多的时候依然费手。如果系统弹窗拦截了多个自动下载,还需要在浏览器设置里临时允许该站点的多文件下载权限。

4.2 把修复经验固化成配置化脚本

建议以后还要长期跟结题报告打交道的人,把修复经验固化下来。修复完这次问题后,我把脚本里容易变的东西全部抽成了配置,包括按钮选择器、下载接口地址、请求方式、参数名、延时范围,全部放在脚本顶部的配置对象里。下次系统再改版,只需要重新抓一次DOM和接口,更新配置即可,核心函数一行都不用改。这个习惯给我省了不少事,科研管理系统这两年更新频率明显比前几年高,每次都从头重写脚本确实太累。

配置化的大致思路是:

const CONFIG = { buttonSelector: 'button[data-action="download"]', reportIdAttr: 'data-report-id', apiUrl: '/api/report/download', method: 'POST', csrfMetaName: 'csrf-token', delayRange: [1200, 2500], timeout: 15000 }; async function downloadReport(reportId) { // 核心逻辑全部基于CONFIG动态拼接 const url = CONFIG.apiUrl.replace('{reportId}', reportId); // ... 后续处理 }

这样即使NSFC下次又把按钮class换掉,你只需要更新第一行配置里的选择器,就能继续跑。

最后分享一点个人体会。这类科研管理系统的下载功能,本质上是高频使用的需求,但官方一直没做批量下载,自己动手补全体验就成了必然选择。修复过程中最重要的是耐心看Network面板,请求一抓,问题基本就水落石出了。如果你也遇到类似情况,建议先花十分钟做排查,再动手改,而不是上来就重写整个脚本。经过几次调教,我现在已经能做到系统改版后半天内恢复下载功能,批处理几十份报告也能稳定跑完。希望这篇记录能帮你在结题季少走点弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询