前端Javascript脚本一键获取网页中的表格资源并导出为Excel文件
我们可能会遇到需要把网页中获取的表格资源转成excel下载,部分网站提供的下载内容可能不能满足你的需求,这时候就需要自定义一个网页表格数据下载的方法了。既然是网页资源嘛,我们最简单的考虑还是通过前端的js代码获取资源并下载,并且制作成油猴脚本可以方便他人使用。
思路一:逐页访问获取表格内容下载
这是第一个版本的,需要访问http://219.xx.xx.xx:8081/whoa/Index.aspx,这个表格数据写的比较简单,js代码没有经过匿名化,通过检查会引起数据刷新的按键绑定的点击事件,可以找到js代码里原本写的获取表格数据的方法,这就比较简单了,直接拿来就行了。
整个思路就是模翻页操作,一页页获取表格内容,拼接到一起。
主要遇到的问题是表格数据展示的页面是在一个新的<iframe>中的,网页本身的jquery不能访问,需要通过
var x = document.getElementById("MFrame");
var y = (x.contentWindow || x.contentDocument);
var iJquery = y.jQuery;
的方法获取一个新的局域jquery来进行操作
最终的代码如下,当页面加载完毕后会在左下角放置一个比较隐蔽的start按键,点击后会获取表格所在的MFrame,模拟点击表头对时间进行排序,然后获取总页数,对每一页分别通过loopNext()函数获取内容。由于翻页需要时间,就设置了setInterval()循环去检测当前页页码是否是我们需要的下一页,并且当前的第一个case跟我们缓存的上一页的第一个case不同时才去把这个表的数据加入html字符串,加入的方式在addNewData()函数里,通过map函数实现。

