贵州执业药师继续教育在线培训刷课脚本分享
# 写在前面
上周贵阳的老周在后台留言,说他们那边药店老板要上"贵州执业药师继续教育在线培训",网址是 https://gzzyys-p.webtrn.cn/cms/ 。老周在市区开了家大药房,每年执业药师继续教育是必须要搞的。课程内容翻来覆去就那些,药事法规、临床药学、药品经营质量管理什么的,说重要也重要,但确实挺磨人。
老周说白天要看店理货,晚上回家累得不行还得完成培训任务,评职称要查学分。他问我有没有省时的办法。
我研究了一下这个平台,发现跟其他继续教育平台差不多——实名登录、看视频、做测试。视频内容多,他们那边几个药师加起来有几十个小时的课程要刷。我给他弄了个辅助脚本,让视频自动跑、遇到问题自动恢复、播完自动跳下一节。脚本安装地址目前显示暂时下架,有需要的看页面底部联系方式。
# 平台情况
贵州执业药师继续教育在线培训,网址是 https://gzzyys-p.webtrn.cn/cms/ ,登录后进入个人中心能看到课程列表。课程分必修和选修两大块,每门课由若干小节组成,每小节看完要做几道练习题,全部通过才能算完成。
平台视频加载速度还行,不过偶尔会遇到卡顿。防挂机机制主要是检测鼠标键盘活动,超过五六分钟不操作就会暂停视频。视频用的是标准H5方案,对浏览器兼容性不错。
# 脚本功能
针对这个平台开发的脚本实现了以下功能:
视频自动播放,持续监测状态,发现暂停自动恢复。倍速播放可调,默认1.5倍速,完全不影响理解。防挂机检测模拟,每隔一段时间自动模拟用户操作,用随机间隔更接近真人行为。课程自动切换,播完自动检测并点击下一节。静音模式可选,夜间挂机不扰民。视频卡顿自动处理,加载失败自动重试。
脚本安装地址暂时下架,有需要看页面底部联系方式。
提示
如需代学,请联系客服,支持闲鱼交易。

微信联系:yizhituziang

