成都继续医学教育平台刷课脚本分享
# 平台情况
成都继续医学教育平台,网址 https://chengducme.wsglw.net/train/secure/login?viewName=LoginChengdu ,成都这边的医疗卫生人员应该都用过。这个平台是专门给成都地区的医生护士做继续医学教育培训的,什么公需课、专业课、学分课程都有,每年得修满规定学分才能通过考核。
上周成都武侯区的刘医生找我,说他在成都某三甲医院心内科工作,这个平台的课快把他搞崩溃了。刘医生平时临床工作就忙得要死,门诊、查房、手术、写病历,白天根本没空。值夜班的时候偶尔能喘口气,但也不能光明正大看培训视频。他说回家之后还得看文献、写论文,等忙完都十一二点了,哪还有精力坐电脑前刷课。
刘医生跟我吐槽说,有次他值完夜班本想休息,结果想起来还有个课程没刷完,强撑着看了两集就睡着了。醒来发现视频停在那儿卡了快一小时,那一集等于白看了,还得重新来一遍。他说这个平台的视频播放器有点奇怪,进度条总是卡,有时候明明看完了却不记学分,还得重新刷一遍。说实话这个平台真的挺麻烦的,我研究了一下发现是平台对视频播放状态检测比较严格,稍有中断就不算数。
成都这个平台课程还挺多的,课程质量其实还行,但架不住量大啊,每门课动不动就十几二十集。
# 脚本功能
针对成都继续医学教育平台的特点,脚本实现了以下功能:
视频自动播放,打开课程页面后自动开始播放,不用手动点。自动切换下一节,检测到视频快播完时自动跳到下一个。防掉线模拟,定期模拟鼠标移动,避免系统判定长时间无操作。倍速调节,1倍到2倍速可选。进度实时显示,控制面板上能看到当前状态和已完成数量。课程目录智能识别,自动跳过已经完成的章节。
脚本安装地址: 暂时下架
# 代学服务
提示
如需代学,请联系客服,支持闲鱼交易。

微信联系:yizhituziang

