"""抓包:把页面 XHR/fetch 落成 data/capture-*.json。""" import json import os import time from study_tool import store from study_tool.config import * from study_tool.runtime import RUNS, log # ------------------------------------------------------- 抓包(排查平台行为) CAPTURES = {} def capture_worker(key, cookies, minutes=10): """开一个有头浏览器,把这期间所有 /train/** 请求和响应记下来。 用于搞不清「某个状态到底由哪一发请求写成」:手动走一遍(点课程 → 播几秒 → 交评价), 再看抓到的接口和响应,就能定位该自动补哪一步。 """ try: from playwright.sync_api import sync_playwright except Exception as e: log(key, "抓包需要 playwright:%s" % e) return recs = CAPTURES.setdefault(key, []) log(key, "抓包开始:浏览器里手动走一遍(点课程 → 播几秒 → 交评价)," "完事点「停止抓包」,最多 %d 分钟" % minutes) def on_resp(r): try: if r.request.resource_type not in ("xhr", "fetch"): return ct = (r.headers or {}).get("content-type", "") body = "" if "json" in ct: try: body = r.text()[:4000] except Exception: body = "" recs.append({"t": time.strftime("%H:%M:%S"), "method": r.request.method, "url": r.url, "req": (r.request.post_data or "")[:1000], "status": r.status, "resp": body}) u = urlsplit(r.url) log(key, " · %s %s%s → %s" % (r.request.method, u.netloc, u.path, r.status)) except Exception: pass try: with sync_playwright() as p: b = p.chromium.launch(headless=False) ctx = b.new_context() for c in cookies: if "dgjy" in (c.get("domain") or ""): try: ctx.add_cookies([{"name": c["name"], "value": c.get("value") or "", "domain": (c.get("domain") or "").lstrip("."), "path": c.get("path") or "/"}]) except Exception: pass # 平台点课程/视频是 window.open 新开标签,必须把新页面也挂上监听,否则只抓到第一页 hooked = set() def hook(page): if id(page) in hooked: return hooked.add(id(page)) page.on("response", on_resp) ctx.on("page", hook) pg = ctx.new_page() hook(pg) # /training/ 根路径没有路由,会落到 404;直接开「我的培训」列表页 pg.goto(STUDY + "/training/myTrain", wait_until="domcontentloaded", timeout=60000) t0 = time.time() while time.time() - t0 < minutes * 60: if (RUNS.get(key) or {}).get("capture_stop"): break pg.wait_for_timeout(1000) except Exception as e: log(key, "抓包异常:%s" % str(e)[:150]) finally: path = "" if recs: try: path = os.path.join(store.DATA_DIR, "capture-%s-%s.json" % (key, time.strftime("%Y%m%d-%H%M%S"))) with open(path, "w", encoding="utf-8") as f: json.dump(recs, f, ensure_ascii=False, indent=1) except Exception as e: log(key, "抓包结果保存失败:%s" % e) set_state(key, capturing=False, capture_stop=False, capture_file=path) log(key, "抓包结束:%d 条请求%s" % (len(recs), (",已存 " + path) if path else ""))