菏泽市继续教育刷课脚本分享
# 平台情况
菏泽市继续教育平台,网址 http://mh.shunjy.com:8090/index_l.html#/ ,山东菏泽这边专业技术人员应该都不陌生。这个平台是顺捷教育做的,登录页面就一个账号密码框加个注册和忘记密码的入口,挺简洁的。
前阵子菏泽牡丹区的孙哥微信上找我,说他快被继续教育搞疯了。孙哥在牡丹区一个建筑公司做项目管理,白天跑工地盯施工,晚上回来还得开电脑刷课。他说菏泽这边的继续教育要求还挺严格的,学时不够影响职称评审,但视频一个比一个长,手动刷太费劲了。
孙哥跟我讲,他有一天晚上从七点刷到十一点多,才完成了两门课。中间视频卡了好几次,还有一次掉线了重新登录发现进度没保存,又得从头看。那天他气得差点把鼠标摔了,嗯...我理解那种感觉。
我打开这个平台研究了一下,界面是那种比较现代的单页应用风格,左侧菜单右侧内容区。课程分类挺清楚的,公需课专业课都有。视频播放器用的是通用方案,但有个问题——进度条有时候不太准,你以为看完了其实还差一点。播完一节不会自动跳下一节,得手动点,长时间不操作也会掉线。
# 脚本功能
提示
如需代学,请联系客服,支持闲鱼交易。

微信联系:yizhituziang

