以下の csv_find_result() の通りです。 client(ブラウザ)のみで、ダウンロード機能を実現できます。
参考url https://samehack.com/javascript-csv-download/
'use strict'; const cgi_base_url = "./find_hinban.cgi?"; const scan_base_url = "http://xcan.sexy.co.jp/xcan.xcanplan?type=viewzumen&XCANCODE="; class FindHinban { constructor() {} init_window =()=> { document.querySelector("#find_btn").addEventListener('click',()=>{ this.find_hinban(); }); document.querySelector("#find_str").addEventListener('keypress',(e)=>{ if (e.keyCode == 13) { // ENTER this.find_hinban(); } }); document.querySelector("#csv_btn").addEventListener('click',()=>{ this.csv_find_result(); }); } csv_find_result=()=>{ let tbody_trs = document.querySelectorAll("#find_result tbody tr"); if( tbody_trs.length==0 ){ this.toast_msg("検索結果がある状態でご利用ください"); return; } let csv_rows = []; for(let tr_elm of tbody_trs ){ let tr_cols = tr_elm.textContent.trim().split("\n"); csv_rows.push( [ '"'+tr_cols[1].trim()+'"', '"'+tr_cols[2].trim()+'"', '"'+tr_cols[3].trim()+'"', '"'+tr_cols[4].trim()+'"', '"'+tr_cols[5].trim()+'"' ].join(",") ); } let csv_rows_str = csv_rows.join("\r\n"); // c.f. https://samehack.com/javascript-csv-download/ const bom = new Uint8Array([0xef,0xbb,0xbf]); const blob = new Blob([bom, csv_rows_str], {type:"text/csv"}); const objectUrl = URL.createObjectURL(blob); const downloadLink = document.createElement("a"); const fileName = "検索結果.csv"; downloadLink.download = fileName; downloadLink.href = objectUrl; downloadLink.click(); downloadLink.remove(); } find_hinban=()=>{ let find_str = document.querySelector("#find_str").value; find_str = this.validate_find_str(find_str); if(! find_str ){ this.toast_msg( "検索キーワードは、品番やその一部(英数字 3~7文字)を"+ "空白区切りで10コまでです"); return; } let zumen_type = document.querySelector("#zumen_type").value.trim(); let req_params = {"zumen_type":zumen_type, "find_str":find_str }; let req_params_str = new URLSearchParams(req_params); let req_url = cgi_base_url + req_params_str; let img_container = document.querySelector("#loading_container img"); img_container.className = img_container.className.replace("hide",""); fetch(req_url,{method:"GET", cache:"no-cache"}) .then((res)=>{ if(!res.ok){ img_container.className = "hide"; throw new Error(`${res.status} ${res.statusText}`); } return res.text(); }) .then((res_tsvs)=>{ let tmp_container = document.querySelector("#tmp_container"); this.disp_find_result(res_tsvs); img_container.className = "hide"; }) .catch((reason)=>{ alert(reason); }); } disp_find_result=(res_tsvs)=>{ let tbody_elm = document.querySelector("#find_result tbody"); var clone_tbody = tbody_elm.cloneNode(false); tbody_elm.parentNode.replaceChild(clone_tbody, tbody_elm); tbody_elm = document.querySelector("#find_result tbody"); let tsvs = res_tsvs.split("\n"); if ( res_tsvs.length == 0){ this.toast_msg("検索キーワードに一致するものは見つかりませんでした"); return; } let tr_tmpl = document.querySelector("#tbody_tr"); let i = 1; for(let tsv of tsvs){ let disp_cols = tsv.split("\t"); var tr_elm = tr_tmpl.content.cloneNode(true); var td = tr_elm.querySelectorAll("td"); td[0].textContent = i++; td[1].textContent = disp_cols[0]; td[2].textContent = disp_cols[1]; td[3].textContent = disp_cols[2]; td[4].textContent = disp_cols[3]; td[5].textContent = disp_cols[4].substring(2); tbody_elm.appendChild(tr_elm); } let btn_elms = document.querySelectorAll("#find_result tbody button"); for(let btn_elm of btn_elms ){ btn_elm.addEventListener("click",(e)=>{ this.view_scan(e); }); } } view_scan=(e)=>{ let tr_elm = e.target.parentNode.parentNode; let td_elms = tr_elm.querySelectorAll("td"); let zuban = td_elms[1].textContent.trim(); let req_url = scan_base_url + zuban; location.href = req_url; } toast_msg=(msg)=>{ let msg_container = document.querySelector("#toast_msg"); msg_container.innerHTML = msg; msg_container.className = msg_container.className.replace("hide",""); setTimeout(()=>{ msg_container.className="hide";},4500); } validate_find_str=(org_find_str)=>{ org_find_str = org_find_str.trim(); if ( org_find_str.length == 0 ){ return undefined; } org_find_str = this.to_upper_hankaku(org_find_str); let re_pat = /([\dA-Z]{3,7})/g; let match; let find_strs = []; while ((match = re_pat.exec(org_find_str))!== null) { find_strs.push(match[1]); } if(find_strs.length < 1 || find_strs.length > 10){ return undefined; } return find_strs.join(" "); } to_upper_hankaku=(str)=> { let tmp_str = str.replace(/[A-Za-z0-9]/g, function(s) { return String.fromCharCode(s.charCodeAt(0) - 0xFEE0); }); return tmp_str.toUpperCase(); } } let find_hinban = new FindHinban(); find_hinban.init_window();