汶上县委党校公需课刷课脚本分享
# 平台情况
汶上县委党校专业技术人员继续教育平台,网址 https://wsdxzj.zpwedu.com/ ,济宁汶上这边在事业单位或者企业做技术工作的朋友应该都接触过。每年公需课专业课都得完成,学时不够影响职称评审和岗位聘用。
上个月济宁汶上县的小李微信找我,说他在汶上某单位上班,这个继续教育平台把他折腾得够呛。小李平时在单位忙各种业务,写材料、跑审批、对接上级部门,白天根本没空看视频。晚上回家还得做饭带娃,等安顿完都九点多了,哪还有心思坐电脑前刷课。
小李跟我吐槽说,有回他硬撑着看到十点多,中间接了个电话回来,发现视频暂停了,之前看的那个进度也没保存住,等于白看。他说这个平台的视频播放器有点奇怪,进度条总是卡,有时候明明看完了却不记学时,还得重新刷一遍。嗯...这个确实挺烦人的,我研究了一下发现是平台对视频播放状态检测比较严格,稍有中断就不算数。
汶上这边冬天也挺冷的,小李说有时候下班回家手都冻僵了,还得坐电脑前点鼠标刷课,想想就心酸。还好后来找到方法了,给他研究了个脚本。
# 脚本功能
针对汶上县委党校继续教育平台的特点,脚本实现了以下功能:
视频自动播放,打开课程页面后自动开始播放,不用手动点。自动切换下一节,检测到视频快播完时自动跳到下一个。防掉线模拟,定期模拟鼠标移动,避免系统判定长时间无操作。倍速调节,1倍到2倍速可选。进度实时显示,控制面板上能看到当前状态和已完成数量。课程目录智能识别,自动跳过已经完成的章节。
脚本安装地址: 暂时下架
# 代学服务
提示
如需代学,请联系客服,支持闲鱼交易。

微信联系:yizhituziang