// ==UserScript== // @name hh1 // @namespace http://tampermonkey.net/ // @version 1.0 // @description xx // @author xx // @match http://219.xx.xx.xx:8081/whoa/Index.aspx // @icon https://www.google.com/s2/favicons?sz=64&domain=192.76 // @require https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.5.1.min.js // @grant GM_addStyle // ==/UserScript== var start_css = ".egg_start_btn{outline:0;border:0;position:fixed;bottom:5px;left:5px;padding:12px 20px;border-radius:10px;cursor:pointer;background-color:#fff0;color:#d2d2d2;font-size:14px;text-align:center}"; GM_addStyle(start_css); var html = ''; var sheetName = ''; var tmp_case_num = ''; var total_num = 0; var total_page = 0; var case_count = 0; var speed = 5; const thead = ['课程号', '课程时间', '课程内容']; $(document).ready(function() { let ready = setInterval(function() { if (document.getElementsByClassName("m-top-box")[0]) { clearInterval(ready); //停止定时器 //创建"开始"按钮 createStartButton(); } }, 800); }); function downloadExcel() { // 将table添加到html中,在html中加入excel和sheet元素 let template = ''; template += '<html lang="" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel">'; template += '<head><title></title>'; template += '<xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet>'; template += '<x:Name>'; template += sheetName; template += '</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions>'; template += '</x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml>'; template += '</head>'; template += '<body><table>'; template += html; template += '</table></body>'; template += '</html>'; // 将html编码为excel文件,并下载 let url = 'data:application/vnd.ms-excel;base64,' + window.btoa(unescape(encodeURIComponent(template))); let a = document.createElement('a'); a.href = url; a.download = sheetName + ".xls"; a.click(); } function createStartButton() { let body = document.getElementsByTagName("body")[0]; let startButton = document.createElement("button"); startButton.setAttribute("id", "startButton"); startButton.innerText = "START"; startButton.className = "egg_start_btn"; //添加事件监听 try { // Chrome、FireFox、Opera、Safari、IE9.0及其以上版本 startButton.addEventListener("click", start, false); } catch(e) { try { // IE8.0及其以下版本 startButton.attachEvent('onclick', start); } catch(e) { // 早期浏览器 console.log("error: 开始按钮绑定事件失败") } } //插入节点 body.append(startButton); console.log(`%ctttttttttttttttttttttttttttttttttttttttfi;i1ttttttttttttttttttttttttttttttffffffffffffffLLLLLLLLLLLLLLLLLL tttttttttttttttttttttttttttttttttttiiiiiiiii1tttttttttttttttttt1iiiiiiiiii1tffffffft1ffLLfLLLLLLLLLLLLLLLL ttttttttttttttttttttttttttttttttttt11iiiiii1ttttttffftffftffffftt111111111tffffffffffffLLLLLLLLLLLLLLLLLLL ttttttttttttttttttttttttttt111tt1ii1ttt1111tfLffffLGCCCLLGLLLLffft1111111tffffffLfLffLLLLLLLLLLLLLLLLLLLLL ttttttttttttttttttttttttttttt1iiiii1tCLLfftLCCGGi,,:fLGGGGGGGLfttt1111111ttfGLGGLLLLfLLLLLLLLLLLLLLLLLLfLL ttttttttttttfffffLffffftfttfft11111tf1::::::::::::::::,:::::::;fLLGLt111tLLLGGLL8CGGLLLLLLLLLLLLLLLLLLLLLL`,'color:#aaa;font-size:3px;');//字符画 } function loopNext(tmp_case_num, next_page, iJquery){ let timer = setInterval(() =>{ let case_num = iJquery(".tdlist").data("ordertable").TABLE[0].课程号; if (case_num != tmp_case_num && iJquery("#txtPage").val()==next_page) { clearInterval(timer); tmp_case_num = case_num; console.log('Processing...'); //console.log('Processing for page ' + next_page + '/' + total_page + ' ...'); let tbody = iJquery(".tdlist").data("ordertable").TABLE; case_count += tbody.length; addNewData(tbody); if (iJquery("#NextPageBtn .btn_disabled").text() == "0") { iJquery(".page-next").click(); setTimeout(()=>{ loopNext(tmp_case_num, next_page + 1, iJquery); }, 1000 / speed + 100); } else { setTimeout(()=>{ downloadExcel(); console.log("%c一共"+case_count+"/"+total_num+"份课程,请注意核对数量是否正确,内容是否重复!",'color:#ba0707;font-size:20px;font-weight: bold;'); }, 1000); } } }, i * 1000 / speed); } function addNewData(tbody) { tbody.map(item =>{ html += '<tr>'; thead.map(v =>{ html += '<td style="text-align: center;">'; html += (item[v] ? item[v] : '') + '\t'; html += '</td>'; }); html += '</tr>'; }); } function start() { var x = document.getElementById("MFrame"); var y = (x.contentWindow || x.contentDocument); var iJquery = y.jQuery; iJquery(".th_td:nth-child(6)").click(); // 寻找对应的表头进行点击排序,排序后翻页才能不重复 //console.log(iJquery(".tdlist").html()); total_num = iJquery(".tdlist").data("ordertable").COUNT; total_page = iJquery("#AllPageNum").html(); let time = new Date(); let Y = time.getFullYear(); let M = time.getMonth(); M = (M + 1) < 10 ? '0' + (M + 1) : (M + 1); //这里月份加1的原因是因为月份是从0开始的,0-11月,加1让月份从1-12月区间。 let d = time.getDate(); d = d < 10 ? '0' + d: d; let h = time.getHours(); h = h < 10 ? '0' + h: h; let m = time.getMinutes(); m = m < 10 ? '0' + m: m; sheetName = Y + "-" + M + "-" + d + "-" + h + "-" + m; html = ''; html += '<tr>'; thead.map(item => { html += '<th>'; html += item + '\t'; html += '</th>'; }); html += '</tr>'; case_count = 0; tmp_case_num = ''; $.ajax({ type : "GET", url : 'http://api.tianapi.com/qingshi/index?key=xxxx', success : function(poem){ //console.log(poem_str); //let poem = eval("("+poem_str+")"); //'{"code":200,"msg":"success","newslist":[{"content":"离愁渐远渐无穷,迢迢不断如春水。","source":"踏莎行","author":"欧阳修"}]}' poem = poem.newslist[0]; console.log("%c❤❤❤Wish you happy everyday.❤❤❤",'color:#ba0707;text-align: center;font-size:20px;'); console.log("%c"+poem.content+"\n\t\t\t\t\t%c——"+poem.author+"《"+poem.source+"》",'color:#740505;text-align: center;font-size:20px;','color:#740505;text-align: right;font-size:14px;'); console.log("Waiting to be arranged in order..."); setTimeout(()=>{ loopNext(tmp_case_num, 1, iJquery); },1000); }, error: function (err) { console.log("%c❤❤❤You are everything when you are with me, and everything is you when you are not.",'color:#ba0707;font-size:20px;'); console.log("Waiting to be arranged in order..."); setTimeout(()=>{ loopNext(tmp_case_num, 1, iJquery); },1000); } }); }
思路二:根据网页的HTTP请求,模拟请求进行表格内容获取
后来经手了一个新的网页表格下载的需求,由于我不是网页制作的开发人员,无法获取人家的源代码,这个新网页的代码全部都进行了匿名化处理,导致去找按键绑定事件看的也是云里雾里。我就尝试了一个新的方法,通过chrome的http请求记录功能,去尝试模拟其对应的http请求,获取表格数据。
chrome打开http请求记录的方法为
这样当网页进行了http请求时你就能看到请求的链接。但实际上这个方法看链接也只能看到请求网址,看不到请求载荷(就是?后面跟的参数)
我们可以通过查看chrome的“网络”页面,寻找我们需要的请求的详细参数,其中重要的为
- 标头:告诉你对应的网址和请求方式,
- 载荷:告诉你请求的详细参数(点击查看源代码才是原始链接形式),我之前就出过错,模拟请求没有带对应的参数,导致一直报错500,我还说这服务器这么智能识别了我的异常请求
- 预览:我之前做的是内网的网页的插件,访问内网本身多了步代理的操作,代理的时候请求的方式是OPTION,正常的方式是GET和POST等,这时候就需要通过预览来快速查看正确返回值的网页
那我们在js脚本中如何捕获http请求呢,方法在于修改http请求的函数。比如下面的方式,将console.log()函数通过add()函数加进了http请求中。我们在第6行获取了本次请求的参数之后就可以任意做操作了,在我们这个例子中请求的参数arg是个列表,第一个是请求方式,第二个是请求url,我们可以针对url提取对应的token,为我们所用
1 function XMLHttpRequestBreak(fun=()=>false){ 2 let f = XMLHttpRequest.prototype.open; 3 4 let add = function(){ 5 XMLHttpRequest.prototype.open = function(...args){ 6 check = fun(args); 7 if(check){ 8 throw check; 9 } 10 f.apply(this,args) 11 } 12 }; 13 14 let remove = function(){ 15 XMLHttpRequest.prototype.open = f 16 }; 17 18 return {add, remove} 19 } 20 21 test = XMLHttpRequestBreak(console.log); 22 test.add() 23 test.remove()
现在的问题就变得简单了,变为模拟获取数据操作的请求,主要的难点在于处理返回的数据,这里下载了两个表的内容,一个是todo,一个是end。
todo的内容就比较规整,主要是有的手机号中带了星号,需要再次访问一个url获取。
end的内容需要再次访问一个url获取处理的细节,处理的细节json中又有列表,我们将列表中的回复和满意度都用====进行了分割,放在一个单元格进行展示。
其中url请求的max_num是要先模拟一次请求,获取总共有多少个事件,这样比直接设定一个超级大的上限要更科学。

