first commit
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
遍历全部顶导航模块, 映射每个模块的侧边栏功能结构。
|
||||
含随机 5-15s 点击间隔(反爬)。结果写入 db/menu_map.json, 进度写 db/map_progress.log
|
||||
"""
|
||||
import json, time, random, os, urllib.request
|
||||
|
||||
DAEMON = "http://127.0.0.1:10086/command"
|
||||
SESSION = "2haohr"
|
||||
BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
OUT = os.path.join(BASE, "db", "menu_map.json")
|
||||
LOG = os.path.join(BASE, "db", "map_progress.log")
|
||||
|
||||
# (模块名, 是否在"更多"下拉里, 备注)
|
||||
MODULES = [
|
||||
("工作台", 0), ("人事", 0), ("组织", 0), ("招聘", 0), ("考勤假期", 0), ("薪酬", 0),
|
||||
("审批", 1), ("培训学习", 1), ("绩效", 1), ("社保公积金", 1), ("行政管理", 1),
|
||||
("电子合同", 1), ("背景调查", 1), ("人才测评", 1), ("员工福利", 1), ("人事仪表盘", 1),
|
||||
("第三方对接", 1), ("培训商城", 1), ("短信发送", 1), ("电子工资条", 1), ("智能薪酬", 1),
|
||||
("个税对接", 1), ("AI员工助手", 1), ("应用管理", 1),
|
||||
]
|
||||
|
||||
def call(action, args):
|
||||
body = json.dumps({"action": action, "args": args, "session": SESSION}).encode()
|
||||
req = urllib.request.Request(DAEMON, data=body, headers={"Content-Type": "application/json"})
|
||||
with urllib.request.urlopen(req, timeout=45) as resp:
|
||||
return json.loads(resp.read().decode())
|
||||
|
||||
def ev(code):
|
||||
try:
|
||||
return call("evaluate", {"code": code}).get("data", {}).get("value")
|
||||
except Exception as e:
|
||||
return "ERR:" + str(e)
|
||||
|
||||
CLICK_TOP = """
|
||||
(()=>{const name=%s;
|
||||
const els=[...document.querySelectorAll("a,span,div,li")].filter(e=>{
|
||||
const t=(e.innerText||"").trim();const r=e.getBoundingClientRect();
|
||||
return t===name && r.top<60 && r.width>8 && r.width<160;});
|
||||
if(els.length){els[0].click();return "top-clicked";}return "not-found";})()
|
||||
"""
|
||||
OPEN_MORE = """
|
||||
(()=>{const els=[...document.querySelectorAll("a,span,div,li")].filter(e=>{
|
||||
const t=(e.innerText||"").trim();const r=e.getBoundingClientRect();
|
||||
return t==="更多" && r.top<60 && r.width>8 && r.width<160;});
|
||||
if(!els.length)return "no-more"; const el=els[0];
|
||||
["pointerover","mouseover","mouseenter","pointerenter"].forEach(tp=>el.dispatchEvent(new MouseEvent(tp,{bubbles:true,cancelable:true,view:window})));
|
||||
el.click(); return "more-hover";})()
|
||||
"""
|
||||
CLICK_ANY = """
|
||||
(()=>{const name=%s;
|
||||
const els=[...document.querySelectorAll("a,span,div,li")].filter(e=>(e.innerText||"").trim()===name && e.getBoundingClientRect().width>=0);
|
||||
if(els.length){els[els.length-1].click();return "clicked";}return "not-found";})()
|
||||
"""
|
||||
EXTRACT = r"""
|
||||
(()=>{
|
||||
const root=document.querySelector("[class*=leftMenu]")||document.querySelector("[class*=menu_]");
|
||||
const groups=[]; let cur={title:"",items:[]}; groups.push(cur); const seen=new Set();
|
||||
if(root){const walk=(el)=>{for(const c of el.children){const cls=(c.className||"").toString();
|
||||
if(/nameTitle/.test(cls)&&!c.closest("a")){const t=(c.innerText||"").trim();if(t){cur={title:t,items:[]};groups.push(cur);}}
|
||||
if(c.tagName==="A"){const h=c.getAttribute("href")||"";const t=(c.innerText||"").trim().replace(/\s+/g," ");
|
||||
if(h&&h.length>1&&t&&!seen.has(h)){seen.add(h);cur.items.push({name:t,href:h});}}
|
||||
walk(c);}}; walk(root);}
|
||||
return JSON.stringify({loc:location.href,groups:groups.filter(g=>g.items.length)});
|
||||
})()
|
||||
"""
|
||||
|
||||
def log(msg):
|
||||
line = time.strftime("%H:%M:%S ") + msg
|
||||
print(line, flush=True)
|
||||
with open(LOG, "a") as f:
|
||||
f.write(line + "\n")
|
||||
|
||||
def main():
|
||||
results = {}
|
||||
if os.path.exists(OUT):
|
||||
results = json.load(open(OUT))
|
||||
for i, (mod, in_more) in enumerate(MODULES):
|
||||
if mod in results and results[mod].get("groups"):
|
||||
log(f"skip {mod} (already mapped)"); continue
|
||||
delay = random.uniform(5, 15)
|
||||
log(f"[{i+1}/{len(MODULES)}] wait {delay:.1f}s -> {mod}")
|
||||
time.sleep(delay)
|
||||
name_js = json.dumps(mod, ensure_ascii=False)
|
||||
if in_more:
|
||||
ev(OPEN_MORE); time.sleep(1.5); r = ev(CLICK_ANY % name_js)
|
||||
else:
|
||||
r = ev(CLICK_TOP % name_js)
|
||||
if r == "not-found":
|
||||
ev(OPEN_MORE); time.sleep(1.5); r = ev(CLICK_ANY % name_js)
|
||||
time.sleep(3.2)
|
||||
data = ev(EXTRACT)
|
||||
try:
|
||||
parsed = json.loads(data)
|
||||
except Exception:
|
||||
parsed = {"loc": "", "groups": [], "raw": str(data)[:200]}
|
||||
parsed["in_more"] = in_more
|
||||
parsed["click"] = r
|
||||
results[mod] = parsed
|
||||
n = sum(len(g["items"]) for g in parsed.get("groups", []))
|
||||
log(f" {mod}: loc={parsed.get('loc','')} groups={len(parsed.get('groups',[]))} items={n} click={r}")
|
||||
json.dump(results, open(OUT, "w"), ensure_ascii=False, indent=1)
|
||||
log("DONE all modules")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user