"""广东省教师继续教育平台抓包工具(一次性取证用,不参与运行)。 用法: python script/capture-jsglpt.py [分钟数] 行为:开一个有头 Chromium(持久 profile,登录态会保留),打开 jsglpt 登录页, 把你在窗口里操作期间产生的 XHR/fetch 全量落到 data/capture-jsglpt-<时间>.jsonl, 同时每 3 秒把 cookie 快照写到 data/capture-jsglpt-cookies.json。 """ import json import os import sys import time from playwright.sync_api import sync_playwright ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DATA = os.path.join(ROOT, "data") LOGIN = "https://jsglpt.gds.edu.cn/login.jsp" MINUTES = float(sys.argv[1]) if len(sys.argv) > 1 else 40 os.makedirs(DATA, exist_ok=True) STAMP = time.strftime("%Y%m%d-%H%M%S") JSONL = os.path.join(DATA, "capture-jsglpt-%s.jsonl" % STAMP) COOKIES = os.path.join(DATA, "capture-jsglpt-cookies.json") PROFILE = os.path.join(DATA, ".gd-profile") out = open(JSONL, "a", encoding="utf-8") seen = 0 def emit(rec): global seen seen += 1 rec["n"] = seen out.write(json.dumps(rec, ensure_ascii=False) + "\n") out.flush() print(" %-4s %s %s -> %s" % (rec.get("rt", "doc"), rec.get("method", ""), rec.get("url", "")[:150], rec.get("status", "")), flush=True) def on_response(r): try: req = r.request rt = req.resource_type if rt not in ("xhr", "fetch", "document"): return h = {} try: h = dict(r.headers or {}) except Exception: pass body = "" # 只留文本类响应,视频/图片跳过 if not (r.url or "").lower().split("?")[0].endswith((".mp4", ".m3u8", ".ts", ".png", ".jpg", ".gif", ".woff", ".woff2", ".css", ".js")): try: body = r.text()[:8000] except Exception: body = "" emit({ "t": time.strftime("%H:%M:%S"), "rt": rt, "method": req.method, "url": r.url, "req_headers": dict(req.headers or {}), "req": (req.post_data or "")[:4000], "status": r.status, "resp_headers": h, "resp": body, }) except Exception: pass def main(): print("抓包输出:%s" % JSONL, flush=True) with sync_playwright() as p: ctx = p.chromium.launch_persistent_context( PROFILE, headless=False, args=["--disable-blink-features=AutomationControlled"], viewport={"width": 1440, "height": 900}, ) hooked = set() def hook(page): if id(page) in hooked: return hooked.add(id(page)) page.on("response", on_response) print("[页面] %s" % (page.url or "about:blank"), flush=True) ctx.on("page", hook) pg = ctx.pages[0] if ctx.pages else ctx.new_page() hook(pg) try: pg.goto(LOGIN, wait_until="domcontentloaded", timeout=60000) except Exception as e: print("打开登录页失败:%s" % e, flush=True) last = 0.0 t0 = time.time() while time.time() - t0 < MINUTES * 60: try: now = time.time() if now - last > 3: last = now with open(COOKIES, "w", encoding="utf-8") as f: json.dump(ctx.cookies(), f, ensure_ascii=False, indent=1) ctx.pages[0].wait_for_timeout(1000) except Exception: # 用户把所有页面关掉了,收工 break try: with open(COOKIES, "w", encoding="utf-8") as f: json.dump(ctx.cookies(), f, ensure_ascii=False, indent=1) except Exception: pass try: ctx.close() except Exception: pass out.close() print("抓包结束:%d 条 → %s" % (seen, JSONL), flush=True) if __name__ == "__main__": main()