// ==UserScript== // @name hh2 // @namespace http://tampermonkey.net/ // @version 2.0 // @description xx // @author xx // @match http://rafxxx.natappfree.cc/* // @icon https://www.google.com/s2/favicons?sz=64&domain=192.76 // @require https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.5.1.min.js // @grant GM_addStyle // ==/UserScript== var start_css = ".egg_start_btn{outline:0;border:0;position:fixed;bottom:5px;left:5px;padding:12px 15px;border-radius:10px;cursor:pointer;background-color:#fff0;color:#e5eef8;z-index:999;font-size:12px;text-align:center}"; GM_addStyle(start_css); var html_todo = ''; var html_end = ''; var sheetName = ''; var token = ''; var test = ''; const thead_todo = [ //'事件ID', '事件号', '事件类型名', '规定完成时间', '电话']; const thead_todo_en = [ //'id', 'caseNumber', 'eventType', 'regCompleteTime', 'reportPhone']; const thead_end = [ //'事件ID', '事件号', '事件类型名', '规定完成时间','经办处室', '实际完成时间', '处理结果', '是否满意']; const thead_end_en = [ //'id', 'caseNumber', 'eventType', 'eventRegCompleteTime'];//handleDeptName,createTime,message,satisfactionType const http_pre = "http://10.xx.xx.xx:9000/"; function XMLHttpRequestBreak(fun=()=>false){ let f = XMLHttpRequest.prototype.open; let remove = function(){ XMLHttpRequest.prototype.open = f }; let add = function(){ XMLHttpRequest.prototype.open = function(...args){ if (args[0] == "POST"){ let check = fun(args[1]); token = getParams(args[1], 'accessToken'); if (token){ console.log("%cGet ready for token="+token,'color:#ba0707;text-align: center;font-size:20px;'); remove(); } if (check){ throw check; } } f.apply(this,args); } }; return {add, remove}; } $(document).ready(function() { let ready = setInterval(function() { if (document.getElementsByClassName("el-container")[0]) { //如果找到了这个,就认为加载完毕 clearInterval(ready); //停止定时器 //创建"开始学习"按钮 createStartButton(); console.log(`%ctttttttttttttttttttttttttttttttttttttttfi;i1ttttttttttttttttttttttttttttttffffffffffffffLLLLLLLLLLLLLLLLLL tttttttttttttttttttttttttttttttttttiiiiiiiii1tttttttttttttttttt1iiiiiiiiii1tffffffft1ffLLfLLLLLLLLLLLLLLLL ttttttttttttttttttttttttttttttttttt11iiiiii1ttttttffftffftffffftt111111111tffffffffffffLLLLLLLLLLLLLLLLLLL ttttttttttttttttttttttttttt111tt1ii1ttt1111tfLffffLGCCCLLGLLLLffft1111111tffffffLfLffLLLLLLLLLLLLLLLLLLLLL ttttttttttttttttttttttttttttt1iiiii1tCLLfftLCCGGi,,:fLGGGGGGGLfttt1111111ttfGLGGLLLLfLLLLLLLLLLLLLLLLLLfLL`,'color:#aaa;font-size:3px;');//抽象画 } }, 800); }); function downloadExcel(html,suffix) { // 将table添加到html中,在html中加入excel和sheet元素 let template = ''; template += '<html lang="" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel">'; template += '<head><title></title>'; template += '<xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet>'; template += '<x:Name>'; template += sheetName + "-" + suffix; template += '</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions>'; template += '</x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml>'; //html转excel中的\n换行符不管用,需要用<br/>。通过加css,控制<br/>在同一个单元格换行,否则会分割一个单元格。 template += '<style type="text/css">br {mso-data-placement:same-cell;}</style>'; template += '</head>'; template += '<body><table>'; template += html; template += '</table></body>'; template += '</html>'; // 将html编码为excel文件,并下载 let url = 'data:application/vnd.ms-excel;base64,' + window.btoa(unescape(encodeURIComponent(template))); let a = document.createElement('a'); a.href = url; a.download = sheetName + "-" + suffix +".xls"; a.click(); } function getParams(url, params){ // 获取url中某个参数的值 var res = new RegExp("(?:&|/?)" + params + "=([^&$]+)").exec(url); return res ? res[1] : ''; } function createStartButton() { let body = document.getElementsByTagName("body")[0]; let startButton = document.createElement("button"); startButton.setAttribute("id", "startButton"); startButton.innerText = "S\nT\nA\nR\nT"; startButton.className = "egg_start_btn"; //添加事件监听 try { // Chrome、FireFox、Opera、Safari、IE9.0及其以上版本 startButton.addEventListener("click", start, false); } catch(e) { try { // IE8.0及其以下版本 startButton.attachEvent('onclick', start); } catch(e) { // 早期浏览器 console.log("error: 开始按钮绑定事件失败") } } //插入节点 body.append(startButton); //监听请求 test = XMLHttpRequestBreak(console.log); test.add(); } function downloadTodo(){ let max_num_todo = 2; let url_todo = http_pre + "distribute/disEventInfo/toDoList?accessToken="+token+"&boxType=1&pageNumber=1&pageSize="+max_num_todo+"&searchConditionJson=%5B%7B%22"; console.log(url_todo); $.ajax({ type : "POST", url : url_todo, success : function(res){ //{state: true, msg: '', data: Array(2), code: 200, total: 499, …} max_num_todo = res.total+88; url_todo = http_pre + "distribute/disEventInfo/toDoList?accessToken="+token+"&boxType=1&pageNumber=1&pageSize="+max_num_todo+"&searchConditionJson=%5B%7B%22"; $.ajax({ type : "POST", url : url_todo, success : function(res){ //{state: true, msg: '', data: Array(499), code: 200, total: 499, …} let data = res.data; console.log(data); addNewDataTodo(data); //console.log(html_todo); downloadExcel(html_todo,"todo"); downloadEnd(); }, error: function (err) { console.log("%c!WARNING!",'color:#ba0707;font-size:20px;'); console.log(err); },async:false }); }, error: function (err) { console.log("%c!WARNING!",'color:#ba0707;font-size:20px;'); console.log(err); },async:false }); } function downloadEnd(){ let max_num_end = 2; let url_end = http_pre + "distribute/disEventInfo/EndList?accessToken="+token+"&pageNumber=1&pageSize="+max_num_end+"&searchConditionJson=%5B%7B%22"; $.ajax({ type : "POST", url : url_end, success : function(res){ //{state: true, msg: '', data: Array(2), code: 200, total: 1169, …} max_num_end = res.total+88; url_end = http_pre + "distribute/disEventInfo/EndList?accessToken="+token+"&pageNumber=1&pageSize="+max_num_end+"&searchConditionJson=%5B%7B%22"; $.ajax({ type : "POST", url : url_end, success : function(res){ //{state: true, msg: '', data: Array(1169), code: 200, total: 1169, …} let data = res.data; console.log(data); addNewDataEnd(data); //console.log(html_end); downloadExcel(html_end,"end"); }, error: function (err) { console.log("%c!WARNING!",'color:#ba0707;font-size:20px;'); console.log(err); },async:false }); }, error: function (err) { console.log("%c!WARNING!",'color:#ba0707;font-size:20px;'); console.log(err); },async:false }); } function addNewDataTodo(tbody) { let count = 1; tbody.map(item =>{ if (count%10 == 0 || count==1|| count==tbody.length){ console.log("Preparing Todo data: ["+ count +"/"+tbody.length+"]"); } let url_event = http_pre + "distribute/disEventInfo/"+item.id+"?accessToken="+token;//to get reportPhone html_todo += '<tr>'; thead_todo_en.map(v =>{ html_todo += '<td style="text-align: center;">'; if (v == 'reportPhone'){ let phone = item[v]; if (phone.indexOf("*") != -1){ //手机号中存在星号 $.ajax({ type : "GET", url : url_event, success : function(res){ //{state: true, msg: '', data: {reportPhone:"189xx",..}, code: 200} phone = res.data.reportPhone; }, error: function (err) { console.log("%c!WARNING!",'color:#ba0707;font-size:20px;'); console.log(err); },async:false }); } if (phone.indexOf(",") != -1) { phone = phone.replaceAll(",","<br/>") } html_todo += (phone ? phone : item[v]) + '\t'; }else{ html_todo += (item[v] ? item[v] : '') + '\t'; } html_todo += '</td>'; }); html_todo += '</tr>'; count++; }); } function CompareDate(d1, d2) { //比较日期,d1比d2晚则返回true return ((new Date(d1.replace(/-/g, "/"))) > (new Date(d2.replace(/-/g, "/")))); } function maxDate(datelist){ // 接受列表 返回最大日期 //datelist eg: ['2019-1-2','2019-3-4','2019-5-7'] var newDateList = datelist.map((x) => new Date(x).getTime()); // 获取Date对象的秒数 var maxdate = Math.max(...newDateList); // 获取最大秒数 var maxdateindex = newDateList.indexOf(maxdate); // 最大秒数索引 var maxdatestring = datelist[maxdateindex];// 通过索引取出datelist中最大日期字符串 return maxdatestring; } function addNewDataEnd(tbody) { let count = 1; tbody.map(item =>{ if (count%10 == 0 || count==1|| count==tbody.length){ console.log("Preparing End data: ["+ count +"/"+tbody.length+"]"); } let url_dispose = http_pre + "distribute/disDisposeMsgList?accessToken="+token+"&processInstId="+item.processInstId+"&processType="+item.processType+"&businessid="+item.businessId;//to get handleDeptName list, message list, last createTime, last disReviewSituationModels.satisfactionType html_end += '<tr>'; thead_end_en.map(v =>{ html_end += '<td style="text-align: center;">'; if (v == 'reportPhone' && item[v].indexOf(",") != -1){ html_end += item[v].replaceAll(",","<br/>") + '\t'; }else{ html_end += (item[v] ? item[v] : '') + '\t'; } html_end += '</td>'; }); $.ajax({//获取handleDeptName,createTime, message, satisfactionType type : "GET", url : url_dispose, success : function(res){ /*[{ "handleDeptName": "精神卫生中心", "message": "尊敬的X先生...", "createTime": "2021-10-12 17:26:36", "disReviewSituationModels": [ { "satisfactionType": "1", } ] }, {...}]*/ let data_list = res.data; //console.log(data_list); let handleDeptName_list = []; let createTime_list = []; let message_list = []; let satisfactionType_list = []; data_list.map(d => { handleDeptName_list.push(d.handleDeptName); createTime_list.push(d.createTime); message_list.push(d.message); let drsm = d.disReviewSituationModels; if (drsm){ let dsType = drsm[drsm.length-1].satisfactionType; if(dsType){ //值为1 satisfactionType_list.push("满意"); }else{ //值为0或null satisfactionType_list.push(parseInt(dsType)==0?"不满意":"未反馈"); } }else{ //没有回访记录 satisfactionType_list.push("未回访"); } }); html_end += '<td style="text-align: center;">'; let handleDeptNames = handleDeptName_list.join('<br/>=====<br/>'); html_end += (handleDeptNames ? handleDeptNames : '') + '\t'; html_end += '</td>'; html_end += '<td style="text-align: center;">'; let last_time = maxDate(createTime_list); html_end += (last_time ? last_time : '') + '\t'; html_end += '</td>'; html_end += '<td style="text-align: center;">'; let messages = message_list.join('<br/>===========<br/>'); html_end += (messages ? messages : '') + '\t'; html_end += '</td>'; html_end += '<td style="text-align: center;">'; let satisfactions = satisfactionType_list.join('<br/>=====<br/>'); html_end += (satisfactions ? satisfactions : '') + '\t'; html_end += '</td>'; }, error: function (err) { console.log("%c!WARNING!",'color:#ba0707;font-size:20px;'); console.log(err); },async:false }); html_end += '</tr>'; count++; }); } function start() { if (!token){ console.log("%c!!!请先访问某个事件,获取Token后再点击开始!!!",'color:#ba0707;text-align: center;font-size:20px;'); return; } test.remove(); console.log("Starting!..."); let time = new Date(); let Y = time.getFullYear(); let M = time.getMonth(); M = (M + 1) < 10 ? '0' + (M + 1) : (M + 1); //这里月份加1的原因是因为月份是从0开始的,0-11月,加1让月份从1-12月区间。 let d = time.getDate(); d = d < 10 ? '0' + d: d; let h = time.getHours(); h = h < 10 ? '0' + h: h; let m = time.getMinutes(); m = m < 10 ? '0' + m: m; sheetName = Y + "-" + M + "-" + d + "-" + h + "-" + m; html_todo = ''; html_todo += '<tr>'; thead_todo.map(item => { html_todo += '<th>'; html_todo += item + '\t'; html_todo += '</th>'; }); html_todo += '</tr>'; html_end = ''; html_end += '<tr>'; thead_end.map(item => { html_end += '<th>'; html_end += item + '\t'; html_end += '</th>'; }); html_end += '</tr>'; $.ajax({ type : "GET", url : 'http://api.tianapi.com/qingshi/index?key=xxx', success : function(poem){ //console.log(poem_str); //let poem = eval("("+poem_str+")"); //'{"code":200,"msg":"success","newslist":[{"content":"离愁渐远渐无穷,迢迢不断如春水。","source":"踏莎行","author":"欧阳修"}]}' poem = poem.newslist[0]; console.log("%c❤❤❤Wish you happy everyday.❤❤❤",'color:#ba0707;text-align: center;font-size:20px;'); console.log("%c"+poem.content+"\n\t\t\t\t\t%c——"+poem.author+"《"+poem.source+"》",'color:#740505;text-align: center;font-size:20px;','color:#740505;text-align: right;font-size:14px;'); downloadTodo(); }, error: function (err) { console.log("%c❤❤❤You are everything when you are with me, and everything is you when you are not.",'color:#ba0707;font-size:20px;'); downloadTodo(); } }); }
并行请求获取表格数据
由于end任务的数据量太大了,且每个数据都要重新请求获取满意度等内容,想通过并行来加速,但又要限制同一时间访问请求数量不能太大,所以通过递归promise来实现:
通过给multiRequest()一个url列表,返回获取数据的列表,在获取的同时也进行了一些处理,之后addNewDataEnd(tbody)可以直接拼接表格。
由于返回的是Promise对象,所以可以通过接续then()进行下载任务。
下面的代码把下载todo和end两个表的任务分成了两个按钮进行绑定,点哪个下哪个。