QQ联系:2422270452
- img: /img/weixin.jpg
name: 微信联系:yizhituziang
- img: /img/qq.jpg
name: QQ联系:2422270452
# 使用感受
小李用了差不多两周了,跟我说轻松太多了。现在每天晚上把浏览器挂着,自己在旁边刷刷手机或者陪孩子,回来一看进度已经跑了不少。他说再也不用定闹钟提醒自己切下一集了,之前那种半夜被闹钟吵醒的感觉真的太痛苦了。
不过有个事得说一下,汶上这个平台有些课程后面带在线测验或者考试,脚本暂时帮不了你,得自己做。还有如果你们单位要求人脸识别验证,那也得自己来。小李说他大部分课程都能自动刷,就几门带测验的自己做了,也不算太麻烦。
对了小李还提醒了一个事,用脚本的时候浏览器窗口不能最小化,得开着但可以缩小放到旁边。他之前试过最小化,结果有些浏览器会自动暂停视频播放,这个坑要注意。
# 使用场景
白天在单位忙得脚不沾地的,像小李那样写材料开会连轴转的。课程内容之前就学过的,走个流程拿学时就行。想省时间早点完成任务的,开个1.5倍速挂着自动跑。
# 技术细节
汶上县委党校继续教育平台用的是zpwedu系列框架,界面是传统的在线教育风格。脚本通过定时检测video元素状态来判断播放进度,配合课程列表的DOM结构找到下一节。
防掉线这块比较关键,因为平台对长时间不操作检测比较严格。脚本会生成随机鼠标移动轨迹,间隔时间也做了随机化处理,不会太规律被系统发现。另外加了视频暂停检测,如果视频意外停止会自动尝试恢复播放。还有个细节,这个平台的进度条有时候会卡,脚本额外加了进度检测机制,如果发现进度长时间不动会自动刷新页面重新加载。
整体方案针对汶上平台做了专门适配,小李用了两周基本没出什么问题。
# 结束语
汶上县委党校继续教育平台是汶上地区专技人员每年都要用的,小李之前为了刷课经常熬到半夜,用了脚本之后终于不用专门守在电脑前了。山东这边冬天漫长,在家刷课的日子不好过,脚本能帮你省去大部分盯屏幕的时间,让你能把精力放在工作和生活上。
# 核心代码
(function() {
'use strict';
const WS = {
site: 'zpwedu.com',
interval: 2800,
switchDelay: 3500,
keepAlive: 15000,
threshold: 90,
storage: 'wsdx_auto_cfg'
};
let M = {
active: false,
done: 0,
speed: 1.0,
lastPulse: Date.now(),
noVid: 0,
stall: 0,
prevProgress: 0
};
function trace(msg) { console.log('[汶上党校] ' + msg); }
function restore() {
const raw = localStorage.getItem(WS.storage);
if (raw) { try { M.active = JSON.parse(raw).on !== false; } catch(e) { M.active = true; } }
else { M.active = true; }
}
function persist() {
localStorage.setItem(WS.storage, JSON.stringify({ on: M.active, speed: M.speed }));
}
function launch() {
restore();
if (M.active) { trace('汶上党校继续教育自动学习已启动'); watch(); }
renderUI();
}
function locateVideo() {
const paths = ['video', '#playerBox video', '.course-video video',
'.train-video video', '.video-js video', '.vjs-tech', 'video.zpwedu-player'];
for (const p of paths) {
const el = document.querySelector(p);
if (el && el.duration > 0 && el.offsetParent !== null) return el;
}
return null;
}
function locateWrap() {
const paths = ['#playerBox', '.course-player', '.train-player',
'.video-wrapper', '.player-box', '.zpwedu-player'];
for (const p of paths) {
const el = document.querySelector(p);
if (el) return el;
}
return document.body;
}
function calcPct(v) {
if (!v || !v.duration) return 0;
return (v.currentTime / v.duration) * 100;
}
function autoPlay(v) {
if (!v) return false;
try {
if (v.paused) {
const p = v.play();
if (p && p.catch) p.catch(() => { v.muted = true; v.play().catch(() => {}); });
}
return true;
} catch(e) { return false; }
}
function adjustSpeed(v, r) {
if (!v) return;
try { v.playbackRate = r; M.speed = r; trace('倍速调整为 ' + r + 'x'); }
catch(e) { trace('倍速设置失败'); }
}
function simulateActivity() {
const now = Date.now();
if (now - M.lastPulse > WS.keepAlive) {
const wrap = locateWrap();
const rect = wrap.getBoundingClientRect();
const rx = rect.left + Math.random() * rect.width;
const ry = rect.top + Math.random() * rect.height;
document.dispatchEvent(new MouseEvent('mousemove', {
clientX: rx, clientY: ry, bubbles: true
}));
setTimeout(() => {
document.dispatchEvent(new MouseEvent('click', {
clientX: rx, clientY: ry, bubbles: true
}));
}, 460);
M.lastPulse = now;
trace('模拟操作完成,保持在线');
}
}
function findNextBtn() {
const qs = ['.next-btn', '.btn-next', '.next-chapter', '[class*="next"]'];
for (const q of qs) {
const btns = document.querySelectorAll(q);
for (const b of btns) {
if (b.offsetParent !== null && !b.disabled) return b;
}
}
return null;
}
function getChapters() {
return document.querySelectorAll(
'.chapter-item,.lesson-item,.course-section,.section-row,.catalog-node'
);
}
function isComplete(el) { return el.querySelector('.finished,.done,.complete,.pass') !== null; }
function isActive(el) { return el.classList.contains('active') || el.classList.contains('current'); }
function jumpNext() {
const btn = findNextBtn();
if (btn) { btn.click(); M.done++; trace('切换下一节,已完成 ' + M.done + ' 节'); setTimeout(watch, WS.switchDelay); return; }
const chs = getChapters();
let found = false;
for (const ch of chs) {
if (isActive(ch)) { found = true; continue; }
if (found && !isComplete(ch)) { ch.click(); M.done++; trace('跳转到下一未完成章节'); setTimeout(watch, WS.switchDelay); return; }
}
trace('所有章节已完成');
}
function detectStall(p) {
if (Math.abs(p - M.prevProgress) < 0.1) {
M.stall++;
if (M.stall > 9) { trace('进度可能卡住,刷新页面'); M.stall = 0; location.reload(); }
} else { M.stall = 0; }
M.prevProgress = p;
}
function watch() {
const v = locateVideo();
if (!v) {
M.noVid++;
if (M.noVid > 14) trace('找不到视频,请确认在课程页面');
setTimeout(watch, WS.interval); return;
}
M.noVid = 0;
autoPlay(v);
const p = calcPct(v);
detectStall(p);
if (p >= WS.threshold) { trace('播放至 ' + p.toFixed(1) + '%,准备跳转'); jumpNext(); return; }
simulateActivity();
setTimeout(watch, WS.interval);
}
function renderUI() {
if (document.getElementById('ws-panel')) return;
const d = document.createElement('div');
d.id = 'ws-panel';
d.style.cssText = 'position:fixed;top:120px;right:20px;width:210px;background:#fff;border-radius:8px;box-shadow:0 2px 12px rgba(0,0,0,0.15);padding:16px;z-index:99999;font-size:14px;';
d.innerHTML = '<div style="font-weight:bold;margin-bottom:12px;color:#333;">汶上党校自动刷课</div>' +
'<div style="margin-bottom:8px;"><span style="color:#666;">状态:</span><span id="ws-st" style="color:#52c41a;">运行中</span></div>' +
'<div style="margin-bottom:8px;"><span style="color:#666;">完成:</span><span id="ws-dn" style="color:#1890ff;">0</span> 节</div>' +
'<div style="margin-bottom:12px;"><span style="color:#666;">倍速:</span><select id="ws-sp" style="padding:2px 6px;border-radius:4px;"><option value="1">1倍速</option><option value="1.5" selected>1.5倍速</option><option value="2">2倍速</option></select></div>' +
'<button id="ws-tg" style="width:100%;padding:8px;background:#ff4d4f;color:#fff;border:none;border-radius:4px;cursor:pointer;">停止脚本</button>';
document.body.appendChild(d);
document.getElementById('ws-sp').onchange = function() {
M.speed = parseFloat(this.value);
const v = locateVideo(); if (v) adjustSpeed(v, M.speed); persist();
};
document.getElementById('ws-tg').onclick = function() {
M.active = !M.active;
this.textContent = M.active ? '停止脚本' : '启动脚本';
this.style.background = M.active ? '#ff4d4f' : '#52c41a';
document.getElementById('ws-st').textContent = M.active ? '运行中' : '已停止';
document.getElementById('ws-st').style.color = M.active ? '#52c41a' : '#999';
persist(); if (M.active) { trace('脚本重新启动'); watch(); }
};
setInterval(() => { const e = document.getElementById('ws-dn'); if (e) e.textContent = M.done; }, 2000);
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', launch);
else launch();
})();