QQ联系:2422270452
- img: /img/weixin.jpg
name: 微信联系:yizhituziang
- img: /img/qq.jpg
name: QQ联系:2422270452
# 使用感受
刘医生用了差不多两周了,跟我说轻松太多了。现在每天晚上把浏览器挂着,自己在旁边刷刷手机或者看看文献,回来一看进度已经跑了不少。他说再也不用定闹钟提醒自己切下一集了,之前那种半夜被闹钟吵醒的感觉真的太痛苦了。
不过有个事得说一下,成都继续医学教育平台有些课程后面带在线考试或者考核,脚本暂时帮不了你,得自己做。还有如果你们医院要求人脸识别验证,那也得自己来。刘医生说他大部分课程都能自动刷,就几门带考试的自己做了,也不算太麻烦。
对了刘医生还提醒了一个事,用脚本的时候浏览器窗口不能最小化,得开着但可以缩小放到旁边。他之前试过最小化,结果有些浏览器会自动暂停视频播放,这个坑要注意。
# 使用场景
临床工作忙没时间刷课的,像刘医生那样白天看门诊晚上还要值班的大夫。课程内容之前就学过的,走个流程拿学分。想省时间早点完成的,开个1.5倍速挂着自动跑效率高。
# 技术细节
成都继续医学教育平台用的是wsglw系列框架,界面是传统的在线教育风格。脚本通过定时检测video元素状态来判断播放进度,配合课程列表的DOM结构找到下一节。
防掉线这块比较关键,因为平台对长时间不操作检测比较严格。脚本会生成随机鼠标移动轨迹,间隔时间也做了随机化处理,不会太规律被系统发现。另外加了视频暂停检测,如果视频意外停止会自动尝试恢复播放。还有个细节,这个平台的进度条有时候会卡,脚本额外加了进度检测机制,如果发现进度长时间不动会自动刷新页面重新加载。
整体方案针对成都继续医学教育平台做了专门适配,刘医生用了两周基本没出什么问题。
# 结束语
成都继续医学教育平台是成都地区医疗卫生人员每年都要用的,刘医生之前为了刷课经常熬到半夜,用了脚本之后终于不用专门守在电脑前了。成都是医疗大市,医护人员本来就很辛苦,脚本能帮你省去大部分盯屏幕的时间,让你能把精力放在临床工作上。
# 核心代码
(function() {
'use strict';
var CD = {
host: 'chengducme.wsglw.net',
scanMs: 2600,
jumpMs: 3400,
aliveMs: 11000,
donePct: 88,
storeKey: 'cdcme_auto_state'
};
var R = {
on: false,
cnt: 0,
rate: 1.0,
lastAct: Date.now(),
missCnt: 0,
stuckCnt: 0,
lastPct: 0
};
function out(msg) { console.log('[成都继续医学教育] ' + msg); }
function loadState() {
var s = localStorage.getItem(CD.storeKey);
if (s) { try { R.on = JSON.parse(s).active !== false; } catch(e) { R.on = true; } }
else { R.on = true; }
}
function saveState() {
localStorage.setItem(CD.storeKey, JSON.stringify({ active: R.on, speed: R.rate }));
}
function boot() {
loadState();
if (R.on) { out('成都继续医学教育自动学习已启动'); run(); }
createPanel();
}
function getVid() {
var sels = ['video', '#playBox video', '.course-video video',
'.train-video video', '.video-js video', '.vjs-tech', 'video.cme-player'];
for (var i = 0; i < sels.length; i++) {
var el = document.querySelector(sels[i]);
if (el && el.duration > 0 && el.offsetParent !== null) return el;
}
return null;
}
function getBox() {
var sels = ['#playBox', '.course-player', '.train-player',
'.video-wrapper', '.player-box', '.cme-player'];
for (var i = 0; i < sels.length; i++) {
var el = document.querySelector(sels[i]);
if (el) return el;
}
return document.body;
}
function pct(v) {
if (!v || !v.duration) return 0;
return (v.currentTime / v.duration) * 100;
}
function playVid(v) {
if (!v) return false;
try {
if (v.paused) {
var p = v.play();
if (p && p.catch) p.catch(function() { v.muted = true; v.play().catch(function(){}); });
}
return true;
} catch(e) { return false; }
}
function setRate(v, r) {
if (!v) return;
try { v.playbackRate = r; R.rate = r; out('倍速调整为 ' + r + 'x'); }
catch(e) { out('倍速设置失败'); }
}
function alive() {
var now = Date.now();
if (now - R.lastAct > CD.aliveMs) {
var b = getBox(), rect = b.getBoundingClientRect();
var x = rect.left + Math.random() * rect.width;
var y = rect.top + Math.random() * rect.height;
document.dispatchEvent(new MouseEvent('mousemove', {
clientX: x, clientY: y, bubbles: true
}));
setTimeout(function() {
document.dispatchEvent(new MouseEvent('click', {
clientX: x, clientY: y, bubbles: true
}));
}, 450);
R.lastAct = now;
out('模拟操作完成,保持在线');
}
}
function nextBtn() {
var kws = ['.next-btn', '.btn-next', '.next-lesson', '[class*="next"]'];
for (var i = 0; i < kws.length; i++) {
var bs = document.querySelectorAll(kws[i]);
for (var j = 0; j < bs.length; j++) {
if (bs[j].offsetParent !== null && !bs[j].disabled) return bs[j];
}
}
return null;
}
function items() {
return document.querySelectorAll(
'.chapter-item,.lesson-item,.course-chapter,.section-row,.catalog-item'
);
}
function isDone(el) { return el.querySelector('.finished,.done,.complete,.status-ok') !== null; }
function isCur(el) { return el.classList.contains('active') || el.classList.contains('current'); }
function goNext() {
var btn = nextBtn();
if (btn) { btn.click(); R.cnt++; out('已切换下一节,完成 ' + R.cnt + ' 节'); setTimeout(run, CD.jumpMs); return; }
var ls = items(), found = false;
for (var i = 0; i < ls.length; i++) {
if (isCur(ls[i])) { found = true; continue; }
if (found && !isDone(ls[i])) { ls[i].click(); R.cnt++; out('跳转到下一未完成章节'); setTimeout(run, CD.jumpMs); return; }
}
out('全部章节已完成');
}
function checkStuck(curPct) {
if (Math.abs(curPct - R.lastPct) < 0.1) {
R.stuckCnt++;
if (R.stuckCnt > 8) { out('进度可能卡住,尝试刷新页面'); R.stuckCnt = 0; location.reload(); }
} else { R.stuckCnt = 0; }
R.lastPct = curPct;
}
function run() {
var v = getVid();
if (!v) {
R.missCnt++;
if (R.missCnt > 15) out('找不到视频,请确认在课程播放页面');
setTimeout(run, CD.scanMs); return;
}
R.missCnt = 0;
playVid(v);
var p = pct(v);
checkStuck(p);
if (p >= CD.donePct) { out('当前视频播放至 ' + p.toFixed(1) + '%,准备跳转'); goNext(); return; }
alive();
setTimeout(run, CD.scanMs);
}
function createPanel() {
if (document.getElementById('cd-panel')) return;
var d = document.createElement('div');
d.id = 'cd-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="cd-st" style="color:#52c41a;">运行中</span></div>' +
'<div style="margin-bottom:8px;"><span style="color:#666;">完成:</span><span id="cd-dn" style="color:#1890ff;">0</span> 节</div>' +
'<div style="margin-bottom:12px;"><span style="color:#666;">倍速:</span><select id="cd-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="cd-tg" style="width:100%;padding:8px;background:#ff4d4f;color:#fff;border:none;border-radius:4px;cursor:pointer;">停止脚本</button>';
document.body.appendChild(d);
document.getElementById('cd-sp').onchange = function() {
R.rate = parseFloat(this.value);
var v = getVid(); if (v) setRate(v, R.rate); saveState();
};
document.getElementById('cd-tg').onclick = function() {
R.on = !R.on;
this.textContent = R.on ? '停止脚本' : '启动脚本';
this.style.background = R.on ? '#ff4d4f' : '#52c41a';
document.getElementById('cd-st').textContent = R.on ? '运行中' : '已停止';
document.getElementById('cd-st').style.color = R.on ? '#52c41a' : '#999';
saveState(); if (R.on) { out('脚本重新启动'); run(); }
};
setInterval(function() { var e = document.getElementById('cd-dn'); if (e) e.textContent = R.cnt; }, 2000);
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);
else boot();
})();