QQ联系:2422270452
- img: /img/weixin.jpg
name: 微信联系:yizhituziang
- img: /img/qq.jpg
name: QQ联系:2422270452
针对菏泽市继续教育平台的特点,脚本实现了以下功能:
视频自动播放,打开课程页面后自动开始播放,不用手动点。自动切换下一节,视频快播完时自动跳到下一个章节。防掉线模拟,定期模拟鼠标移动和点击,避免系统判定长时间无操作。倍速调节,1倍到2倍速可选。进度实时显示,控制面板上能看到当前状态和已完成数量。自动重连,如果检测到视频异常停止会尝试重新播放。
脚本安装地址: 暂时下架
# 使用感受
孙哥用了快两周了,他跟我说比之前纯手动刷省心太多了。现在他每天出门前把浏览器打开挂着,中午休息的时候看一眼进度,晚上回来基本就刷完了。控制面板在右侧,运行状态、完成数量、倍速都显示得清清楚楚。
不过有个事得提醒,菏泽这个平台有些课带课后测验或者在线考试,脚本暂时帮不了这个,得自己做。还有如果你们单位要求人脸验证,那也得自己来。孙哥说他大部分课都能自动刷,就几门带测验的自己做了。
还有个小细节,孙哥说他之前用360浏览器有时候会出问题,后来换了Chrome就稳了。建议用Chrome或者Edge,兼容性最好。
# 使用场景
工地跑了一天晚上回来没精力看课的,像孙哥那样白天忙得脚不沾地的。课程内容之前就学过的,走个流程拿学时。想省时间早点刷完的,开倍速挂着自动跑效率高多了。
# 技术细节
菏泽这个平台用的是顺捷教育的框架,单页应用架构,播放器兼容性还行。脚本通过定时检测video元素状态来判断播放进度,配合课程列表DOM结构找到下一节。
防掉线这块比较关键,因为菏泽平台对长时间不操作检测比较敏感。脚本会生成随机鼠标移动轨迹,间隔时间做了随机化处理。另外还加了视频异常检测,如果视频意外停止会自动尝试恢复播放。
整体方案针对菏泽平台做了专门适配,孙哥用了两周基本没出过什么问题。
# 常见问题
脚本安装地址暂时下架,有需要代学的朋友看页面底部联系方式。
倍速开多少?建议1.5倍,菏泽这边网络还行但太快怕加载跟不上。
浏览器用什么?Chrome或Edge最稳,360要开极速模式。
进度没保存怎么办?刷新页面,平台会自动同步进度。
课后测验能自动做吗?不行,得自己看题目做。
# 结束语
菏泽市继续教育平台是菏泽地区专技人员每年都要打交道的,孙哥之前为了刷课好几个晚上没休息好,用了脚本之后终于不用熬夜了。山东这边的继续教育平台挺多的,基本都有视频长、不自动跳、容易掉线这些通病,脚本能帮你解决大部分问题。
# 核心代码
(function() {
'use strict';
const OPT = {
host: 'mh.shunjy.com',
tickMs: 2800,
jumpMs: 3500,
aliveMs: 12000,
endPct: 90,
storeKey: 'hzjy_auto_flag'
};
let ctx = {
alive: false,
done: 0,
rate: 1.0,
lastTick: Date.now(),
failCnt: 0
};
function log(m) {
console.log(`[菏泽继续教育] ${m}`);
}
function readStore() {
const raw = localStorage.getItem(OPT.storeKey);
if (raw) {
try {
const obj = JSON.parse(raw);
ctx.alive = obj.on !== false;
} catch (e) {
ctx.alive = true;
}
} else {
ctx.alive = true;
}
}
function writeStore() {
localStorage.setItem(OPT.storeKey, JSON.stringify({
on: ctx.alive,
rate: ctx.rate
}));
}
function main() {
readStore();
if (ctx.alive) {
log('菏泽市继续教育自动学习已启动');
watchLoop();
}
drawPanel();
}
function grabVideo() {
const sels = [
'video',
'#player video',
'.video-box video',
'.course-video video',
'.video-js video',
'.vjs-tech',
'video.xg-video'
];
for (const s of sels) {
const el = document.querySelector(s);
if (el && el.duration > 0 && el.offsetParent !== null) {
return el;
}
}
return null;
}
function grabBox() {
const sels = [
'#player',
'.video-box',
'.course-player',
'.video-container',
'.player-wrap',
'.xg-player'
];
for (const s of sels) {
const el = document.querySelector(s);
if (el) return el;
}
return document.body;
}
function progress(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 r = v.play();
if (r && r.catch) {
r.catch(() => {
v.muted = true;
v.play().catch(() => {});
});
}
}
return true;
} catch (e) {
return false;
}
}
function setRate(v, r) {
if (!v) return;
try {
v.playbackRate = r;
ctx.rate = r;
log(`倍速设为 ${r}x`);
} catch (e) {
log('倍速设置失败');
}
}
function keepAlive() {
const now = Date.now();
if (now - ctx.lastTick > OPT.aliveMs) {
const box = grabBox();
const rect = box.getBoundingClientRect();
const x = rect.left + Math.random() * rect.width;
const y = rect.top + Math.random() * rect.height;
document.dispatchEvent(new MouseEvent('mousemove', {
clientX: x, clientY: y, bubbles: true
}));
setTimeout(() => {
document.dispatchEvent(new MouseEvent('click', {
clientX: x, clientY: y, bubbles: true
}));
}, 550);
ctx.lastTick = now;
log('模拟操作,防掉线');
}
}
function nextLink() {
const sels = ['.next-btn', '.btn-next', '.next-chapter', '[class*="next"]'];
for (const s of sels) {
const btns = document.querySelectorAll(s);
for (const b of btns) {
if (b.offsetParent !== null && !b.disabled) return b;
}
}
return null;
}
function chapters() {
return document.querySelectorAll(
'.course-item, .chapter-item, .lesson-item, .section-item'
);
}
function isOver(el) {
return el.querySelector('.status-done, .finished, .completed, .done-icon') !== null;
}
function isNow(el) {
return el.classList.contains('active') || el.classList.contains('current');
}
function goNext() {
const btn = nextLink();
if (btn) {
btn.click();
ctx.done++;
log(`点击下一节,已完成 ${ctx.done} 节`);
setTimeout(watchLoop, OPT.jumpMs);
return;
}
const list = chapters();
let hit = false;
for (const el of list) {
const over = isOver(el);
const now = isNow(el);
if (now) { hit = true; continue; }
if (hit && !over) {
el.click();
ctx.done++;
log('跳到下一未完成章节');
setTimeout(watchLoop, OPT.jumpMs);
return;
}
}
log('全部完成或无更多课程');
}
function watchLoop() {
if (!ctx.alive) return;
const v = grabVideo();
if (!v) {
ctx.failCnt++;
if (ctx.failCnt > 30) {
log('多次未找到视频,请检查页面');
return;
}
setTimeout(watchLoop, OPT.tickMs);
return;
}
ctx.failCnt = 0;
if (v.paused && ctx.alive) autoPlay(v);
const p = progress(v);
if (p >= OPT.endPct || v.ended) {
log(`进度 ${p.toFixed(1)}%,准备切换`);
goNext();
return;
}
keepAlive();
setTimeout(watchLoop, OPT.tickMs);
}
function halt() {
ctx.alive = false;
writeStore();
log('已暂停');
syncPanel();
}
function go() {
ctx.alive = true;
writeStore();
log('已恢复');
watchLoop();
syncPanel();
}
function flip() {
ctx.alive ? halt() : go();
}
function spd(r) {
const v = grabVideo();
if (v) setRate(v, r);
ctx.rate = r;
syncPanel();
}
function syncPanel() {
const a = document.getElementById('hz_st');
const b = document.getElementById('hz_ct');
const c = document.getElementById('hz_sp');
if (a) a.textContent = ctx.alive ? '运行中' : '已暂停';
if (b) b.textContent = ctx.done;
if (c) c.textContent = ctx.rate;
}
function drawPanel() {
const old = document.getElementById('hz_panel');
if (old) return;
const d = document.createElement('div');
d.id = 'hz_panel';
d.style.cssText = `
position:fixed;top:160px;right:20px;
background:linear-gradient(135deg,#004d40 0%,#00897b 100%);
color:#fff;padding:14px 16px;border-radius:10px;
box-shadow:0 3px 12px rgba(0,0,0,0.25);
z-index:999999;font-size:13px;min-width:170px;
`;
d.innerHTML = `
<div style="font-weight:bold;margin-bottom:10px;font-size:14px;">
菏泽继续教育自动学习
</div>
<div style="margin-bottom:6px;">状态: <span id="hz_st">${ctx.alive ? '运行中' : '已暂停'}</span></div>
<div style="margin-bottom:6px;">已完成: <span id="hz_ct">${ctx.done}</span> 节</div>
<div style="margin-bottom:10px;">倍速: <span id="hz_sp">${ctx.rate}</span>x</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;">
<button onclick="window.togHz()" style="
padding:5px 10px;border:none;border-radius:5px;
cursor:pointer;background:rgba(255,255,255,0.25);
color:#fff;font-size:12px;
">${ctx.alive ? '暂停' : '开始'}</button>
<button onclick="window.spdHz(1.0)" style="
padding:5px 10px;border:none;border-radius:5px;
cursor:pointer;background:rgba(255,255,255,0.25);
color:#fff;font-size:12px;
">1x</button>
<button onclick="window.spdHz(1.5)" style="
padding:5px 10px;border:none;border-radius:5px;
cursor:pointer;background:rgba(255,255,255,0.25);
color:#fff;font-size:12px;
">1.5x</button>
<button onclick="window.spdHz(2.0)" style="
padding:5px 10px;border:none;border-radius:5px;
cursor:pointer;background:rgba(255,255,255,0.25);
color:#fff;font-size:12px;
">2x</button>
</div>
`;
document.body.appendChild(d);
window.togHz = flip;
window.spdHz = spd;
setInterval(syncPanel, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', main);
} else {
main();
}
})();