QQ联系:2422270452
- img: /img/weixin.jpg
name: 微信联系:yizhituziang
- img: /img/qq.jpg
name: QQ联系:2422270452
# 使用场景
第一种是工作繁忙型的,像老周那样白天要看店理货,晚上才能抽空学。脚本挂着让视频自己跑,比干等着强多了。
第二种是拖延症晚期,去年遵义某读者国庆前两天才想起还有十几节课没刷,连续熬通宵。用脚本加1.5倍速勉强能搞定。
第三种是内容重复型的,执业药师继续教育每年内容差不多,用脚本挂着可以跳过已经学过的部分。
# 使用建议
倍速建议先从1.25倍开始试,感觉影响不大再调到1.5倍。不要一上来就用2倍速,有些内容需要理解记忆,太快了可能跟不上。
浏览器推荐Chrome或者Edge,这俩对视频支持最好。360浏览器要开极速模式。
进度方面每隔一两个小时检查一下,虽然脚本会尽量保证进度保存,但万一出问题还能及时发现。
测试题部分是得自己做,脚本只负责视频部分。不过视频认真看了的话,做题也不会太难。
# 技术细节
平台视频是标准video标签,实现起来最简单。主要问题是防挂机检测比较敏感,脚本用了随机时间间隔模拟鼠标移动,间隔在5到10秒之间随机,更接近真人操作。
进度保存机制是轮询向服务器报告,间隔有长有短,脚本会在关键节点触发额外的保存请求,减少意外关闭时的进度损失。
# 常见问题
多标签页同时刷不同课程是不行的,平台会检测同一账号多处登录。倍速设置目前稳定,但不排除以后平台升级会改。脚本安装地址暂时下架,有需要找客服。遇到视频加载不出来可以刷新重试,脚本会自动恢复。
# 小结
贵州执业药师继续教育在线培训,网址 https://gzzyys-p.webtrn.cn/cms/ ,脚本能帮你自动完成视频观看部分。倍速建议1.5倍,浏览器推荐Chrome或Edge,安装地址暂时下架需要找其他方式。有问题可以留言,祝学习顺利。
# 核心代码
(function() {
'use strict';
const CONFIG = {
speed: 1.5,
checkInterval: 600,
activityInterval: 7500,
maxRetryAttempts: 5,
initialDelay: 3000,
stallThreshold: 3000,
randomRangeMin: 5000,
randomRangeMax: 10000,
saveInterval: 12000,
nextClickDelay: 2000
};
let retryCount = 0;
let previousTime = 0;
let stallCounter = 0;
let activityTimer = null;
let saveTimer = null;
let checkTimer = null;
function detectPlatform() {
const hostname = window.location.hostname;
if (hostname.includes('gzzyys-p.webtrn.cn')) {
return 'guizhou';
}
return 'unknown';
}
function getPlatformConfig(platform) {
const configs = {
guizhou: {
videoSelectors: [
'video',
'#video-player video',
'.video-player video',
'[class*="player"] video',
'[id*="video"] video'
],
nextSelectors: ['.next-btn', '.btn-next', '[class*="next"]', 'button.next', '.next-lesson'],
activityInterval: 6500,
checkInterval: 550
}
};
return configs[platform] || configs.guizhou;
}
function locateVideoElement(selectors) {
for (const selector of selectors) {
const el = document.querySelector(selector);
if (el) {
return { type: 'html5', element: el };
}
}
const iframes = document.querySelectorAll('iframe');
for (const iframe of iframes) {
try {
for (const selector of selectors) {
if (selector.includes('iframe')) continue;
const innerEl = iframe.contentDocument.querySelector(selector);
if (innerEl) {
return { type: 'iframe', element: innerEl, frame: iframe };
}
}
} catch (e) {}
}
return null;
}
function waitForVideo(callback, maxAttempts) {
let attempts = 0;
const platform = detectPlatform();
const config = getPlatformConfig(platform);
const interval = setInterval(() => {
const videoInfo = locateVideoElement(config.videoSelectors);
if (videoInfo || attempts >= maxAttempts) {
clearInterval(interval);
callback(videoInfo);
}
attempts++;
}, CONFIG.initialDelay);
}
function setSpeed(videoInfo, speed) {
if (!videoInfo) return;
if (videoInfo.type === 'html5' || videoInfo.type === 'iframe') {
const video = videoInfo.element;
if (video && video.playbackRate !== speed) {
video.playbackRate = speed;
}
}
}
function playVideo(videoInfo) {
if (!videoInfo) return;
if (videoInfo.type === 'html5' || videoInfo.type === 'iframe') {
const video = videoInfo.element;
if (video && video.paused) {
video.play().catch(() => {
retryCount++;
if (retryCount < CONFIG.maxRetryAttempts) {
setTimeout(() => playVideo(videoInfo), 2000);
}
});
}
}
}
function pauseVideo(videoInfo) {
if (!videoInfo) return;
if (videoInfo.type === 'html5' || videoInfo.type === 'iframe') {
const video = videoInfo.element;
if (video && !video.paused) {
video.pause();
}
}
}
function getCurrentTime(videoInfo) {
if (!videoInfo) return 0;
if (videoInfo.type === 'html5' || videoInfo.type === 'iframe') {
return videoInfo.element ? videoInfo.element.currentTime : 0;
}
return 0;
}
function detectStall(videoInfo) {
if (!videoInfo) return;
const currentTime = getCurrentTime(videoInfo);
if (videoInfo.type === 'html5' || videoInfo.type === 'iframe') {
const video = videoInfo.element;
if (video && !video.paused && currentTime === previousTime && video.readyState > 0) {
stallCounter++;
if (stallCounter > 2) {
pauseVideo(videoInfo);
setTimeout(() => {
playVideo(videoInfo);
stallCounter = 0;
}, 1000);
}
} else {
stallCounter = 0;
}
}
previousTime = currentTime;
}
function generateRandomInterval(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function simulateActivity() {
const activities = [
{ type: 'mousemove', options: { bubbles: true, cancelable: true, clientX: Math.random() * window.innerWidth, clientY: Math.random() * window.innerHeight } },
{ type: 'click', options: { bubbles: true, cancelable: true } },
{ type: 'keydown', options: { bubbles: true, cancelable: true } },
{ type: 'scroll', options: { bubbles: true, cancelable: true } }
];
const activity = activities[Math.floor(Math.random() * activities.length)];
if (activity.type === 'scroll') {
window.scrollBy({
top: Math.random() * 100,
left: 0,
behavior: 'smooth'
});
} else {
document.dispatchEvent(new MouseEvent(activity.type, activity.options));
}
}
function startRandomActivity(interval) {
if (activityTimer) clearInterval(activityTimer);
activityTimer = setInterval(() => {
simulateActivity();
}, interval);
}
function findNextButton(selectors) {
for (const sel of selectors) {
const btn = document.querySelector(sel);
if (btn && btn.offsetParent !== null && !btn.disabled) {
return btn;
}
}
const allButtons = document.querySelectorAll('button');
for (const btn of allButtons) {
const text = btn.textContent.toLowerCase();
if ((text.includes('下一') || text.includes('next') || text.includes('继续')) &&
btn.offsetParent !== null && !btn.disabled) {
return btn;
}
}
return null;
}
function triggerProgressSave() {
const saveButtons = document.querySelectorAll('[class*="save"], [class*="submit"], button.save');
saveButtons.forEach(btn => {
if (btn.offsetParent !== null) {
btn.click();
}
});
}
function setupAutoSave(interval) {
if (saveTimer) clearInterval(saveTimer);
saveTimer = setInterval(() => {
triggerProgressSave();
}, interval);
}
function handleVideoEnded(videoInfo) {
const platform = detectPlatform();
const config = getPlatformConfig(platform);
setTimeout(() => {
const nextBtn = findNextButton(config.nextSelectors);
if (nextBtn) {
nextBtn.click();
}
}, CONFIG.nextClickDelay);
}
function start(videoInfo) {
if (!videoInfo) return;
const platform = detectPlatform();
const config = getPlatformConfig(platform);
setSpeed(videoInfo, CONFIG.speed);
playVideo(videoInfo);
checkTimer = setInterval(() => {
setSpeed(videoInfo, CONFIG.speed);
detectStall(videoInfo);
}, config.checkInterval);
startRandomActivity(config.activityInterval);
setupAutoSave(CONFIG.saveInterval);
if (videoInfo.type === 'html5' || videoInfo.type === 'iframe') {
videoInfo.element.addEventListener('ended', () => handleVideoEnded(videoInfo));
}
const observer = new MutationObserver(() => {
setSpeed(videoInfo, CONFIG.speed);
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
function initialize() {
const platform = detectPlatform();
if (platform === 'unknown') {
console.log('未识别平台,使用默认配置');
} else {
console.log('检测到平台:', platform);
}
waitForVideo(videoInfo => {
if (videoInfo) {
console.log('视频元素已找到,开始自动化');
start(videoInfo);
} else {
console.log('未找到视频元素');
}
}, 20);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
initialize();
}
window.addEventListener('beforeunload', () => {
if (saveTimer) clearInterval(saveTimer);
if (activityTimer) clearInterval(activityTimer);
if (checkTimer) clearInterval(checkTimer);
});
})();