87 lines
3.9 KiB
Python
87 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""干净验证:报表页点下载 -> 捕获CDN直链 -> 页面内同步XHR取字节 -> base64回传 -> 落盘。"""
|
||
import json, urllib.request, base64, time, os
|
||
|
||
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):
|
||
return call("evaluate", {"code": code}).get("data", {}).get("value")
|
||
|
||
# 单一、幂等的 hook(navigate 后页面重载,hook 会被清)
|
||
HOOK = r"""(()=>{
|
||
if(window.__H)return 'already';
|
||
window.__H=1; window.__cap=[];
|
||
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|\.pdf/i.test(h)||d))
|
||
window.__cap.push(h);}catch(e){}
|
||
return ac.apply(this,arguments);
|
||
};
|
||
return 'hooked';
|
||
})()"""
|
||
|
||
CLICK_DL = r"""(()=>{const cand=['直接下载','下载','生成excel','导出Excel','导出'];
|
||
for(const t of cand){const b=[...document.querySelectorAll('button,a,[role=button]')]
|
||
.find(x=>(x.innerText||'').trim()===t && x.getBoundingClientRect().width>0);
|
||
if(b){b.click();return t;}}return 'no-btn';})()"""
|
||
|
||
# 弹窗里确认导出(选择部门后 生成excel/确定/下载)
|
||
CONFIRM = r"""(()=>{const cand=['生成excel','确定','下载','导出','确认'];
|
||
const ms=[...document.querySelectorAll('[class*=modal],[class*=Modal],[role=dialog],[class*=dialog],[class*=drawer]')];
|
||
for(const m of ms){const r=m.getBoundingClientRect();
|
||
if(r.width>200&&r.height>100&&getComputedStyle(m).display!=='none'){
|
||
for(const t of cand){const b=[...m.querySelectorAll('button,a')]
|
||
.find(x=>(x.innerText||'').trim()===t);
|
||
if(b){b.click();return 'modal:'+t;}}}}return 'no-modal';})()"""
|
||
|
||
FETCH_B64 = r"""(()=>{try{
|
||
if(!window.__cap.length)return 'NO_HREF';
|
||
const u=window.__cap[window.__cap.length-1];
|
||
const x=new XMLHttpRequest();x.open('GET',u,false);
|
||
x.overrideMimeType('text/plain;charset=x-user-defined');x.send();
|
||
if(x.status!==200)return 'HTTP'+x.status;
|
||
const s=x.responseText;let out='';const CH=8192;
|
||
for(let i=0;i<s.length;i+=CH){let c='';const e=Math.min(i+CH,s.length);
|
||
for(let j=i;j<e;j++)c+=String.fromCharCode(s.charCodeAt(j)&0xff);out+=c;}
|
||
window.__b64=btoa(out);
|
||
const m=u.match(/attname=([^&]+)/);let name=m?decodeURIComponent(m[1]):'file';
|
||
return JSON.stringify({size:s.length,name:name,url:u.slice(0,60)});
|
||
}catch(e){return 'EX '+e.message;}})()"""
|
||
|
||
def main():
|
||
call("find_tab", {"url": "2haohr.com"})
|
||
call("navigate", {"url": "https://2haohr.com/personnel/employee/personnel-report/home", "newTab": False})
|
||
# 轮询等下载按钮出现
|
||
COUNT = r"""(()=>{let n=0;document.querySelectorAll('button,a,[role=button]').forEach(x=>{const t=(x.innerText||'').trim();if(['直接下载','下载','生成excel','导出Excel','导出'].includes(t)&&x.getBoundingClientRect().width>0)n++;});return n;})()"""
|
||
for i in range(15):
|
||
time.sleep(2)
|
||
c = ev(COUNT)
|
||
print(" wait btn count=", c)
|
||
if isinstance(c, int) and c > 0:
|
||
break
|
||
print("hook:", ev(HOOK))
|
||
print("click:", ev(CLICK_DL))
|
||
time.sleep(2.5)
|
||
print("confirm:", ev(CONFIRM))
|
||
time.sleep(2.5)
|
||
meta = ev(FETCH_B64)
|
||
print("fetch:", meta)
|
||
if isinstance(meta, str) and meta.startswith("{"):
|
||
info = json.loads(meta)
|
||
b64 = ev("window.__b64")
|
||
data = base64.b64decode(b64)
|
||
os.makedirs("downloads/_test", exist_ok=True)
|
||
out = "downloads/_test/probe_" + info["name"]
|
||
open(out, "wb").write(data)
|
||
print("落盘:", out, len(data), "bytes")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|