#!/usr/bin/env python3 # -*- coding: utf-8 -*- """代码级探测:在代表性报表/文档页扫描下载入口的 DOM 特征,并观察点击后的生成过程。 不落盘,只输出发现,用于设计正式下载器。""" import json, urllib.request, time D = "http://127.0.0.1:10086/command"; S = "2haohr" def call(a, args, timeout=120): b = json.dumps({"action": a, "args": args, "session": S}).encode() r = urllib.request.Request(D, data=b, headers={"Content-Type": "application/json"}) return json.loads(urllib.request.urlopen(r, timeout=timeout).read().decode()) def ev(code, timeout=120): try: return call("evaluate", {"code": code}, timeout).get("data", {}).get("value") except Exception as e: return "EXC:" + str(e)[:80] # 增强 hook:捕获 a.click / window.open / location 赋值 / blob URL / fetch(下载类) HOOK = r"""(()=>{ window.__cap=[]; window.__evt=[]; const push=(t,v)=>{try{window.__cap.push({t:t,v:(''+v).slice(0,220)});}catch(e){}}; const ac=HTMLAnchorElement.prototype.click; HTMLAnchorElement.prototype.click=function(){try{const h=this.href||'';const d=this.getAttribute('download')||''; if(h&&(/prv-cdn|attname=|\.xlsx|\.xls|\.docx|\.doc|\.zip|blob:/i.test(h)||d))push('a.click',h);}catch(e){} return ac.apply(this,arguments);}; const wo=window.open;window.open=function(u){if(u)push('open',u);return wo.apply(this,arguments);}; const oc=URL.createObjectURL;URL.createObjectURL=function(b){try{push('blob','size='+(b&&b.size)+' type='+(b&&b.type));}catch(e){}return oc.apply(this,arguments);}; const of=window.fetch;window.fetch=function(u,o){const s=''+(u&&u.url||u);if(/export|download|excel|report|generate|create.*task/i.test(s))push('fetch',s);return of.apply(this,arguments);}; const os=XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open=function(m,u){const s=''+u;if(/export|download|excel|report|generate/i.test(s))push('xhr',s);return os.apply(this,arguments);}; return 'hooked'; })()""" # 代码级扫描:不依赖可见文字,扫 title/aria-label/class/svg use/data-* SCAN = r"""(()=>{ const KW=/下载|导出|excel|xlsx|导表|生成表|report|download|export/i; const out=[];const seen=new Set(); const els=document.querySelectorAll('button,a,[role=button],i,span,div,[class*=icon],[class*=download],[class*=export],[class*=btn]'); els.forEach(el=>{ const t=(el.innerText||'').trim(); const g=n=>el.getAttribute?(el.getAttribute(n)||''):''; const title=g('title')||g('aria-label')||g('data-title')||g('alt'); let cls='';try{cls=(el.className&&el.className.baseVal!==undefined)?el.className.baseVal:(el.className||'');}catch(e){} const useEl=el.querySelector&&el.querySelector('use,svg'); let use='';if(useEl){use=useEl.getAttribute('xlink:href')||useEl.getAttribute('href')||useEl.getAttribute('data-icon')||'';} const hit=KW.test(t)||KW.test(title)||KW.test(''+cls)||KW.test(use); if(!hit)return; const r=el.getBoundingClientRect();if(r.width<=0||r.height<=0)return; const key=el.tagName+'|'+t.slice(0,8)+'|'+title.slice(0,10)+'|'+(''+cls).slice(0,24); if(seen.has(key))return;seen.add(key); out.push({tag:el.tagName,txt:t.slice(0,12),title:(''+title).slice(0,18),cls:(''+cls).slice(0,44),use:(''+use).slice(0,26),xy:[Math.round(r.x),Math.round(r.y)]}); }); return JSON.stringify(out.slice(0,50)); })()""" # 观察 toast/loading 文案(生成过程提示) TOAST = r"""(()=>{const a=[];document.querySelectorAll('[class*=message],[class*=Message],[class*=toast],[class*=Toast],[class*=notify],[class*=loading],[class*=Loading],[class*=tip]').forEach(el=>{const t=(el.innerText||'').trim();if(t&&t.length<40&&el.getBoundingClientRect().width>0)a.push(t);});return JSON.stringify([...new Set(a)].slice(0,10));})()""" def probe(url, label): print("\n" + "=" * 70) print("PAGE:", label, url) call("navigate", {"url": url, "newTab": False}) time.sleep(7) print("scan-entries:", ev(SCAN)) print("hook:", ev(HOOK)) def main(): call("find_tab", {"url": "2haohr.com"}) targets = [ ("https://2haohr.com/attendance/stat/monthly", "月考勤表(整表导出)"), ("https://2haohr.com/social-insurance/reports", "社保报表"), ("https://2haohr.com/oa/regulation", "企业制度(文档)"), ] for url, label in targets: probe(url, label) if __name__ == "__main__": main()