实现挂视频的三种方法
前言
需要在某学习课堂挂视频。该网站视频播放期间没有干扰,所以只要实现将课程下所有的视频片段从头走到尾即可。
- 之前也做过一样的挂视频py脚本,使用python+selenium实现。所以起初就想使用同样的方式来实现,这是第一种方法。缺点:需要python,需要selenium webdriver。相对其它两种方法优点是啥,目前还真想不出。
- 由于视频的播放动作和点击下一片段都是使用selenium的webdriver.execute_script()函数把js代码发送至浏览器中执行实现的,就是向页面中添加一些js代码。突然想起这样的实现方式使用油猴脚本不就完了吗?还不需要python和webdriver,只要在安装油猴的浏览器上打开该视频页面就能添加自动播放功能。这是第二种方法。缺点:需要能安装油猴的浏览器(firefox/chreom/edge)、并且安装油猴扩展、还得在油猴中新增脚本,对计算机小白确实还有点不够友好。优点:直接就在浏览器中实现功能,而且打开视频页面就自动起作用,不需要其它什么操作。
- 受Reabble的Sent to Kindle第三种使用方式的启发,把js代码做成一个书签按钮,然后进入视频页面点击一下该书签按钮就可以了。缺点:得知道怎么设置书签按钮,并把浏览器中设置“显示收藏夹栏”。优点:只要把js代码事先做成一个书签(和IE收藏夹中网页快捷方式一样的东西,不同的叫法),然后你带到任意一个浏览器,只要点击收藏夹栏中该书签就能启用自动播放功能,使用起来方便简单。
三种方法,我比较倾向于使用第三种。因为只要记下那段js代码,不管在哪、使用什么浏览器,它都能用,并不需要安装其它软件或者扩展。
第一种方法:phthon+selenium
V0.1版实现了基本功能,然后V0.2版做了面向对象的重构。因为想到更方便的实现方法,也就没再仔细去继续优化代码。
# 通服云课堂 自动播放视频
# V0.2 :功能重构
import time
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
LOGIN_URL = 'https://www.ccspx.com.cn/login'
USER = '*******'
PWD = '*******'
driver = webdriver.Edge(executable_path='msedgedriver.exe')
class Player:
def __init__(self, parallel_num, proceed, task_center_title='任务中心'):
self.parallel_num = parallel_num
self.proceed = proceed
self.task_center_title = task_center_title
self.tasks = []
self.logined = False
def run(self):
self._login()
self._create_tasks()
for task in self.tasks:
task.start()
time.sleep(3)
for task in self.tasks:
print(f'{task.name}\n{task.command}\n{task.video_fragment_commands}\n----------------------')
while self.tasks:
time.sleep(10)
for i in range(len(self.tasks)):
self.tasks[i].check_video_ended()
if self.tasks[i].completed:
self.tasks.remove(self.tasks[i])
def _login(self):
driver.get(LOGIN_URL)
time.sleep(1)
driver.find_element_by_id('username').send_keys(USER)
driver.find_element_by_id('password').send_keys(PWD)
driver.find_element_by_id('validateCode').click()
while True:
try:
driver.find_element_by_id('username')
except NoSuchElementException:
self.logined = True
break
time.sleep(2)
def _create_tasks(self):
window = None
while window is None:
time.sleep(10)
for w in driver.window_handles:
driver.switch_to.window(w)
if driver.title == self.task_center_title:
window = w
driver.switch_to.window(window)
time.sleep(2)
task_commands = [a.get_attribute('onclick') for a in
driver.find_elements_by_xpath('//div[@id="dataList"]//li/a')]
progresses = [span.text for span in driver.find_elements_by_xpath('//div[@id="dataList"]//li//span')]
for c, p in zip(task_commands, progresses):
if self.proceed:
if p != '100%':
self.tasks.append(Task(command=c, parent_window=window, proceed=self.proceed))
else:
self.tasks.append(Task(command=c, parent_window=window, proceed=self.proceed))
class Task:
def __init__(self, command, parent_window, proceed):
self.command = command
self.parent_window = parent_window
self.proceed = proceed
self.running = False
self.completed = False
self.name, self.window = None, None
self.video_fragment_commands = []
def start(self):
driver.switch_to.window(self.parent_window)
driver.execute_script(self.command)
time.sleep(3)
self.window = driver.window_handles[-1]
driver.switch_to.window(self.window)
self.name = driver.title
self._get_video_fragments()
self._play()
def _get_video_fragments(self):
driver.switch_to.window(self.window)
fragment_commands = [li.get_attribute('onclick') for li in
driver.find_elements_by_xpath('//li[contains(@class, "task-item")]')]
progresses = [sp.text for sp in driver.find_elements_by_xpath('//li[contains(@class, "task-item")]//span')][
2::3]
for c, p in zip(fragment_commands, progresses):
if self.proceed:
if p != '100%':
self.video_fragment_commands.append(c)
else:
self.video_fragment_commands.append(c)
def _play(self):
if len(self.video_fragment_commands) != 0:
driver.switch_to.window(self.window)
driver.execute_script(self.video_fragment_commands[0])
time.sleep(2)
driver.execute_script('document.getElementsByTagName("video")[0].play();')
else:
self.completed = True
driver.execute_script('alert("页面无视频或采集错误");')
def check_video_ended(self):
driver.switch_to.window(self.window)
if driver.find_element_by_tag_name('video').get_property('ended'):
self.video_fragment_commands.pop(0)
if len(self.video_fragment_commands) == 0:
self.completed = True
driver.execute_script('alert("视频播放完毕");')
# driver.close()
else:
self._play()
else:
driver.execute_script('document.getElementsByTagName("video")[0].play();')
def __repr__(self):
return f'{self.__class__.__name__}(name={self.name}, command={self.command}, ' \
f'video_fragments={self.video_fragment_commands})'
def main():
player = Player(parallel_num=5, proceed=False)
player.run()
if __name__ == '__main__':
main()
第二种方法:使用油猴扩展
思路:添加一个定时调用器 setInterval 。每隔4秒执行一次,检查视频播放是否结束(判断player.ended),以及播放下一个片段。
// ==UserScript==
// @name 通服云课堂 - 自动播放/静音
// @namespace *
// @version 0.1
// @description 自动播放“通服云课堂”视频
// @author czbuyi
// @match https://www.ccspx.com.cn/course/detail*
// @grant none
// ==/UserScript==
(function() {
if(confirm("开始视频自动播放?")){
setInterval(function(){
let player = document.getElementsByTagName('video')[0];
if(player.ended){
let all = [].slice.call(document.getElementsByClassName('task-item'));
let active = document.getElementsByClassName('task-item active')[0];
let next_index = all.indexOf(active)+1;
console.log(all.indexOf(active));
if (next_index <= all.length-1){
all[next_index].click();
}else{
alert("视频播放结束");
window.close();
}
};
player.play();
player.muted=true;
}, 4000)
}
// Your code here...
})();
第三种方法:使用标签按钮
js代码与第二种方法是一样的。为了能做成标签,js代码前面需加入"javascript:"并且去掉回车,整理成一行。
javascript:(function(){
if(confirm("开始视频自动播放?")){
setInterval(function(){
let player=document.getElementsByTagName('video')[0];if(player.ended){
let all=[].slice.call(document.getElementsByClassName('task-item'));let active=document.getElementsByClassName('task-item active')[0];let next_index=all.indexOf(active)+1;if(next_index<=all.length-1){
all[next_index].click();}else{
alert("视频播放结束");window.close();}};player.play();player.muted=true;},4000)}})();
记录几个坑
- 在网页中查找操作某元素或者测试js代码,之前我一直都是使用运行代码(通过python,或者html、js脚本)来检测,每次可能要涉及登录、点击至目标页面、刷新等等,浪费了非常多的时间精力。后来才发现可以在浏览器“开发者调试工具”(按F12调出来)的控制台下直接输入测试,马上就看到结果,非常直接方便。
- 这个网站的视频播放页面里 video 元素会变化,所以每次操作都需要重新获取一下 video 元素。也因同样的问题,不能为 video 元素添加 ended 事件来处理视频片段播放完毕后的操作。
- 为了实现连续播放,需要找到下一个片段的il元素。但是各个页面结构有些差异,只好将页面内所有带“task-item”类的il全部找出来转成数组,再查找“task-item active”类的il元素,通过检查其在数组中的位置来确定下一个片段的il。
- js的document.getElementsBy***返回的是一个HTMLCollection对象,它类似数组,可以用for遍历,但没有indexOf的函数,不能确定某元素在对象中的位置。因为在实现播放下一下片段,我需要找到当前“task-item active”类的il,并click下一个il元素,所以只能把HTMLCollection对象转换成数组:all = [].slice.call(document.getElementsByClassName(‘task-item’))
- html元素在<>内定义的属性可以使用getAttribute(“属性名”)来获取,比如 video元素.getAttribute(“scr”) 。但video元素的状态,比如播放结束"ended"、静音状态"muted"等,直接使用 video元素.ended 来判断。