feat: 接入广东公需课平台(jsglpt 登录 + jsxx 进度心跳)
- 新增 platforms/gdgxk.py:base64 登录 → ssoItlmsGxkTicket 换票进 jsxx → /uc/store/courseRegister 取课程 → updateViewTime1 心跳上报进度(无需真播) - platforms/__init__.py 注册;配套抓包脚本与自测脚本 - CHANGELOG / OPTIMIZATION / todo 同步,版本升到 0.0.2
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
"""广东省教师继续教育平台抓包工具(一次性取证用,不参与运行)。
|
||||
|
||||
用法:
|
||||
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 = "<binary>"
|
||||
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()
|
||||
Reference in New Issue
Block a user