Files
twoJob-demo/scripts/_probe2.py
T

80 lines
4.3 KiB
Python
Raw Normal View History

2026-09-03 15:35:23 +08:00
#!/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()