// ==UserScript== // @name v2 // @namespace http://tampermonkey.net/ // @version xx // @description xx // @author xx // @match http://xxxx/* // @icon https://www.google.com/s2/favicons?sz=64&domain=192.76 // @require https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.5.1.min.js // @grant GM_addStyle // ==/UserScript== var start_css = ".start_btn_todo{outline:0;border:0;position:fixed;bottom:55px;left:5px;padding:12px 15px;border-radius:10px;cursor:pointer;background-color:#fff0;color:#e5eef8;z-index:999;font-size:12px;text-align:center}.start_btn_end{outline:0;border:0;position:fixed;bottom:5px;left:5px;padding:12px 15px;border-radius:10px;cursor:pointer;background-color:#fff0;color:#e5eef8;z-index:999;font-size:12px;text-align:center}"; GM_addStyle(start_css); var html_todo = ''; var html_end = ''; var sheetName = ''; var token = ''; var test = ''; const MAX_CON = 20; //最大并发数 const thead_todo = [ //'事件ID', '事件号', '事件类型名', '规定完成时间', '电话']; const thead_todo_en = [ //'id', 'caseNumber', 'eventType', 'regCompleteTime', 'reportPhone']; const thead_end = [ //'事件ID', '事件号', '事件类型名', '规定完成时间','经办处室', '实际完成时间', '处理结果', '是否满意']; const thead_end_en = [ //'id', 'caseNumber', 'eventType', 'eventRegCompleteTime'];//handleDeptName,createTime,message,satisfactionType const http_pre = "http://10.xx.xx.xx:9000/"; function XMLHttpRequestBreak(fun=()=>false){ let f = XMLHttpRequest.prototype.open; let remove = function(){ XMLHttpRequest.prototype.open = f }; let add = function(){ XMLHttpRequest.prototype.open = function(...args){ if (args[0] == "POST"){ let check = fun(args[1]); token = getParams(args[1], 'accessToken'); if (token){ console.log("%cGet ready for token="+token,'color:#ba0707;text-align: center;font-size:20px;'); remove(); } if (check){ throw check; } } f.apply(this,args); } }; return {add, remove}; } $(document).ready(function() { let ready = setInterval(function() { if (document.getElementsByClassName("el-container")[0]) { //如果找到了这个,就认为加载完毕 clearInterval(ready); //停止定时器 //创建"开始学习"按钮 createStartButton(); console.log(`%ctttttttttttttttttttttttttttttttttttttttfi;i1ttttttttttttttttttttttttttttttffffffffffffffLLLLLLLLLLLLLLLLLL tttttttttttttttttttttttttttttttttttiiiiiiiii1tttttttttttttttttt1iiiiiiiiii1tffffffft1ffLLfLLLLLLLLLLLLLLLL`,'color:#aaa;font-size:3px;'); } }, 800); }); function downloadExcel(html,suffix) { // 将table添加到html中,在html中加入excel和sheet元素 let template = ''; template += '<html lang="" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel">'; template += '<head><title></title>'; template += '<xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet>'; template += '<x:Name>'; template += sheetName + "-" + suffix; template += '</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions>'; template += '</x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml>'; //html转excel中的\n换行符不管用,需要用<br/>。通过加css,控制<br/>在同一个单元格换行,否则会分割一个单元格。 template += '<style type="text/css">br {mso-data-placement:same-cell;}</style>'; template += '</head>'; template += '<body><table>'; template += html; template += '</table></body>'; template += '</html>'; // 将html编码为excel文件,并下载 let url = 'data:application/vnd.ms-excel;base64,' + window.btoa(unescape(encodeURIComponent(template))); let a = document.createElement('a'); a.href = url; a.download = sheetName + "-" + suffix +".xls"; console.log("%c!Finished for "+suffix,'color:#ba0707;font-size:20px;'); a.click(); } function getParams(url, params){ // 获取url中某个参数的值 var res = new RegExp("(?:&|/?)" + params + "=([^&$]+)").exec(url); return res ? res[1] : ''; } function createStartButton() { let body = document.getElementsByTagName("body")[0]; let startButton1 = document.createElement("button"); startButton1.setAttribute("id", "startButton1"); startButton1.innerText = "待\n处\n理"; startButton1.className = "start_btn_todo"; //添加事件监听 try { // Chrome、FireFox、Opera、Safari、IE9.0及其以上版本 startButton1.addEventListener("click", start_todo, false); } catch(e) { try { // IE8.0及其以下版本 startButton1.attachEvent('onclick', start_todo); } catch(e) { // 早期浏览器 console.log("error: 开始按钮绑定事件失败") } } //插入节点 body.append(startButton1); let startButton2 = document.createElement("button"); startButton2.setAttribute("id", "startButton2"); startButton2.innerText = "已\n完\n成"; startButton2.className = "start_btn_end"; //添加事件监听 try { // Chrome、FireFox、Opera、Safari、IE9.0及其以上版本 startButton2.addEventListener("click", start_end, false); } catch(e) { try { // IE8.0及其以下版本 startButton2.attachEvent('onclick', start_end); } catch(e) { // 早期浏览器 console.log("error: 开始按钮绑定事件失败") } } //插入节点 body.append(startButton2); //监听请求 test = XMLHttpRequestBreak(console.log); test.add(); } function downloadTodo(){ let max_num_todo = 2; let url_todo = http_pre + "distribute/disEventInfo/toDoList?accessToken="+token+"&boxType=1&pageNumber=1&pageSize="+max_num_todo+"&searchConditionJson=%5B%7B%22"; console.log(url_todo); $.ajax({ type : "POST", url : url_todo, success : function(res){ //{state: true, msg: '', data: Array(2), code: 200, total: 499, …} max_num_todo = res.total+88; url_todo = http_pre + "distribute/disEventInfo/toDoList?accessToken="+token+"&boxType=1&pageNumber=1&pageSize="+max_num_todo+"&searchConditionJson=%5B%7B%22"; $.ajax({ type : "POST", url : url_todo, success : function(res){ //{state: true, msg: '', data: Array(1169), code: 200, total: 1169, …} let data = res.data; console.log(data); addNewDataTodo(data); //console.log(html_todo); downloadExcel(html_todo,"todo"); //downloadEnd(); }, error: function (err) { console.log("%c!WARNING!",'color:#ba0707;font-size:20px;'); console.log(err); },async:false }); }, error: function (err) { console.log("%c!WARNING!",'color:#ba0707;font-size:20px;'); console.log(err); },async:false }); } function downloadEnd(){ let max_num_end = 2; let url_end = http_pre + "distribute/disEventInfo/EndList?accessToken="+token+"&pageNumber=1&pageSize="+max_num_end+"&searchConditionJson=%5B%7B%22"; $.ajax({ type : "POST", url : url_end, success : function(res){ //{state: true, msg: '', data: Array(2), code: 200, total: 1169, …} max_num_end = res.total+88; url_end = http_pre + "distribute/disEventInfo/EndList?accessToken="+token+"&pageNumber=1&pageSize="+max_num_end+"&searchConditionJson=%5B%7B%22"; $.ajax({ type : "POST", url : url_end, success : function(res){ //{state: true, msg: '', data: Array(1169), code: 200, total: 1169, …} let data = res.data; console.log(data); addNewDataEnd(data); //console.log(html_end); //downloadExcel(html_end,"end"); }, error: function (err) { console.log("%c!WARNING!",'color:#ba0707;font-size:20px;'); console.log(err); },async:false }); }, error: function (err) { console.log("%c!WARNING!",'color:#ba0707;font-size:20px;'); console.log(err); },async:false }); } function addNewDataTodo(tbody) { let count = 1; tbody.map(item =>{ if (count%10 == 0 || count==1|| count==tbody.length){ console.log("Preparing Todo data: ["+ count +"/"+tbody.length+"]"); } let url_event = http_pre + "geosocial-flowable-distribute/disEventInfo/"+item.id+"?accessToken="+token;//to get reportPhone html_todo += '<tr>'; thead_todo_en.map(v =>{ html_todo += '<td style="text-align: center;">'; if (v == 'reportPhone'){ let phone = item[v]; if (phone.indexOf("*") != -1){ //手机号中存在星号 $.ajax({ type : "GET", url : url_event, success : function(res){ //{state: true, msg: '', data: {reportPhone:"189xx",..}, code: 200} phone = res.data.reportPhone; }, error: function (err) { console.log("%c!WARNING!",'color:#ba0707;font-size:20px;'); console.log(err); },async:false }); } if (phone.indexOf(",") != -1) { phone = phone.replaceAll(",","<br/>") } html_todo += (phone ? phone : item[v]) + '\t'; }else{ html_todo += (item[v] ? item[v] : '') + '\t'; } html_todo += '</td>'; }); html_todo += '</tr>'; count++; }); } function CompareDate(d1, d2) { //比较日期,d1比d2晚则返回true return ((new Date(d1.replace(/-/g, "/"))) > (new Date(d2.replace(/-/g, "/")))); } function maxDate(datelist){ // 接受列表 返回最大日期 //datelist eg: ['2019-1-2','2019-3-4','2019-5-7'] var newDateList = datelist.map((x) => new Date(x).getTime()); // 获取Date对象的秒数 var maxdate = Math.max(...newDateList); // 获取最大秒数 var maxdateindex = newDateList.indexOf(maxdate); // 最大秒数索引 var maxdatestring = datelist[maxdateindex];// 通过索引取出datelist中最大日期字符串 return maxdatestring; } function addNewDataEnd(tbody) { let urls = []; tbody.map(item =>{ let url_dispose = http_pre + "distribute/disDisposeMsgList?accessToken="+token+"&processInstId="+item.processInstId+"&processType="+item.processType+"&businessid="+item.businessId;//to get handleDeptName list, message list, last createTime, last disReviewSituationModels.satisfactionType urls.push(url_dispose); }); multiRequest(urls, MAX_CON).then(function(request_result) { let count = 0; tbody.map(item =>{ if (count%10 == 9 || count==0|| count==tbody.length-1){ console.log("Preparing End data: ["+ (count+1) +"/"+tbody.length+"]"); } html_end += '<tr>'; thead_end_en.map(v =>{ html_end += '<td style="text-align: center;">'; if (v == 'reportPhone' && item[v].indexOf(",") != -1){ html_end += item[v].replaceAll(",","<br/>") + '\t'; }else{ html_end += (item[v] ? item[v] : '') + '\t'; } html_end += '</td>'; }); html_end += '<td style="text-align: center;">'; let handleDeptNames = request_result[count].handleDeptNames; html_end += (handleDeptNames ? handleDeptNames : '') + '\t'; html_end += '</td>'; html_end += '<td style="text-align: center;">'; let last_time = request_result[count].last_time; html_end += (last_time ? last_time : '') + '\t'; html_end += '</td>'; html_end += '<td style="text-align: center;">'; let messages = request_result[count].messages; html_end += (messages ? messages : '') + '\t'; html_end += '</td>'; html_end += '<td style="text-align: center;">'; let satisfactions = request_result[count].satisfactions; html_end += (satisfactions ? satisfactions : '') + '\t'; html_end += '</td>'; html_end += '</tr>'; count++; }); }).then(()=>{ downloadExcel(html_end,"end"); }).catch((err) => { console.log(err); }); } function multiRequest(urls = [], maxNum) { // 请求总数量 const len = urls.length; // 根据请求数量创建一个数组来保存请求的结果 const result = new Array(len).fill(false); // 当前完成的数量 let count = 0; return new Promise((resolve, reject) => { // 请求maxNum个 while (count < maxNum) { next(); } function next() { let current = count++; // 处理边界条件 if (current >= len) { // 请求全部完成就将promise置为成功状态, 然后将result作为promise值返回 !result.includes(false) && resolve(result); return; } const url = urls[current]; console.log(`开始 ${current}`, new Date().toLocaleString()); fetch(url) .then((res) => { return res.json(); }) .then((res) => { /*[{ "handleDeptName": "精神卫生中心", "message": "尊敬的X先生...", "createTime": "2021-10-12 17:26:36", "disReviewSituationModels": [ { "satisfactionType": "1", } ] }, {...}]*/ // 保存请求结果 let data_list = res.data; //console.log(data_list); let handleDeptName_list = []; let createTime_list = []; let message_list = []; let satisfactionType_list = []; data_list.map(d => { handleDeptName_list.push(d.handleDeptName); createTime_list.push(d.createTime); message_list.push(d.message); let drsm = d.disReviewSituationModels; if (drsm){ let dsType = drsm[drsm.length-1].satisfactionType; if(dsType){ //值为1 satisfactionType_list.push("满意"); }else{ //值为0或null satisfactionType_list.push(parseInt(dsType)==0?"不满意":"未反馈"); } }else{ //没有回访记录 satisfactionType_list.push("未回访"); } }); let handleDeptNames = handleDeptName_list.join('<br/>=====<br/>'); let last_time = maxDate(createTime_list); let messages = message_list.join('<br/>===========<br/>'); let satisfactions = satisfactionType_list.join('<br/>=====<br/>'); result[current] = { 'handleDeptNames': handleDeptNames, 'last_time': last_time, 'messages': messages, 'satisfactions': satisfactions, }; //console.log(`完成 ${current}`, new Date().toLocaleString()); // 请求没有全部完成, 就递归 if (current < len) { next(); } }) .catch((err) => { console.log(`结束 ${current}`, new Date().toLocaleString()); result[current] = err; // 请求没有全部完成, 就递归 if (current < len) { next(); } }); } }); } function start_todo() { if (!token){ console.log("%c!!!请先访问某个案件,获取Token后再点击开始!!!",'color:#ba0707;text-align: center;font-size:20px;'); return; } test.remove(); console.log("Starting!..."); let time = new Date(); let Y = time.getFullYear(); let M = time.getMonth(); M = (M + 1) < 10 ? '0' + (M + 1) : (M + 1); //这里月份加1的原因是因为月份是从0开始的,0-11月,加1让月份从1-12月区间。 let d = time.getDate(); d = d < 10 ? '0' + d: d; let h = time.getHours(); h = h < 10 ? '0' + h: h; let m = time.getMinutes(); m = m < 10 ? '0' + m: m; sheetName = Y + "-" + M + "-" + d + "-" + h + "-" + m; html_todo = ''; html_todo += '<tr>'; thead_todo.map(item => { html_todo += '<th>'; html_todo += item + '\t'; html_todo += '</th>'; }); html_todo += '</tr>'; $.ajax({ type : "GET", url : 'http://api.tianapi.com/qingshi/index?key=xxx', success : function(poem){ //console.log(poem_str); //let poem = eval("("+poem_str+")"); //'{"code":200,"msg":"success","newslist":[{"content":"离愁渐远渐无穷,迢迢不断如春水。","source":"踏莎行","author":"欧阳修"}]}' poem = poem.newslist[0]; console.log("%c❤❤❤Wish you happy everyday.❤❤❤",'color:#ba0707;text-align: center;font-size:20px;'); console.log("%c"+poem.content+"\n\t\t\t\t\t%c——"+poem.author+"《"+poem.source+"》",'color:#740505;text-align: center;font-size:20px;','color:#740505;text-align: right;font-size:14px;'); downloadTodo(); }, error: function (err) { console.log("%c❤❤❤You are everything when you are with me, and everything is you when you are not.",'color:#ba0707;font-size:20px;'); downloadTodo(); } }); } function start_end() { if (!token){ console.log("%c!!!请先访问某个案件,获取Token后再点击开始!!!",'color:#ba0707;text-align: center;font-size:20px;'); return; } test.remove(); console.log("Starting!..."); let time = new Date(); let Y = time.getFullYear(); let M = time.getMonth(); M = (M + 1) < 10 ? '0' + (M + 1) : (M + 1); //这里月份加1的原因是因为月份是从0开始的,0-11月,加1让月份从1-12月区间。 let d = time.getDate(); d = d < 10 ? '0' + d: d; let h = time.getHours(); h = h < 10 ? '0' + h: h; let m = time.getMinutes(); m = m < 10 ? '0' + m: m; sheetName = Y + "-" + M + "-" + d + "-" + h + "-" + m; html_end = ''; html_end += '<tr>'; thead_end.map(item => { html_end += '<th>'; html_end += item + '\t'; html_end += '</th>'; }); html_end += '</tr>'; $.ajax({ type : "GET", url : 'http://api.tianapi.com/qingshi/index?key=xxx', success : function(poem){ //console.log(poem_str); //let poem = eval("("+poem_str+")"); //'{"code":200,"msg":"success","newslist":[{"content":"离愁渐远渐无穷,迢迢不断如春水。","source":"踏莎行","author":"欧阳修"}]}' poem = poem.newslist[0]; console.log("%c❤❤❤Wish you happy everyday.❤❤❤",'color:#ba0707;text-align: center;font-size:20px;'); console.log("%c"+poem.content+"\n\t\t\t\t\t%c——"+poem.author+"《"+poem.source+"》",'color:#740505;text-align: center;font-size:20px;','color:#740505;text-align: right;font-size:14px;'); downloadEnd(); }, error: function (err) { console.log("%c❤❤❤You are everything when you are with me, and everything is you when you are not.",'color:#ba0707;font-size:20px;'); downloadEnd(); } }); }
上面这些map函数里面套push的,最好可以改成https://www.cnblogs.com/zaishiyu/p/13967240.html里最后这样的直接赋值的情况,虽然说确实是按顺序的,但改一下还是保险一些。
const dateList=res.list.map(item => ({
name:item.name,
age:item.age
})
);
使用这样的方式,就不需要一个map里那么多push了,可以把多个列表元素并为一个列表里的object,这样每次索引的时候同一个object里的内容可以确保是对应的。
组织成表格html字符串,自动导出下载为Excel文件
将打包好的html字符串,发送到下面的函数即可
function downloadExcel(html,sheetName) { // 将table添加到html中,在html中加入excel和sheet元素 let template = ''; template += '<html lang="" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel">'; template += '<head><title></title>'; template += '<xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet>'; template += '<x:Name>'; template += sheetName; template += '</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions>'; template += '</x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml>'; //html转excel中的\n换行符不管用,需要用<br/>。通过加css,控制<br/>在同一个单元格换行,否则会分割一个单元格。 template += '<style type="text/css">br {mso-data-placement:same-cell;}</style>'; template += '</head>'; template += '<body><table>'; template += html; template += '</table></body>'; template += '</html>'; // 将html编码为excel文件,并下载 let url = 'data:application/vnd.ms-excel;base64,' + window.btoa(unescape(encodeURIComponent(template))); let a = document.createElement('a'); a.href = url; a.download = sheetName + ".xls"; a.click(); }
这里会遇到一个问题,如果想要导出的内容里有换行符‘\n’,在excel里是不识别的,需要换成对应的html语言的<br />。但问题是<br />会拆分单元格,把换行后的内容放到另一个单元格了,如果想要在同一个单元格内换行,需要在css中加入以下内容控制<br/>在同一个单元格换行,否则会分割一个单元格。
br {mso-data-placement:same-cell;}
关于时间的操作
获取当前时间作为表名
使用Date()然后格式化字符串
let time = new Date(); let Y = time.getFullYear(); let M = time.getMonth(); M = (M + 1) < 10 ? '0' + (M + 1) : (M + 1); //这里月份加1的原因是因为月份是从0开始的,0-11月,加1让月份从1-12月区间。 let d = time.getDate(); d = d < 10 ? '0' + d: d; let h = time.getHours(); h = h < 10 ? '0' + h: h; let m = time.getMinutes(); m = m < 10 ? '0' + m: m; sheetName = Y + "-" + M + "-" + d + "-" + h + "-" + m;
比较时间的大小,返回最大的时间
function maxDate(datelist){ // 接受列表 返回最大日期 //datelist eg: ['2019-1-2','2019-3-4','2019-5-7'] var newDateList = datelist.map((x) => new Date(x).getTime()); // 获取Date对象的秒数 var maxdate = Math.max(...newDateList); // 获取最大秒数 var maxdateindex = newDateList.indexOf(maxdate); // 最大秒数索引 var maxdatestring = datelist[maxdateindex];// 通过索引取出datelist中最大日期字符串 return maxdatestring; }
判断两个时间的先后
function CompareDate(d1, d2) { //比较日期,d1比d2晚则返回true return ((new Date(d1.replace(/-/g, "/"))) > (new Date(d2.replace(/-/g, "/")))); }
获取url中某个参数的值
function getParams(url, params){ // 获取url中某个参数的值 var res = new RegExp("(?:&|/?)" + params + "=([^&$]+)").exec(url); return res ? res[1] : ''; }
console界面交互
输出字符串勾勒的图片
为了增加一些交互感,在console界面输出了一个字符串图片,你可以把你想展示的图片通过网站(图片转ascii字符画)转成字符串,用console的%c格式化输出,注意键盘左上角的波浪线那个按键一起的``按键可以使换行的字符串被认为是同一个字符串的组成部分。
通过api获取随机的诗句
http://api.tianapi.com/qingshi/index?key=xxxxxx,方法为GET,返回json如下
'{"code":200,"msg":"success","newslist":[{"content":"离愁渐远渐无穷,迢迢不断如春水。","source":"踏莎行","author":"欧阳修"}]}'
何时插入开始按钮?
寻找你认为代表网页准备完成的那个元素,设置个定时器检测到这个元素就判断为网页加载完成
$(document).ready(function() { let ready = setInterval(function() { if (document.getElementsByClassName("m-top-box")[0]) { clearInterval(ready); //停止定时器 //创建"开始"按钮 createStartButton(); } }, 800); });
安装到浏览器
如何安装油猴脚本呢?参考教程https://www.cnblogs.com/jdq-123/p/16272121.html安装插件,然后参考“三、2.如何将本地的脚本文件配置至浏览器”把js内容复制进去保存
由于要对接油猴脚本,其中的设置css效果就可以使用官方提供的GM_addStyle方式进行注入,这也就是上面的代码之中的那些内容
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2018-10-16 斐讯K2 PSG1218 刷机教程 基于Breed互刷 清除配置